chore: Merge pull request 'backups-rewrite' (#228) from backups-rewrite into master
continuous-integration/drone/push Build is failing Details

Reviewed-on: #228
Reviewed-by: NaiJi  <naiji@udongein.xyz>
pull/229/head
NaiJi ✨ 2023-07-03 23:39:00 +03:00
commit 8bc1121206
63 changed files with 9219 additions and 10980 deletions

View File

@ -162,6 +162,7 @@
}, },
"backup": { "backup": {
"card_title": "Backup", "card_title": "Backup",
"card_subtitle": "Manage your backups",
"description": "Will save your day in case of incident: hackers attack, server deletion, etc.", "description": "Will save your day in case of incident: hackers attack, server deletion, etc.",
"reupload_key": "Force reupload key", "reupload_key": "Force reupload key",
"reuploaded_key": "Key reuploaded", "reuploaded_key": "Key reuploaded",
@ -176,7 +177,27 @@
"restore_alert": "You are about to restore from backup created on {}. All current data will be lost. Are you sure?", "restore_alert": "You are about to restore from backup created on {}. All current data will be lost. Are you sure?",
"refresh": "Refresh status", "refresh": "Refresh status",
"refetch_backups": "Refetch backup list", "refetch_backups": "Refetch backup list",
"refetching_list": "In a few minutes list will be updated" "refetch_backups_subtitle": "Invalidate cache and refetch data from your storage provider. May cause additional charges.",
"reupload_key_subtitle": "Will instruct the server to initialize backup storage again. Use if something is broken.",
"refetching_list": "In a few minutes list will be updated",
"select_all": "Backup everything",
"create_new_select_heading": "Select what to backup",
"start": "Start backup",
"service_busy": "Another backup operation is in progress",
"latest_snapshots": "Latest snapshots",
"latest_snapshots_subtitle": "Showing last 15 snapshots",
"show_more": "Show more",
"autobackup_period_title": "Automatic backups period",
"autobackup_period_subtitle": "Backups created every {period}",
"autobackup_period_never": "Automatic backups are disabled",
"autobackup_period_every": "Every {period}",
"autobackup_period_disable": "Disable automatic backups",
"autobackup_custom": "Custom",
"autobackup_custom_hint": "Enter custom period in minutes",
"autobackup_set_period": "Set period",
"autobackup_period_set": "Period set",
"pending_jobs": "Currently running backup jobs",
"snapshots_title": "Snapshot list"
}, },
"storage": { "storage": {
"card_title": "Server Storage", "card_title": "Server Storage",
@ -210,6 +231,7 @@
"enable": "Enable service", "enable": "Enable service",
"move": "Move to another volume", "move": "Move to another volume",
"uses": "Uses {usage} on {volume}", "uses": "Uses {usage} on {volume}",
"snapshots": "Backup snapshots",
"status": { "status": {
"active": "Up and running", "active": "Up and running",
"inactive": "Stopped", "inactive": "Stopped",
@ -514,4 +536,4 @@
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again", "reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
"cubit_statuses": "Cubit loading statuses" "cubit_statuses": "Cubit loading statuses"
} }
} }

View File

@ -4,7 +4,7 @@ import 'dart:typed_data';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
@ -15,12 +15,13 @@ class HiveConfig {
Hive.registerAdapter(UserAdapter()); Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(ServerHostingDetailsAdapter()); Hive.registerAdapter(ServerHostingDetailsAdapter());
Hive.registerAdapter(ServerDomainAdapter()); Hive.registerAdapter(ServerDomainAdapter());
Hive.registerAdapter(BackblazeCredentialAdapter()); Hive.registerAdapter(BackupsCredentialAdapter());
Hive.registerAdapter(BackblazeBucketAdapter()); Hive.registerAdapter(BackblazeBucketAdapter());
Hive.registerAdapter(ServerVolumeAdapter()); Hive.registerAdapter(ServerVolumeAdapter());
Hive.registerAdapter(UserTypeAdapter()); Hive.registerAdapter(UserTypeAdapter());
Hive.registerAdapter(DnsProviderTypeAdapter()); Hive.registerAdapter(DnsProviderTypeAdapter());
Hive.registerAdapter(ServerProviderTypeAdapter()); Hive.registerAdapter(ServerProviderTypeAdapter());
Hive.registerAdapter(BackupsProviderTypeAdapter());
await Hive.openBox(BNames.appSettingsBox); await Hive.openBox(BNames.appSettingsBox);
@ -110,7 +111,7 @@ class BNames {
/// A [ServerHostingDetails] field of [serverInstallationBox] box. /// A [ServerHostingDetails] field of [serverInstallationBox] box.
static String serverDetails = 'hetznerServer'; static String serverDetails = 'hetznerServer';
/// A [BackblazeCredential] field of [serverInstallationBox] box. /// A [BackupsCredential] field of [serverInstallationBox] box.
static String backblazeCredential = 'backblazeKey'; static String backblazeCredential = 'backblazeKey';
/// A [BackblazeBucket] field of [serverInstallationBox] box. /// A [BackblazeBucket] field of [serverInstallationBox] box.

View File

@ -0,0 +1,93 @@
query BackupConfiguration {
backup {
configuration {
autobackupPeriod
encryptionKey
isInitialized
locationId
locationName
provider
}
}
}
query AllBackupSnapshots {
backup {
allSnapshots {
id
createdAt
service {
displayName
id
}
}
}
}
fragment genericBackupConfigReturn on GenericBackupConfigReturn {
code
message
success
configuration {
provider
encryptionKey
isInitialized
autobackupPeriod
locationName
locationId
}
}
mutation ForceSnapshotsReload {
backup {
forceSnapshotsReload {
...basicMutationReturnFields
}
}
}
mutation StartBackup($serviceId: String!) {
backup {
startBackup(serviceId: $serviceId) {
...basicMutationReturnFields
job {
...basicApiJobsFields
}
}
}
}
mutation SetAutobackupPeriod($period: Int = null) {
backup {
setAutobackupPeriod(period: $period) {
...genericBackupConfigReturn
}
}
}
mutation RemoveRepository {
backup {
removeRepository {
...genericBackupConfigReturn
}
}
}
mutation InitializeRepository($repository: InitializeRepositoryInput!) {
backup {
initializeRepository(repository: $repository) {
...genericBackupConfigReturn
}
}
}
mutation RestoreBackup($snapshotId: String!) {
backup {
restoreBackup(snapshotId: $snapshotId) {
...basicMutationReturnFields
job {
...basicApiJobsFields
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,3 @@
fragment basicMutationReturnFields on MutationReturnInterface{
code
message
success
}
query GetServerDiskVolumes { query GetServerDiskVolumes {
storage { storage {
volumes { volumes {
@ -53,17 +47,7 @@ mutation MigrateToBinds($input: MigrateToBindsInput!) {
migrateToBinds(input: $input) { migrateToBinds(input: $input) {
...basicMutationReturnFields ...basicMutationReturnFields
job { job {
createdAt ...basicApiJobsFields
description
error
finishedAt
name
progress
result
status
statusText
uid
updatedAt
} }
} }
} }

View File

@ -1,54 +1,65 @@
type Alert { type Alert {
message: String!
severity: Severity! severity: Severity!
timestamp: DateTime
title: String! title: String!
message: String!
timestamp: DateTime
} }
type Api { type Api {
devices: [ApiDevice!]!
recoveryKey: ApiRecoveryKeyStatus!
version: String! version: String!
recoveryKey: ApiRecoveryKeyStatus!
devices: [ApiDevice!]!
} }
type ApiDevice { type ApiDevice {
name: String!
creationDate: DateTime! creationDate: DateTime!
isCaller: Boolean! isCaller: Boolean!
name: String!
} }
type ApiJob { type ApiJob {
createdAt: DateTime! uid: String!
description: String! typeId: String!
error: String
finishedAt: DateTime
name: String! name: String!
progress: Int description: String!
result: String
status: String! status: String!
statusText: String statusText: String
uid: String! progress: Int
createdAt: DateTime!
updatedAt: DateTime! updatedAt: DateTime!
finishedAt: DateTime
error: String
result: String
} }
type ApiKeyMutationReturn implements MutationReturnInterface { type ApiKeyMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int! code: Int!
key: String key: String
message: String! }
success: Boolean!
type ApiMutations {
getNewRecoveryApiKey(limits: RecoveryKeyLimitsInput = null): ApiKeyMutationReturn!
useRecoveryApiKey(input: UseRecoveryKeyInput!): DeviceApiTokenMutationReturn!
refreshDeviceApiToken: DeviceApiTokenMutationReturn!
deleteDeviceApiToken(device: String!): GenericMutationReturn!
getNewDeviceApiKey: ApiKeyMutationReturn!
invalidateNewDeviceApiKey: GenericMutationReturn!
authorizeWithNewDeviceApiKey(input: UseNewDeviceKeyInput!): DeviceApiTokenMutationReturn!
} }
type ApiRecoveryKeyStatus { type ApiRecoveryKeyStatus {
creationDate: DateTime
exists: Boolean! exists: Boolean!
valid: Boolean!
creationDate: DateTime
expirationDate: DateTime expirationDate: DateTime
usesLeft: Int usesLeft: Int
valid: Boolean!
} }
type AutoUpgradeOptions { type AutoUpgradeOptions {
allowReboot: Boolean!
enable: Boolean! enable: Boolean!
allowReboot: Boolean!
} }
input AutoUpgradeSettingsInput { input AutoUpgradeSettingsInput {
@ -57,53 +68,102 @@ input AutoUpgradeSettingsInput {
} }
type AutoUpgradeSettingsMutationReturn implements MutationReturnInterface { type AutoUpgradeSettingsMutationReturn implements MutationReturnInterface {
allowReboot: Boolean! success: Boolean!
message: String!
code: Int! code: Int!
enableAutoUpgrade: Boolean! enableAutoUpgrade: Boolean!
message: String! allowReboot: Boolean!
success: Boolean! }
type Backup {
configuration: BackupConfiguration!
allSnapshots: [SnapshotInfo!]!
}
type BackupConfiguration {
provider: BackupProvider!
encryptionKey: String!
isInitialized: Boolean!
autobackupPeriod: Int
locationName: String
locationId: String
}
type BackupMutations {
initializeRepository(repository: InitializeRepositoryInput!): GenericBackupConfigReturn!
removeRepository: GenericBackupConfigReturn!
setAutobackupPeriod(period: Int = null): GenericBackupConfigReturn!
startBackup(serviceId: String!): GenericJobMutationReturn!
restoreBackup(snapshotId: String!): GenericJobMutationReturn!
forceSnapshotsReload: GenericMutationReturn!
}
enum BackupProvider {
BACKBLAZE
NONE
MEMORY
FILE
} }
"""Date with time (isoformat)""" """Date with time (isoformat)"""
scalar DateTime scalar DateTime
type DeviceApiTokenMutationReturn implements MutationReturnInterface { type DeviceApiTokenMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean! success: Boolean!
message: String!
code: Int!
token: String token: String
} }
enum DnsProvider { enum DnsProvider {
CLOUDFLARE, CLOUDFLARE
DESEC,
DIGITALOCEAN DIGITALOCEAN
DESEC
} }
type DnsRecord { type DnsRecord {
content: String!
name: String!
priority: Int
recordType: String! recordType: String!
name: String!
content: String!
ttl: Int! ttl: Int!
priority: Int
} }
type GenericJobButationReturn implements MutationReturnInterface { type GenericBackupConfigReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int!
configuration: BackupConfiguration
}
type GenericJobMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int! code: Int!
job: ApiJob job: ApiJob
message: String!
success: Boolean!
} }
type GenericMutationReturn implements MutationReturnInterface { type GenericMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean! success: Boolean!
message: String!
code: Int!
}
input InitializeRepositoryInput {
provider: BackupProvider!
locationId: String!
locationName: String!
login: String!
password: String!
} }
type Job { type Job {
getJob(jobId: String!): ApiJob
getJobs: [ApiJob!]! getJobs: [ApiJob!]!
getJob(jobId: String!): ApiJob
}
type JobMutations {
removeJob(jobId: String!): GenericMutationReturn!
} }
input MigrateToBindsInput { input MigrateToBindsInput {
@ -120,52 +180,60 @@ input MoveServiceInput {
} }
type Mutation { type Mutation {
addSshKey(sshInput: SshMutationInput!): UserMutationReturn! getNewRecoveryApiKey(limits: RecoveryKeyLimitsInput = null): ApiKeyMutationReturn! @deprecated(reason: "Use `api.get_new_recovery_api_key` instead")
authorizeWithNewDeviceApiKey(input: UseNewDeviceKeyInput!): DeviceApiTokenMutationReturn! useRecoveryApiKey(input: UseRecoveryKeyInput!): DeviceApiTokenMutationReturn! @deprecated(reason: "Use `api.use_recovery_api_key` instead")
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn! refreshDeviceApiToken: DeviceApiTokenMutationReturn! @deprecated(reason: "Use `api.refresh_device_api_token` instead")
changeTimezone(timezone: String!): TimezoneMutationReturn! deleteDeviceApiToken(device: String!): GenericMutationReturn! @deprecated(reason: "Use `api.delete_device_api_token` instead")
createUser(user: UserMutationInput!): UserMutationReturn! getNewDeviceApiKey: ApiKeyMutationReturn! @deprecated(reason: "Use `api.get_new_device_api_key` instead")
deleteDeviceApiToken(device: String!): GenericMutationReturn! invalidateNewDeviceApiKey: GenericMutationReturn! @deprecated(reason: "Use `api.invalidate_new_device_api_key` instead")
deleteUser(username: String!): GenericMutationReturn! authorizeWithNewDeviceApiKey(input: UseNewDeviceKeyInput!): DeviceApiTokenMutationReturn! @deprecated(reason: "Use `api.authorize_with_new_device_api_key` instead")
disableService(serviceId: String!): ServiceMutationReturn! changeTimezone(timezone: String!): TimezoneMutationReturn! @deprecated(reason: "Use `system.change_timezone` instead")
enableService(serviceId: String!): ServiceMutationReturn! changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn! @deprecated(reason: "Use `system.change_auto_upgrade_settings` instead")
getNewDeviceApiKey: ApiKeyMutationReturn! runSystemRebuild: GenericMutationReturn! @deprecated(reason: "Use `system.run_system_rebuild` instead")
getNewRecoveryApiKey(limits: RecoveryKeyLimitsInput = null): ApiKeyMutationReturn! runSystemRollback: GenericMutationReturn! @deprecated(reason: "Use `system.run_system_rollback` instead")
invalidateNewDeviceApiKey: GenericMutationReturn! runSystemUpgrade: GenericMutationReturn! @deprecated(reason: "Use `system.run_system_upgrade` instead")
migrateToBinds(input: MigrateToBindsInput!): GenericJobButationReturn! rebootSystem: GenericMutationReturn! @deprecated(reason: "Use `system.reboot_system` instead")
mountVolume(name: String!): GenericMutationReturn! pullRepositoryChanges: GenericMutationReturn! @deprecated(reason: "Use `system.pull_repository_changes` instead")
moveService(input: MoveServiceInput!): ServiceJobMutationReturn! createUser(user: UserMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.create_user` instead")
pullRepositoryChanges: GenericMutationReturn! deleteUser(username: String!): GenericMutationReturn! @deprecated(reason: "Use `users.delete_user` instead")
rebootSystem: GenericMutationReturn! updateUser(user: UserMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.update_user` instead")
refreshDeviceApiToken: DeviceApiTokenMutationReturn! addSshKey(sshInput: SshMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.add_ssh_key` instead")
removeJob(jobId: String!): GenericMutationReturn! removeSshKey(sshInput: SshMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.remove_ssh_key` instead")
removeSshKey(sshInput: SshMutationInput!): UserMutationReturn! resizeVolume(name: String!): GenericMutationReturn! @deprecated(reason: "Use `storage.resize_volume` instead")
resizeVolume(name: String!): GenericMutationReturn! mountVolume(name: String!): GenericMutationReturn! @deprecated(reason: "Use `storage.mount_volume` instead")
restartService(serviceId: String!): ServiceMutationReturn! unmountVolume(name: String!): GenericMutationReturn! @deprecated(reason: "Use `storage.unmount_volume` instead")
runSystemRebuild: GenericMutationReturn! migrateToBinds(input: MigrateToBindsInput!): GenericJobMutationReturn! @deprecated(reason: "Use `storage.migrate_to_binds` instead")
runSystemRollback: GenericMutationReturn! enableService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.enable_service` instead")
runSystemUpgrade: GenericMutationReturn! disableService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.disable_service` instead")
startService(serviceId: String!): ServiceMutationReturn! stopService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.stop_service` instead")
stopService(serviceId: String!): ServiceMutationReturn! startService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.start_service` instead")
restartService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.restart_service` instead")
moveService(input: MoveServiceInput!): ServiceJobMutationReturn! @deprecated(reason: "Use `services.move_service` instead")
removeJob(jobId: String!): GenericMutationReturn! @deprecated(reason: "Use `jobs.remove_job` instead")
api: ApiMutations!
system: SystemMutations!
users: UsersMutations!
storage: StorageMutations!
services: ServicesMutations!
jobs: JobMutations!
backup: BackupMutations!
testMutation: GenericMutationReturn! testMutation: GenericMutationReturn!
unmountVolume(name: String!): GenericMutationReturn!
updateUser(user: UserMutationInput!): UserMutationReturn!
useRecoveryApiKey(input: UseRecoveryKeyInput!): DeviceApiTokenMutationReturn!
} }
interface MutationReturnInterface { interface MutationReturnInterface {
code: Int!
message: String!
success: Boolean! success: Boolean!
message: String!
code: Int!
} }
type Query { type Query {
api: Api! api: Api!
jobs: Job!
services: Services!
storage: Storage!
system: System! system: System!
users: Users! users: Users!
storage: Storage!
jobs: Job!
services: Services!
backup: Backup!
} }
input RecoveryKeyLimitsInput { input RecoveryKeyLimitsInput {
@ -179,61 +247,79 @@ enum ServerProvider {
} }
type Service { type Service {
description: String!
displayName: String!
dnsRecords: [DnsRecord!]
id: String! id: String!
isEnabled: Boolean! displayName: String!
description: String!
svgIcon: String!
isMovable: Boolean! isMovable: Boolean!
isRequired: Boolean! isRequired: Boolean!
isEnabled: Boolean!
canBeBackedUp: Boolean!
backupDescription: String!
status: ServiceStatusEnum! status: ServiceStatusEnum!
storageUsage: ServiceStorageUsage!
svgIcon: String!
url: String url: String
dnsRecords: [DnsRecord!]
storageUsage: ServiceStorageUsage!
backupSnapshots: [SnapshotInfo!]
} }
type ServiceJobMutationReturn implements MutationReturnInterface { type ServiceJobMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int! code: Int!
job: ApiJob job: ApiJob
message: String!
service: Service service: Service
success: Boolean!
} }
type ServiceMutationReturn implements MutationReturnInterface { type ServiceMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
service: Service
success: Boolean! success: Boolean!
message: String!
code: Int!
service: Service
} }
enum ServiceStatusEnum { enum ServiceStatusEnum {
ACTIVATING
ACTIVE ACTIVE
DEACTIVATING
FAILED
INACTIVE
OFF
RELOADING RELOADING
INACTIVE
FAILED
ACTIVATING
DEACTIVATING
OFF
} }
type ServiceStorageUsage implements StorageUsageInterface { type ServiceStorageUsage implements StorageUsageInterface {
service: Service
title: String!
usedSpace: String! usedSpace: String!
volume: StorageVolume volume: StorageVolume
title: String!
service: Service
} }
type Services { type Services {
allServices: [Service!]! allServices: [Service!]!
} }
type ServicesMutations {
enableService(serviceId: String!): ServiceMutationReturn!
disableService(serviceId: String!): ServiceMutationReturn!
stopService(serviceId: String!): ServiceMutationReturn!
startService(serviceId: String!): ServiceMutationReturn!
restartService(serviceId: String!): ServiceMutationReturn!
moveService(input: MoveServiceInput!): ServiceJobMutationReturn!
}
enum Severity { enum Severity {
CRITICAL
ERROR
INFO INFO
SUCCESS
WARNING WARNING
ERROR
CRITICAL
SUCCESS
}
type SnapshotInfo {
id: String!
service: Service!
createdAt: DateTime!
} }
input SshMutationInput { input SshMutationInput {
@ -251,22 +337,29 @@ type Storage {
volumes: [StorageVolume!]! volumes: [StorageVolume!]!
} }
type StorageMutations {
resizeVolume(name: String!): GenericMutationReturn!
mountVolume(name: String!): GenericMutationReturn!
unmountVolume(name: String!): GenericMutationReturn!
migrateToBinds(input: MigrateToBindsInput!): GenericJobMutationReturn!
}
interface StorageUsageInterface { interface StorageUsageInterface {
title: String!
usedSpace: String! usedSpace: String!
volume: StorageVolume volume: StorageVolume
title: String!
} }
type StorageVolume { type StorageVolume {
freeSpace: String!
model: String
name: String!
root: Boolean!
serial: String
totalSpace: String! totalSpace: String!
freeSpace: String!
usedSpace: String!
root: Boolean!
name: String!
model: String
serial: String
type: String! type: String!
usages: [StorageUsageInterface!]! usages: [StorageUsageInterface!]!
usedSpace: String!
} }
type Subscription { type Subscription {
@ -274,12 +367,12 @@ type Subscription {
} }
type System { type System {
busy: Boolean! status: Alert!
domainInfo: SystemDomainInfo! domainInfo: SystemDomainInfo!
settings: SystemSettings!
info: SystemInfo! info: SystemInfo!
provider: SystemProviderInfo! provider: SystemProviderInfo!
settings: SystemSettings! busy: Boolean!
status: Alert!
workingDirectory: String! workingDirectory: String!
} }
@ -291,14 +384,24 @@ type SystemDomainInfo {
} }
type SystemInfo { type SystemInfo {
pythonVersion: String!
systemVersion: String! systemVersion: String!
pythonVersion: String!
usingBinds: Boolean! usingBinds: Boolean!
} }
type SystemMutations {
changeTimezone(timezone: String!): TimezoneMutationReturn!
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn!
runSystemRebuild: GenericMutationReturn!
runSystemRollback: GenericMutationReturn!
runSystemUpgrade: GenericMutationReturn!
rebootSystem: GenericMutationReturn!
pullRepositoryChanges: GenericMutationReturn!
}
type SystemProviderInfo { type SystemProviderInfo {
id: String!
provider: ServerProvider! provider: ServerProvider!
id: String!
} }
type SystemSettings { type SystemSettings {
@ -308,9 +411,9 @@ type SystemSettings {
} }
type TimezoneMutationReturn implements MutationReturnInterface { type TimezoneMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean! success: Boolean!
message: String!
code: Int!
timezone: String timezone: String
} }
@ -325,9 +428,9 @@ input UseRecoveryKeyInput {
} }
type User { type User {
sshKeys: [String!]!
userType: UserType! userType: UserType!
username: String! username: String!
sshKeys: [String!]!
} }
input UserMutationInput { input UserMutationInput {
@ -336,9 +439,9 @@ input UserMutationInput {
} }
type UserMutationReturn implements MutationReturnInterface { type UserMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean! success: Boolean!
message: String!
code: Int!
user: User user: User
} }
@ -353,10 +456,10 @@ type Users {
getUser(username: String!): User getUser(username: String!): User
} }
fragment dnsRecordFields on DnsRecord { type UsersMutations {
content createUser(user: UserMutationInput!): UserMutationReturn!
name deleteUser(username: String!): GenericMutationReturn!
priority updateUser(user: UserMutationInput!): UserMutationReturn!
recordType addSshKey(sshInput: SshMutationInput!): UserMutationReturn!
ttl removeSshKey(sshInput: SshMutationInput!): UserMutationReturn!
} }

View File

@ -1,5 +1,3 @@
import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart'; import 'package:selfprivacy/utils/scalars.dart';
class Input$AutoUpgradeSettingsInput { class Input$AutoUpgradeSettingsInput {
@ -143,6 +141,190 @@ class _CopyWithStubImpl$Input$AutoUpgradeSettingsInput<TRes>
_res; _res;
} }
class Input$InitializeRepositoryInput {
factory Input$InitializeRepositoryInput({
required Enum$BackupProvider provider,
required String locationId,
required String locationName,
required String login,
required String password,
}) =>
Input$InitializeRepositoryInput._({
r'provider': provider,
r'locationId': locationId,
r'locationName': locationName,
r'login': login,
r'password': password,
});
Input$InitializeRepositoryInput._(this._$data);
factory Input$InitializeRepositoryInput.fromJson(Map<String, dynamic> data) {
final result$data = <String, dynamic>{};
final l$provider = data['provider'];
result$data['provider'] =
fromJson$Enum$BackupProvider((l$provider as String));
final l$locationId = data['locationId'];
result$data['locationId'] = (l$locationId as String);
final l$locationName = data['locationName'];
result$data['locationName'] = (l$locationName as String);
final l$login = data['login'];
result$data['login'] = (l$login as String);
final l$password = data['password'];
result$data['password'] = (l$password as String);
return Input$InitializeRepositoryInput._(result$data);
}
Map<String, dynamic> _$data;
Enum$BackupProvider get provider =>
(_$data['provider'] as Enum$BackupProvider);
String get locationId => (_$data['locationId'] as String);
String get locationName => (_$data['locationName'] as String);
String get login => (_$data['login'] as String);
String get password => (_$data['password'] as String);
Map<String, dynamic> toJson() {
final result$data = <String, dynamic>{};
final l$provider = provider;
result$data['provider'] = toJson$Enum$BackupProvider(l$provider);
final l$locationId = locationId;
result$data['locationId'] = l$locationId;
final l$locationName = locationName;
result$data['locationName'] = l$locationName;
final l$login = login;
result$data['login'] = l$login;
final l$password = password;
result$data['password'] = l$password;
return result$data;
}
CopyWith$Input$InitializeRepositoryInput<Input$InitializeRepositoryInput>
get copyWith => CopyWith$Input$InitializeRepositoryInput(
this,
(i) => i,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (!(other is Input$InitializeRepositoryInput) ||
runtimeType != other.runtimeType) {
return false;
}
final l$provider = provider;
final lOther$provider = other.provider;
if (l$provider != lOther$provider) {
return false;
}
final l$locationId = locationId;
final lOther$locationId = other.locationId;
if (l$locationId != lOther$locationId) {
return false;
}
final l$locationName = locationName;
final lOther$locationName = other.locationName;
if (l$locationName != lOther$locationName) {
return false;
}
final l$login = login;
final lOther$login = other.login;
if (l$login != lOther$login) {
return false;
}
final l$password = password;
final lOther$password = other.password;
if (l$password != lOther$password) {
return false;
}
return true;
}
@override
int get hashCode {
final l$provider = provider;
final l$locationId = locationId;
final l$locationName = locationName;
final l$login = login;
final l$password = password;
return Object.hashAll([
l$provider,
l$locationId,
l$locationName,
l$login,
l$password,
]);
}
}
abstract class CopyWith$Input$InitializeRepositoryInput<TRes> {
factory CopyWith$Input$InitializeRepositoryInput(
Input$InitializeRepositoryInput instance,
TRes Function(Input$InitializeRepositoryInput) then,
) = _CopyWithImpl$Input$InitializeRepositoryInput;
factory CopyWith$Input$InitializeRepositoryInput.stub(TRes res) =
_CopyWithStubImpl$Input$InitializeRepositoryInput;
TRes call({
Enum$BackupProvider? provider,
String? locationId,
String? locationName,
String? login,
String? password,
});
}
class _CopyWithImpl$Input$InitializeRepositoryInput<TRes>
implements CopyWith$Input$InitializeRepositoryInput<TRes> {
_CopyWithImpl$Input$InitializeRepositoryInput(
this._instance,
this._then,
);
final Input$InitializeRepositoryInput _instance;
final TRes Function(Input$InitializeRepositoryInput) _then;
static const _undefined = <dynamic, dynamic>{};
TRes call({
Object? provider = _undefined,
Object? locationId = _undefined,
Object? locationName = _undefined,
Object? login = _undefined,
Object? password = _undefined,
}) =>
_then(Input$InitializeRepositoryInput._({
..._instance._$data,
if (provider != _undefined && provider != null)
'provider': (provider as Enum$BackupProvider),
if (locationId != _undefined && locationId != null)
'locationId': (locationId as String),
if (locationName != _undefined && locationName != null)
'locationName': (locationName as String),
if (login != _undefined && login != null) 'login': (login as String),
if (password != _undefined && password != null)
'password': (password as String),
}));
}
class _CopyWithStubImpl$Input$InitializeRepositoryInput<TRes>
implements CopyWith$Input$InitializeRepositoryInput<TRes> {
_CopyWithStubImpl$Input$InitializeRepositoryInput(this._res);
TRes _res;
call({
Enum$BackupProvider? provider,
String? locationId,
String? locationName,
String? login,
String? password,
}) =>
_res;
}
class Input$MigrateToBindsInput { class Input$MigrateToBindsInput {
factory Input$MigrateToBindsInput({ factory Input$MigrateToBindsInput({
required String emailBlockDevice, required String emailBlockDevice,
@ -1096,16 +1278,48 @@ class _CopyWithStubImpl$Input$UserMutationInput<TRes>
_res; _res;
} }
enum Enum$DnsProvider { CLOUDFLARE, DESEC, DIGITALOCEAN, $unknown } enum Enum$BackupProvider { BACKBLAZE, NONE, MEMORY, FILE, $unknown }
String toJson$Enum$BackupProvider(Enum$BackupProvider e) {
switch (e) {
case Enum$BackupProvider.BACKBLAZE:
return r'BACKBLAZE';
case Enum$BackupProvider.NONE:
return r'NONE';
case Enum$BackupProvider.MEMORY:
return r'MEMORY';
case Enum$BackupProvider.FILE:
return r'FILE';
case Enum$BackupProvider.$unknown:
return r'$unknown';
}
}
Enum$BackupProvider fromJson$Enum$BackupProvider(String value) {
switch (value) {
case r'BACKBLAZE':
return Enum$BackupProvider.BACKBLAZE;
case r'NONE':
return Enum$BackupProvider.NONE;
case r'MEMORY':
return Enum$BackupProvider.MEMORY;
case r'FILE':
return Enum$BackupProvider.FILE;
default:
return Enum$BackupProvider.$unknown;
}
}
enum Enum$DnsProvider { CLOUDFLARE, DIGITALOCEAN, DESEC, $unknown }
String toJson$Enum$DnsProvider(Enum$DnsProvider e) { String toJson$Enum$DnsProvider(Enum$DnsProvider e) {
switch (e) { switch (e) {
case Enum$DnsProvider.CLOUDFLARE: case Enum$DnsProvider.CLOUDFLARE:
return r'CLOUDFLARE'; return r'CLOUDFLARE';
case Enum$DnsProvider.DESEC:
return r'DESEC';
case Enum$DnsProvider.DIGITALOCEAN: case Enum$DnsProvider.DIGITALOCEAN:
return r'DIGITALOCEAN'; return r'DIGITALOCEAN';
case Enum$DnsProvider.DESEC:
return r'DESEC';
case Enum$DnsProvider.$unknown: case Enum$DnsProvider.$unknown:
return r'$unknown'; return r'$unknown';
} }
@ -1115,10 +1329,10 @@ Enum$DnsProvider fromJson$Enum$DnsProvider(String value) {
switch (value) { switch (value) {
case r'CLOUDFLARE': case r'CLOUDFLARE':
return Enum$DnsProvider.CLOUDFLARE; return Enum$DnsProvider.CLOUDFLARE;
case r'DESEC':
return Enum$DnsProvider.DESEC;
case r'DIGITALOCEAN': case r'DIGITALOCEAN':
return Enum$DnsProvider.DIGITALOCEAN; return Enum$DnsProvider.DIGITALOCEAN;
case r'DESEC':
return Enum$DnsProvider.DESEC;
default: default:
return Enum$DnsProvider.$unknown; return Enum$DnsProvider.$unknown;
} }
@ -1149,32 +1363,32 @@ Enum$ServerProvider fromJson$Enum$ServerProvider(String value) {
} }
enum Enum$ServiceStatusEnum { enum Enum$ServiceStatusEnum {
ACTIVATING,
ACTIVE, ACTIVE,
DEACTIVATING,
FAILED,
INACTIVE,
OFF,
RELOADING, RELOADING,
INACTIVE,
FAILED,
ACTIVATING,
DEACTIVATING,
OFF,
$unknown $unknown
} }
String toJson$Enum$ServiceStatusEnum(Enum$ServiceStatusEnum e) { String toJson$Enum$ServiceStatusEnum(Enum$ServiceStatusEnum e) {
switch (e) { switch (e) {
case Enum$ServiceStatusEnum.ACTIVATING:
return r'ACTIVATING';
case Enum$ServiceStatusEnum.ACTIVE: case Enum$ServiceStatusEnum.ACTIVE:
return r'ACTIVE'; return r'ACTIVE';
case Enum$ServiceStatusEnum.DEACTIVATING:
return r'DEACTIVATING';
case Enum$ServiceStatusEnum.FAILED:
return r'FAILED';
case Enum$ServiceStatusEnum.INACTIVE:
return r'INACTIVE';
case Enum$ServiceStatusEnum.OFF:
return r'OFF';
case Enum$ServiceStatusEnum.RELOADING: case Enum$ServiceStatusEnum.RELOADING:
return r'RELOADING'; return r'RELOADING';
case Enum$ServiceStatusEnum.INACTIVE:
return r'INACTIVE';
case Enum$ServiceStatusEnum.FAILED:
return r'FAILED';
case Enum$ServiceStatusEnum.ACTIVATING:
return r'ACTIVATING';
case Enum$ServiceStatusEnum.DEACTIVATING:
return r'DEACTIVATING';
case Enum$ServiceStatusEnum.OFF:
return r'OFF';
case Enum$ServiceStatusEnum.$unknown: case Enum$ServiceStatusEnum.$unknown:
return r'$unknown'; return r'$unknown';
} }
@ -1182,39 +1396,39 @@ String toJson$Enum$ServiceStatusEnum(Enum$ServiceStatusEnum e) {
Enum$ServiceStatusEnum fromJson$Enum$ServiceStatusEnum(String value) { Enum$ServiceStatusEnum fromJson$Enum$ServiceStatusEnum(String value) {
switch (value) { switch (value) {
case r'ACTIVATING':
return Enum$ServiceStatusEnum.ACTIVATING;
case r'ACTIVE': case r'ACTIVE':
return Enum$ServiceStatusEnum.ACTIVE; return Enum$ServiceStatusEnum.ACTIVE;
case r'DEACTIVATING':
return Enum$ServiceStatusEnum.DEACTIVATING;
case r'FAILED':
return Enum$ServiceStatusEnum.FAILED;
case r'INACTIVE':
return Enum$ServiceStatusEnum.INACTIVE;
case r'OFF':
return Enum$ServiceStatusEnum.OFF;
case r'RELOADING': case r'RELOADING':
return Enum$ServiceStatusEnum.RELOADING; return Enum$ServiceStatusEnum.RELOADING;
case r'INACTIVE':
return Enum$ServiceStatusEnum.INACTIVE;
case r'FAILED':
return Enum$ServiceStatusEnum.FAILED;
case r'ACTIVATING':
return Enum$ServiceStatusEnum.ACTIVATING;
case r'DEACTIVATING':
return Enum$ServiceStatusEnum.DEACTIVATING;
case r'OFF':
return Enum$ServiceStatusEnum.OFF;
default: default:
return Enum$ServiceStatusEnum.$unknown; return Enum$ServiceStatusEnum.$unknown;
} }
} }
enum Enum$Severity { CRITICAL, ERROR, INFO, SUCCESS, WARNING, $unknown } enum Enum$Severity { INFO, WARNING, ERROR, CRITICAL, SUCCESS, $unknown }
String toJson$Enum$Severity(Enum$Severity e) { String toJson$Enum$Severity(Enum$Severity e) {
switch (e) { switch (e) {
case Enum$Severity.CRITICAL:
return r'CRITICAL';
case Enum$Severity.ERROR:
return r'ERROR';
case Enum$Severity.INFO: case Enum$Severity.INFO:
return r'INFO'; return r'INFO';
case Enum$Severity.SUCCESS:
return r'SUCCESS';
case Enum$Severity.WARNING: case Enum$Severity.WARNING:
return r'WARNING'; return r'WARNING';
case Enum$Severity.ERROR:
return r'ERROR';
case Enum$Severity.CRITICAL:
return r'CRITICAL';
case Enum$Severity.SUCCESS:
return r'SUCCESS';
case Enum$Severity.$unknown: case Enum$Severity.$unknown:
return r'$unknown'; return r'$unknown';
} }
@ -1222,16 +1436,16 @@ String toJson$Enum$Severity(Enum$Severity e) {
Enum$Severity fromJson$Enum$Severity(String value) { Enum$Severity fromJson$Enum$Severity(String value) {
switch (value) { switch (value) {
case r'CRITICAL':
return Enum$Severity.CRITICAL;
case r'ERROR':
return Enum$Severity.ERROR;
case r'INFO': case r'INFO':
return Enum$Severity.INFO; return Enum$Severity.INFO;
case r'SUCCESS':
return Enum$Severity.SUCCESS;
case r'WARNING': case r'WARNING':
return Enum$Severity.WARNING; return Enum$Severity.WARNING;
case r'ERROR':
return Enum$Severity.ERROR;
case r'CRITICAL':
return Enum$Severity.CRITICAL;
case r'SUCCESS':
return Enum$Severity.SUCCESS;
default: default:
return Enum$Severity.$unknown; return Enum$Severity.$unknown;
} }
@ -1265,306 +1479,13 @@ Enum$UserType fromJson$Enum$UserType(String value) {
} }
} }
class Fragment$dnsRecordFields {
Fragment$dnsRecordFields({
required this.content,
required this.name,
this.priority,
required this.recordType,
required this.ttl,
this.$__typename = 'DnsRecord',
});
factory Fragment$dnsRecordFields.fromJson(Map<String, dynamic> json) {
final l$content = json['content'];
final l$name = json['name'];
final l$priority = json['priority'];
final l$recordType = json['recordType'];
final l$ttl = json['ttl'];
final l$$__typename = json['__typename'];
return Fragment$dnsRecordFields(
content: (l$content as String),
name: (l$name as String),
priority: (l$priority as int?),
recordType: (l$recordType as String),
ttl: (l$ttl as int),
$__typename: (l$$__typename as String),
);
}
final String content;
final String name;
final int? priority;
final String recordType;
final int ttl;
final String $__typename;
Map<String, dynamic> toJson() {
final _resultData = <String, dynamic>{};
final l$content = content;
_resultData['content'] = l$content;
final l$name = name;
_resultData['name'] = l$name;
final l$priority = priority;
_resultData['priority'] = l$priority;
final l$recordType = recordType;
_resultData['recordType'] = l$recordType;
final l$ttl = ttl;
_resultData['ttl'] = l$ttl;
final l$$__typename = $__typename;
_resultData['__typename'] = l$$__typename;
return _resultData;
}
@override
int get hashCode {
final l$content = content;
final l$name = name;
final l$priority = priority;
final l$recordType = recordType;
final l$ttl = ttl;
final l$$__typename = $__typename;
return Object.hashAll([
l$content,
l$name,
l$priority,
l$recordType,
l$ttl,
l$$__typename,
]);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (!(other is Fragment$dnsRecordFields) ||
runtimeType != other.runtimeType) {
return false;
}
final l$content = content;
final lOther$content = other.content;
if (l$content != lOther$content) {
return false;
}
final l$name = name;
final lOther$name = other.name;
if (l$name != lOther$name) {
return false;
}
final l$priority = priority;
final lOther$priority = other.priority;
if (l$priority != lOther$priority) {
return false;
}
final l$recordType = recordType;
final lOther$recordType = other.recordType;
if (l$recordType != lOther$recordType) {
return false;
}
final l$ttl = ttl;
final lOther$ttl = other.ttl;
if (l$ttl != lOther$ttl) {
return false;
}
final l$$__typename = $__typename;
final lOther$$__typename = other.$__typename;
if (l$$__typename != lOther$$__typename) {
return false;
}
return true;
}
}
extension UtilityExtension$Fragment$dnsRecordFields
on Fragment$dnsRecordFields {
CopyWith$Fragment$dnsRecordFields<Fragment$dnsRecordFields> get copyWith =>
CopyWith$Fragment$dnsRecordFields(
this,
(i) => i,
);
}
abstract class CopyWith$Fragment$dnsRecordFields<TRes> {
factory CopyWith$Fragment$dnsRecordFields(
Fragment$dnsRecordFields instance,
TRes Function(Fragment$dnsRecordFields) then,
) = _CopyWithImpl$Fragment$dnsRecordFields;
factory CopyWith$Fragment$dnsRecordFields.stub(TRes res) =
_CopyWithStubImpl$Fragment$dnsRecordFields;
TRes call({
String? content,
String? name,
int? priority,
String? recordType,
int? ttl,
String? $__typename,
});
}
class _CopyWithImpl$Fragment$dnsRecordFields<TRes>
implements CopyWith$Fragment$dnsRecordFields<TRes> {
_CopyWithImpl$Fragment$dnsRecordFields(
this._instance,
this._then,
);
final Fragment$dnsRecordFields _instance;
final TRes Function(Fragment$dnsRecordFields) _then;
static const _undefined = <dynamic, dynamic>{};
TRes call({
Object? content = _undefined,
Object? name = _undefined,
Object? priority = _undefined,
Object? recordType = _undefined,
Object? ttl = _undefined,
Object? $__typename = _undefined,
}) =>
_then(Fragment$dnsRecordFields(
content: content == _undefined || content == null
? _instance.content
: (content as String),
name: name == _undefined || name == null
? _instance.name
: (name as String),
priority:
priority == _undefined ? _instance.priority : (priority as int?),
recordType: recordType == _undefined || recordType == null
? _instance.recordType
: (recordType as String),
ttl: ttl == _undefined || ttl == null ? _instance.ttl : (ttl as int),
$__typename: $__typename == _undefined || $__typename == null
? _instance.$__typename
: ($__typename as String),
));
}
class _CopyWithStubImpl$Fragment$dnsRecordFields<TRes>
implements CopyWith$Fragment$dnsRecordFields<TRes> {
_CopyWithStubImpl$Fragment$dnsRecordFields(this._res);
TRes _res;
call({
String? content,
String? name,
int? priority,
String? recordType,
int? ttl,
String? $__typename,
}) =>
_res;
}
const fragmentDefinitiondnsRecordFields = FragmentDefinitionNode(
name: NameNode(value: 'dnsRecordFields'),
typeCondition: TypeConditionNode(
on: NamedTypeNode(
name: NameNode(value: 'DnsRecord'),
isNonNull: false,
)),
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'content'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
FieldNode(
name: NameNode(value: 'name'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
FieldNode(
name: NameNode(value: 'priority'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
FieldNode(
name: NameNode(value: 'recordType'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
FieldNode(
name: NameNode(value: 'ttl'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
FieldNode(
name: NameNode(value: '__typename'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
]),
);
const documentNodeFragmentdnsRecordFields = DocumentNode(definitions: [
fragmentDefinitiondnsRecordFields,
]);
extension ClientExtension$Fragment$dnsRecordFields on graphql.GraphQLClient {
void writeFragment$dnsRecordFields({
required Fragment$dnsRecordFields data,
required Map<String, dynamic> idFields,
bool broadcast = true,
}) =>
this.writeFragment(
graphql.FragmentRequest(
idFields: idFields,
fragment: const graphql.Fragment(
fragmentName: 'dnsRecordFields',
document: documentNodeFragmentdnsRecordFields,
),
),
data: data.toJson(),
broadcast: broadcast,
);
Fragment$dnsRecordFields? readFragment$dnsRecordFields({
required Map<String, dynamic> idFields,
bool optimistic = true,
}) {
final result = this.readFragment(
graphql.FragmentRequest(
idFields: idFields,
fragment: const graphql.Fragment(
fragmentName: 'dnsRecordFields',
document: documentNodeFragmentdnsRecordFields,
),
),
optimistic: optimistic,
);
return result == null ? null : Fragment$dnsRecordFields.fromJson(result);
}
}
const possibleTypesMap = <String, Set<String>>{ const possibleTypesMap = <String, Set<String>>{
'MutationReturnInterface': { 'MutationReturnInterface': {
'ApiKeyMutationReturn', 'ApiKeyMutationReturn',
'AutoUpgradeSettingsMutationReturn', 'AutoUpgradeSettingsMutationReturn',
'DeviceApiTokenMutationReturn', 'DeviceApiTokenMutationReturn',
'GenericJobButationReturn', 'GenericBackupConfigReturn',
'GenericJobMutationReturn',
'GenericMutationReturn', 'GenericMutationReturn',
'ServiceJobMutationReturn', 'ServiceJobMutationReturn',
'ServiceMutationReturn', 'ServiceMutationReturn',

View File

@ -4,6 +4,21 @@ fragment basicMutationReturnFields on MutationReturnInterface{
success success
} }
fragment basicApiJobsFields on ApiJob{
createdAt
description
error
finishedAt
name
progress
result
status
statusText
uid
typeId
updatedAt
}
query GetApiVersion { query GetApiVersion {
api { api {
version version
@ -13,17 +28,7 @@ query GetApiVersion {
query GetApiJobs { query GetApiJobs {
jobs { jobs {
getJobs { getJobs {
createdAt ...basicApiJobsFields
description
error
finishedAt
name
progress
result
status
statusText
uid
updatedAt
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,3 @@
fragment basicMutationReturnFields on MutationReturnInterface{
code
message
success
}
query SystemSettings { query SystemSettings {
system { system {
settings { settings {
@ -28,6 +22,14 @@ query SystemIsUsingBinds {
} }
} }
fragment fragmentDnsRecords on DnsRecord {
recordType
name
content
ttl
priority
}
query DomainInfo { query DomainInfo {
system { system {
domainInfo { domainInfo {
@ -35,7 +37,7 @@ query DomainInfo {
hostname hostname
provider provider
requiredDnsRecords { requiredDnsRecords {
...dnsRecordFields ...fragmentDnsRecords
} }
} }
} }

View File

@ -1,21 +1,17 @@
fragment basicMutationReturnFields on MutationReturnInterface{
code
message
success
}
query AllServices { query AllServices {
services { services {
allServices { allServices {
description description
displayName displayName
dnsRecords { dnsRecords {
...dnsRecordFields ...fragmentDnsRecords
} }
id id
isEnabled isEnabled
isMovable isMovable
isRequired isRequired
canBeBackedUp
backupDescription
status status
storageUsage { storageUsage {
title title
@ -64,17 +60,7 @@ mutation MoveService($input: MoveServiceInput!) {
moveService(input: $input) { moveService(input: $input) {
...basicMutationReturnFields ...basicMutationReturnFields
job { job {
createdAt ...basicApiJobsFields
description
error
finishedAt
name
progress
result
status
statusText
uid
updatedAt
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,3 @@
fragment basicMutationReturnFields on MutationReturnInterface{
code
message
success
}
fragment userFields on User{ fragment userFields on User{
username username
userType userType

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,248 @@
part of 'server_api.dart';
mixin BackupsApi on GraphQLApiMap {
Future<List<Backup>> getBackups() async {
List<Backup> backups;
QueryResult<Query$AllBackupSnapshots> response;
try {
final GraphQLClient client = await getClient();
response = await client.query$AllBackupSnapshots();
if (response.hasException) {
final message = response.exception.toString();
print(message);
backups = [];
}
final List<Backup> parsed = response.parsedData!.backup.allSnapshots
.map(
(
final Query$AllBackupSnapshots$backup$allSnapshots snapshot,
) =>
Backup.fromGraphQL(snapshot),
)
.toList();
backups = parsed;
} catch (e) {
print(e);
backups = [];
}
return backups;
}
Future<BackupConfiguration?> getBackupsConfiguration() async {
BackupConfiguration? backupConfiguration;
QueryResult<Query$BackupConfiguration> response;
try {
final GraphQLClient client = await getClient();
response = await client.query$BackupConfiguration();
if (response.hasException) {
final message = response.exception.toString();
print(message);
backupConfiguration = null;
}
final BackupConfiguration parsed = BackupConfiguration.fromGraphQL(
response.parsedData!.backup.configuration,
);
backupConfiguration = parsed;
} catch (e) {
print(e);
backupConfiguration = null;
}
return backupConfiguration;
}
Future<GenericResult> forceBackupListReload() async {
try {
final GraphQLClient client = await getClient();
await client.mutate$ForceSnapshotsReload();
} catch (e) {
print(e);
return GenericResult(
success: false,
data: null,
message: e.toString(),
);
}
return GenericResult(
success: true,
data: null,
);
}
Future<GenericResult<ServerJob?>> startBackup(final String serviceId) async {
QueryResult<Mutation$StartBackup> response;
GenericResult<ServerJob?>? result;
try {
final GraphQLClient client = await getClient();
final variables = Variables$Mutation$StartBackup(serviceId: serviceId);
final options = Options$Mutation$StartBackup(variables: variables);
response = await client.mutate$StartBackup(options);
if (response.hasException) {
final message = response.exception.toString();
print(message);
result = GenericResult(
success: false,
data: null,
message: message,
);
}
result = GenericResult(
success: true,
data: ServerJob.fromGraphQL(
response.parsedData!.backup.startBackup.job!,
),
);
} catch (e) {
print(e);
result = GenericResult(
success: false,
data: null,
message: e.toString(),
);
}
return result;
}
Future<GenericResult> setAutobackupPeriod({final int? period}) async {
QueryResult<Mutation$SetAutobackupPeriod> response;
GenericResult? result;
try {
final GraphQLClient client = await getClient();
final variables = Variables$Mutation$SetAutobackupPeriod(period: period);
final options =
Options$Mutation$SetAutobackupPeriod(variables: variables);
response = await client.mutate$SetAutobackupPeriod(options);
if (response.hasException) {
final message = response.exception.toString();
print(message);
result = GenericResult(
success: false,
data: null,
message: message,
);
}
result = GenericResult(
success: true,
data: null,
);
} catch (e) {
print(e);
result = GenericResult(
success: false,
data: null,
message: e.toString(),
);
}
return result;
}
Future<GenericResult> removeRepository() async {
try {
final GraphQLClient client = await getClient();
await client.mutate$RemoveRepository();
} catch (e) {
print(e);
return GenericResult(
success: false,
data: null,
message: e.toString(),
);
}
return GenericResult(
success: true,
data: null,
);
}
Future<GenericResult> initializeRepository(
final InitializeRepositoryInput input,
) async {
QueryResult<Mutation$InitializeRepository> response;
GenericResult? result;
try {
final GraphQLClient client = await getClient();
final variables = Variables$Mutation$InitializeRepository(
repository: Input$InitializeRepositoryInput(
locationId: input.locationId,
locationName: input.locationName,
login: input.login,
password: input.password,
provider: input.provider.toGraphQL(),
),
);
final options =
Options$Mutation$InitializeRepository(variables: variables);
response = await client.mutate$InitializeRepository(options);
if (response.hasException) {
final message = response.exception.toString();
print(message);
result = GenericResult(
success: false,
data: null,
message: message,
);
}
result = GenericResult(
success: true,
data: null,
);
} catch (e) {
print(e);
result = GenericResult(
success: false,
data: null,
message: e.toString(),
);
}
return result;
}
Future<GenericResult<ServerJob?>> restoreBackup(
final String snapshotId,
) async {
QueryResult<Mutation$RestoreBackup> response;
GenericResult<ServerJob?>? result;
try {
final GraphQLClient client = await getClient();
final variables =
Variables$Mutation$RestoreBackup(snapshotId: snapshotId);
final options = Options$Mutation$RestoreBackup(variables: variables);
response = await client.mutate$RestoreBackup(options);
if (response.hasException) {
final message = response.exception.toString();
print(message);
result = GenericResult(
success: false,
data: null,
message: message,
);
}
result = GenericResult(
success: true,
data: ServerJob.fromGraphQL(
response.parsedData!.backup.restoreBackup.job!,
),
);
} catch (e) {
print(e);
result = GenericResult(
success: false,
data: null,
message: e.toString(),
);
}
return result;
}
}

View File

@ -2,6 +2,7 @@ import 'package:graphql/client.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/graphql_api_map.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/graphql_api_map.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
@ -9,12 +10,12 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.g
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/users.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/users.graphql.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
import 'package:selfprivacy/logic/models/json/api_token.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart';
import 'package:selfprivacy/logic/models/json/backup.dart'; import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/json/device_token.dart'; import 'package:selfprivacy/logic/models/json/device_token.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
@ -31,9 +32,16 @@ part 'server_actions_api.dart';
part 'services_api.dart'; part 'services_api.dart';
part 'users_api.dart'; part 'users_api.dart';
part 'volume_api.dart'; part 'volume_api.dart';
part 'backups_api.dart';
class ServerApi extends GraphQLApiMap class ServerApi extends GraphQLApiMap
with VolumeApi, JobsApi, ServerActionsApi, ServicesApi, UsersApi { with
VolumeApi,
JobsApi,
ServerActionsApi,
ServicesApi,
UsersApi,
BackupsApi {
ServerApi({ ServerApi({
this.hasLogger = false, this.hasLogger = false,
this.isWithToken = true, this.isWithToken = true,
@ -288,8 +296,10 @@ class ServerApi extends GraphQLApiMap
} }
records = response.parsedData!.system.domainInfo.requiredDnsRecords records = response.parsedData!.system.domainInfo.requiredDnsRecords
.map<DnsRecord>( .map<DnsRecord>(
(final Fragment$dnsRecordFields fragment) => (
DnsRecord.fromGraphQL(fragment), final Fragment$fragmentDnsRecords record,
) =>
DnsRecord.fromGraphQL(record),
) )
.toList(); .toList();
} catch (e) { } catch (e) {
@ -509,22 +519,4 @@ class ServerApi extends GraphQLApiMap
return token; return token;
} }
/// TODO: backups're not implemented on server side
Future<BackupStatus> getBackupStatus() async => BackupStatus(
progress: 0.0,
status: BackupStatusEnum.error,
errorMessage: null,
);
Future<List<Backup>> getBackups() async => [];
Future<void> uploadBackblazeConfig(final BackblazeBucket bucket) async {}
Future<void> forceBackupListReload() async {}
Future<void> startBackup() async {}
Future<void> restoreBackup(final String backupId) async {}
} }

View File

@ -2,9 +2,9 @@ import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
export 'package:selfprivacy/logic/api_maps/generic_result.dart'; export 'package:selfprivacy/logic/api_maps/generic_result.dart';
@ -36,7 +36,7 @@ class BackblazeApi extends RestApiMap {
responseType: ResponseType.json, responseType: ResponseType.json,
); );
if (isWithToken) { if (isWithToken) {
final BackblazeCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ApiConfigModel>().backblazeCredential;
final String token = backblazeCredential!.applicationKey; final String token = backblazeCredential!.applicationKey;
options.headers = {'Authorization': 'Basic $token'}; options.headers = {'Authorization': 'Basic $token'};
@ -56,7 +56,7 @@ class BackblazeApi extends RestApiMap {
Future<BackblazeApiAuth> getAuthorizationToken() async { Future<BackblazeApiAuth> getAuthorizationToken() async {
final Dio client = await getClient(); final Dio client = await getClient();
final BackblazeCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ApiConfigModel>().backblazeCredential;
if (backblazeCredential == null) { if (backblazeCredential == null) {
throw Exception('Backblaze credential is null'); throw Exception('Backblaze credential is null');
@ -121,7 +121,7 @@ class BackblazeApi extends RestApiMap {
// Create bucket // Create bucket
Future<String> createBucket(final String bucketName) async { Future<String> createBucket(final String bucketName) async {
final BackblazeApiAuth auth = await getAuthorizationToken(); final BackblazeApiAuth auth = await getAuthorizationToken();
final BackblazeCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ApiConfigModel>().backblazeCredential;
final Dio client = await getClient(); final Dio client = await getClient();
client.options.baseUrl = auth.apiUrl; client.options.baseUrl = auth.apiUrl;

View File

@ -6,7 +6,10 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/json/backup.dart'; import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/initialize_repository_input.dart';
import 'package:selfprivacy/logic/models/service.dart';
part 'backups_state.dart'; part 'backups_state.dart';
@ -24,107 +27,85 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
Future<void> load() async { Future<void> load() async {
if (serverInstallationCubit.state is ServerInstallationFinished) { if (serverInstallationCubit.state is ServerInstallationFinished) {
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket; final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
if (bucket == null) { final BackupConfiguration? backupConfig =
emit( await api.getBackupsConfiguration();
const BackupsState( final List<Backup> backups = await api.getBackups();
isInitialized: false, backups.sort((final a, final b) => b.time.compareTo(a.time));
preventActions: false, emit(
refreshing: false, state.copyWith(
), backblazeBucket: bucket,
); isInitialized: backupConfig?.isInitialized,
} else { autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero,
final BackupStatus status = await api.getBackupStatus(); backups: backups,
switch (status.status) { preventActions: false,
case BackupStatusEnum.noKey: refreshing: false,
case BackupStatusEnum.notInitialized: ),
emit( );
BackupsState( print(state);
backups: const [],
isInitialized: true,
preventActions: false,
progress: 0,
status: status.status,
refreshing: false,
),
);
break;
case BackupStatusEnum.initializing:
emit(
BackupsState(
backups: const [],
isInitialized: true,
preventActions: false,
progress: 0,
status: status.status,
refreshTimer: const Duration(seconds: 10),
refreshing: false,
),
);
break;
case BackupStatusEnum.initialized:
case BackupStatusEnum.error:
final List<Backup> backups = await api.getBackups();
emit(
BackupsState(
backups: backups,
isInitialized: true,
preventActions: false,
progress: status.progress,
status: status.status,
error: status.errorMessage ?? '',
refreshing: false,
),
);
break;
case BackupStatusEnum.backingUp:
case BackupStatusEnum.restoring:
final List<Backup> backups = await api.getBackups();
emit(
BackupsState(
backups: backups,
isInitialized: true,
preventActions: true,
progress: status.progress,
status: status.status,
error: status.errorMessage ?? '',
refreshTimer: const Duration(seconds: 5),
refreshing: false,
),
);
break;
default:
emit(const BackupsState());
}
Timer(state.refreshTimer, () => updateBackups(useTimer: true));
}
} }
} }
Future<void> createBucket() async { Future<void> initializeBackups() async {
emit(state.copyWith(preventActions: true)); emit(state.copyWith(preventActions: true));
final String domain = serverInstallationCubit.state.serverDomain!.domainName final String? encryptionKey =
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); (await api.getBackupsConfiguration())?.encryptionKey;
final int serverId = serverInstallationCubit.state.serverDetails!.id; if (encryptionKey == null) {
String bucketName = 'selfprivacy-$domain-$serverId'; getIt<NavigationService>()
// If bucket name is too long, shorten it .showSnackBar("Couldn't get encryption key from your server.");
if (bucketName.length > 49) { emit(state.copyWith(preventActions: false));
bucketName = bucketName.substring(0, 49); return;
} }
final String bucketId = await backblaze.createBucket(bucketName);
final BackblazeApplicationKey key = await backblaze.createKey(bucketId); final BackblazeBucket bucket;
final BackblazeBucket bucket = BackblazeBucket(
bucketId: bucketId, if (state.backblazeBucket == null) {
bucketName: bucketName, final String domain = serverInstallationCubit
applicationKey: key.applicationKey, .state.serverDomain!.domainName
applicationKeyId: key.applicationKeyId, .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
final int serverId = serverInstallationCubit.state.serverDetails!.id;
String bucketName = 'selfprivacy-$domain-$serverId';
// If bucket name is too long, shorten it
if (bucketName.length > 49) {
bucketName = bucketName.substring(0, 49);
}
final String bucketId = await backblaze.createBucket(bucketName);
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
bucket = BackblazeBucket(
bucketId: bucketId,
bucketName: bucketName,
applicationKey: key.applicationKey,
applicationKeyId: key.applicationKeyId,
encryptionKey: encryptionKey,
);
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket);
emit(state.copyWith(backblazeBucket: bucket));
} else {
bucket = state.backblazeBucket!;
}
final GenericResult result = await api.initializeRepository(
InitializeRepositoryInput(
provider: BackupsProviderType.backblaze,
locationId: bucket.bucketId,
locationName: bucket.bucketName,
login: bucket.applicationKeyId,
password: bucket.applicationKey,
),
);
if (result.success == false) {
getIt<NavigationService>()
.showSnackBar(result.message ?? 'Unknown error');
emit(state.copyWith(preventActions: false));
return;
}
await updateBackups();
getIt<NavigationService>().showSnackBar(
'Backups repository is now initializing. It may take a while.',
); );
await getIt<ApiConfigModel>().storeBackblazeBucket(bucket); emit(state.copyWith(preventActions: false));
await api.uploadBackblazeConfig(bucket);
await updateBackups();
emit(state.copyWith(isInitialized: true, preventActions: false));
} }
Future<void> reuploadKey() async { Future<void> reuploadKey() async {
@ -132,37 +113,48 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket; final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
if (bucket == null) { if (bucket == null) {
emit(state.copyWith(isInitialized: false)); emit(state.copyWith(isInitialized: false));
print('bucket is null');
} else { } else {
await api.uploadBackblazeConfig(bucket); print('bucket is not null');
emit(state.copyWith(isInitialized: true, preventActions: false)); final GenericResult result = await api.initializeRepository(
getIt<NavigationService>().showSnackBar('backup.reuploaded_key'); InitializeRepositoryInput(
provider: BackupsProviderType.backblaze,
locationId: bucket.bucketId,
locationName: bucket.bucketName,
login: bucket.applicationKeyId,
password: bucket.applicationKey,
),
);
print('result is $result');
if (result.success == false) {
getIt<NavigationService>()
.showSnackBar(result.message ?? 'Unknown error');
emit(state.copyWith(preventActions: false));
return;
} else {
emit(state.copyWith(preventActions: false));
getIt<NavigationService>().showSnackBar('backup.reuploaded_key');
await updateBackups();
}
} }
} }
Duration refreshTimeFromState(final BackupStatusEnum status) { @Deprecated("we don't have states")
switch (status) { Duration refreshTimeFromState() => const Duration(seconds: 60);
case BackupStatusEnum.backingUp:
case BackupStatusEnum.restoring:
return const Duration(seconds: 5);
case BackupStatusEnum.initializing:
return const Duration(seconds: 10);
default:
return const Duration(seconds: 60);
}
}
Future<void> updateBackups({final bool useTimer = false}) async { Future<void> updateBackups({final bool useTimer = false}) async {
emit(state.copyWith(refreshing: true)); emit(state.copyWith(refreshing: true));
final List<Backup> backups = await api.getBackups(); final backups = await api.getBackups();
final BackupStatus status = await api.getBackupStatus(); backups.sort((final a, final b) => b.time.compareTo(a.time));
final backupConfig = await api.getBackupsConfiguration();
emit( emit(
state.copyWith( state.copyWith(
backups: backups, backups: backups,
progress: status.progress, refreshTimer: refreshTimeFromState(),
status: status.status,
error: status.errorMessage,
refreshTimer: refreshTimeFromState(status.status),
refreshing: false, refreshing: false,
isInitialized: backupConfig?.isInitialized ?? false,
autobackupPeriod: backupConfig?.autobackupPeriod,
), ),
); );
if (useTimer) { if (useTimer) {
@ -172,14 +164,23 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
Future<void> forceUpdateBackups() async { Future<void> forceUpdateBackups() async {
emit(state.copyWith(preventActions: true)); emit(state.copyWith(preventActions: true));
await api.forceBackupListReload();
getIt<NavigationService>().showSnackBar('backup.refetching_list'.tr()); getIt<NavigationService>().showSnackBar('backup.refetching_list'.tr());
await api.forceBackupListReload();
emit(state.copyWith(preventActions: false)); emit(state.copyWith(preventActions: false));
} }
Future<void> createBackup() async { Future<void> createMultipleBackups(final List<Service> services) async {
emit(state.copyWith(preventActions: true)); emit(state.copyWith(preventActions: true));
await api.startBackup(); for (final service in services) {
await api.startBackup(service.id);
}
await updateBackups();
emit(state.copyWith(preventActions: false));
}
Future<void> createBackup(final String serviceId) async {
emit(state.copyWith(preventActions: true));
await api.startBackup(serviceId);
await updateBackups(); await updateBackups();
emit(state.copyWith(preventActions: false)); emit(state.copyWith(preventActions: false));
} }
@ -190,6 +191,26 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
emit(state.copyWith(preventActions: false)); emit(state.copyWith(preventActions: false));
} }
Future<void> setAutobackupPeriod(final Duration? period) async {
emit(state.copyWith(preventActions: true));
final result = await api.setAutobackupPeriod(period: period?.inMinutes);
if (result.success == false) {
getIt<NavigationService>()
.showSnackBar(result.message ?? 'Unknown error');
emit(state.copyWith(preventActions: false));
} else {
getIt<NavigationService>()
.showSnackBar('backup.autobackup_period_set'.tr());
emit(
state.copyWith(
preventActions: false,
autobackupPeriod: period ?? Duration.zero,
),
);
}
await updateBackups();
}
@override @override
void clear() async { void clear() async {
emit(const BackupsState()); emit(const BackupsState());

View File

@ -4,53 +4,54 @@ class BackupsState extends ServerInstallationDependendState {
const BackupsState({ const BackupsState({
this.isInitialized = false, this.isInitialized = false,
this.backups = const [], this.backups = const [],
this.progress = 0.0,
this.status = BackupStatusEnum.noKey,
this.preventActions = true, this.preventActions = true,
this.error = '',
this.refreshTimer = const Duration(seconds: 60), this.refreshTimer = const Duration(seconds: 60),
this.refreshing = true, this.refreshing = true,
this.autobackupPeriod,
this.backblazeBucket,
}); });
final bool isInitialized; final bool isInitialized;
final List<Backup> backups; final List<Backup> backups;
final double progress;
final BackupStatusEnum status;
final bool preventActions; final bool preventActions;
final String error;
final Duration refreshTimer; final Duration refreshTimer;
final bool refreshing; final bool refreshing;
final Duration? autobackupPeriod;
final BackblazeBucket? backblazeBucket;
List<Backup> serviceBackups(final String serviceId) => backups
.where((final backup) => backup.serviceId == serviceId)
.toList(growable: false);
@override @override
List<Object> get props => [ List<Object> get props => [
isInitialized, isInitialized,
backups, backups,
progress,
preventActions, preventActions,
status,
error,
refreshTimer, refreshTimer,
refreshing refreshing,
]; ];
BackupsState copyWith({ BackupsState copyWith({
final bool? isInitialized, final bool? isInitialized,
final List<Backup>? backups, final List<Backup>? backups,
final double? progress,
final BackupStatusEnum? status,
final bool? preventActions, final bool? preventActions,
final String? error,
final Duration? refreshTimer, final Duration? refreshTimer,
final bool? refreshing, final bool? refreshing,
final Duration? autobackupPeriod,
final BackblazeBucket? backblazeBucket,
}) => }) =>
BackupsState( BackupsState(
isInitialized: isInitialized ?? this.isInitialized, isInitialized: isInitialized ?? this.isInitialized,
backups: backups ?? this.backups, backups: backups ?? this.backups,
progress: progress ?? this.progress,
status: status ?? this.status,
preventActions: preventActions ?? this.preventActions, preventActions: preventActions ?? this.preventActions,
error: error ?? this.error,
refreshTimer: refreshTimer ?? this.refreshTimer, refreshTimer: refreshTimer ?? this.refreshTimer,
refreshing: refreshing ?? this.refreshing, refreshing: refreshing ?? this.refreshing,
// The autobackupPeriod might be null, so if the duration is set to 0, we
// set it to null.
autobackupPeriod: autobackupPeriod?.inSeconds == 0
? null
: autobackupPeriod ?? this.autobackupPeriod,
backblazeBucket: backblazeBucket ?? this.backblazeBucket,
); );
} }

View File

@ -3,7 +3,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
class BackblazeFormCubit extends FormCubit { class BackblazeFormCubit extends FormCubit {

View File

@ -5,20 +5,19 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
import 'package:selfprivacy/logic/models/launch_installation_data.dart';
import 'package:selfprivacy/logic/providers/provider_settings.dart';
import 'package:selfprivacy/logic/providers/providers_controller.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/launch_installation_data.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart';
import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart';
import 'package:selfprivacy/logic/models/server_type.dart'; import 'package:selfprivacy/logic/models/server_type.dart';
import 'package:selfprivacy/logic/providers/provider_settings.dart';
import 'package:selfprivacy/logic/providers/providers_controller.dart';
import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/ui/helpers/modals.dart';
export 'package:provider/provider.dart'; export 'package:provider/provider.dart';
@ -195,9 +194,10 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
} }
void setBackblazeKey(final String keyId, final String applicationKey) async { void setBackblazeKey(final String keyId, final String applicationKey) async {
final BackblazeCredential backblazeCredential = BackblazeCredential( final BackupsCredential backblazeCredential = BackupsCredential(
keyId: keyId, keyId: keyId,
applicationKey: applicationKey, applicationKey: applicationKey,
provider: BackupsProviderType.backblaze,
); );
await repository.saveBackblazeKey(backblazeCredential); await repository.saveBackblazeKey(backblazeCredential);
if (state is ServerInstallationRecovery) { if (state is ServerInstallationRecovery) {
@ -699,7 +699,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
provider: dnsProviderType, provider: dnsProviderType,
), ),
); );
await repository.setDnsApiToken(token); // await repository.setDnsApiToken(token);
emit( emit(
dataState.copyWith( dataState.copyWith(
serverDomain: ServerDomain( serverDomain: ServerDomain(
@ -714,7 +714,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
} }
void finishRecoveryProcess( void finishRecoveryProcess(
final BackblazeCredential backblazeCredential, final BackupsCredential backblazeCredential,
) async { ) async {
await repository.saveIsServerStarted(true); await repository.saveIsServerStarted(true);
await repository.saveIsServerResetedFirstTime(true); await repository.saveIsServerResetedFirstTime(true);

View File

@ -13,7 +13,7 @@ import 'package:selfprivacy/logic/providers/provider_settings.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
@ -46,7 +46,7 @@ class ServerInstallationRepository {
final DnsProviderType? dnsProvider = getIt<ApiConfigModel>().dnsProvider; final DnsProviderType? dnsProvider = getIt<ApiConfigModel>().dnsProvider;
final ServerProviderType? serverProvider = final ServerProviderType? serverProvider =
getIt<ApiConfigModel>().serverProvider; getIt<ApiConfigModel>().serverProvider;
final BackblazeCredential? backblazeCredential = final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential; getIt<ApiConfigModel>().backblazeCredential;
final ServerHostingDetails? serverDetails = final ServerHostingDetails? serverDetails =
getIt<ApiConfigModel>().serverDetails; getIt<ApiConfigModel>().serverDetails;
@ -170,12 +170,14 @@ class ServerInstallationRepository {
Future<String?> getDomainId(final String token, final String domain) async { Future<String?> getDomainId(final String token, final String domain) async {
final result = final result =
await ProvidersController.currentDnsProvider!.tryInitApiByToken(token); await ProvidersController.currentDnsProvider!.tryInitApiByToken(token);
return result.success if (!result.success) {
? (await ProvidersController.currentDnsProvider!.getZoneId( return null;
domain, }
)) await setDnsApiToken(token);
.data return (await ProvidersController.currentDnsProvider!.getZoneId(
: null; domain,
))
.data;
} }
Future<Map<String, bool>> isDnsAddressesMatch( Future<Map<String, bool>> isDnsAddressesMatch(
@ -519,7 +521,7 @@ class ServerInstallationRepository {
} }
Future<void> saveBackblazeKey( Future<void> saveBackblazeKey(
final BackblazeCredential backblazeCredential, final BackupsCredential backblazeCredential,
) async { ) async {
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential); await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
} }

View File

@ -32,7 +32,7 @@ abstract class ServerInstallationState extends Equatable {
final String? providerApiToken; final String? providerApiToken;
final String? dnsApiToken; final String? dnsApiToken;
final String? serverTypeIdentificator; final String? serverTypeIdentificator;
final BackblazeCredential? backblazeCredential; final BackupsCredential? backblazeCredential;
final ServerDomain? serverDomain; final ServerDomain? serverDomain;
final User? rootUser; final User? rootUser;
final ServerHostingDetails? serverDetails; final ServerHostingDetails? serverDetails;
@ -167,7 +167,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
final String? providerApiToken, final String? providerApiToken,
final String? serverTypeIdentificator, final String? serverTypeIdentificator,
final String? dnsApiToken, final String? dnsApiToken,
final BackblazeCredential? backblazeCredential, final BackupsCredential? backblazeCredential,
final ServerDomain? serverDomain, final ServerDomain? serverDomain,
final User? rootUser, final User? rootUser,
final ServerHostingDetails? serverDetails, final ServerHostingDetails? serverDetails,
@ -237,7 +237,7 @@ class ServerInstallationFinished extends ServerInstallationState {
required String super.providerApiToken, required String super.providerApiToken,
required String super.serverTypeIdentificator, required String super.serverTypeIdentificator,
required String super.dnsApiToken, required String super.dnsApiToken,
required BackblazeCredential super.backblazeCredential, required BackupsCredential super.backblazeCredential,
required ServerDomain super.serverDomain, required ServerDomain super.serverDomain,
required User super.rootUser, required User super.rootUser,
required ServerHostingDetails super.serverDetails, required ServerHostingDetails super.serverDetails,
@ -324,7 +324,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
final String? providerApiToken, final String? providerApiToken,
final String? serverTypeIdentificator, final String? serverTypeIdentificator,
final String? dnsApiToken, final String? dnsApiToken,
final BackblazeCredential? backblazeCredential, final BackupsCredential? backblazeCredential,
final ServerDomain? serverDomain, final ServerDomain? serverDomain,
final User? rootUser, final User? rootUser,
final ServerHostingDetails? serverDetails, final ServerHostingDetails? serverDetails,

View File

@ -21,6 +21,14 @@ class ServerJobsState extends ServerInstallationDependendState {
} }
} }
List<ServerJob> get backupJobList => serverJobList
.where(
// The backup jobs has the format of 'service.<service_id>.backup'
(final job) =>
job.typeId.contains('backup') || job.typeId.contains('restore'),
)
.toList();
bool get hasRemovableJobs => serverJobList.any( bool get hasRemovableJobs => serverJobList.any(
(final job) => (final job) =>
job.status == JobStatusEnum.finished || job.status == JobStatusEnum.finished ||

View File

@ -12,40 +12,15 @@ class ServicesState extends ServerInstallationDependendState {
final List<Service> services; final List<Service> services;
final List<String> lockedServices; final List<String> lockedServices;
List<Service> get servicesThatCanBeBackedUp => services
.where(
(final service) => service.canBeBackedUp,
)
.toList();
bool isServiceLocked(final String serviceId) => bool isServiceLocked(final String serviceId) =>
lockedServices.contains(serviceId); lockedServices.contains(serviceId);
bool get isPasswordManagerEnable => services
.firstWhere(
(final service) => service.id == 'bitwarden',
orElse: () => Service.empty,
)
.isEnabled;
bool get isCloudEnable => services
.firstWhere(
(final service) => service.id == 'nextcloud',
orElse: () => Service.empty,
)
.isEnabled;
bool get isGitEnable => services
.firstWhere(
(final service) => service.id == 'gitea',
orElse: () => Service.empty,
)
.isEnabled;
bool get isSocialNetworkEnable => services
.firstWhere(
(final service) => service.id == 'pleroma',
orElse: () => Service.empty,
)
.isEnabled;
bool get isVpnEnable => services
.firstWhere(
(final service) => service.id == 'ocserv',
orElse: () => Service.empty,
)
.isEnabled;
Service? getServiceById(final String id) { Service? getServiceById(final String id) {
final service = services.firstWhere( final service = services.firstWhere(
(final service) => service.id == id, (final service) => service.id == id,
@ -63,23 +38,6 @@ class ServicesState extends ServerInstallationDependendState {
lockedServices, lockedServices,
]; ];
bool isEnableByType(final Service service) {
switch (service.id) {
case 'bitwarden':
return isPasswordManagerEnable;
case 'nextcloud':
return isCloudEnable;
case 'pleroma':
return isSocialNetworkEnable;
case 'gitea':
return isGitEnable;
case 'ocserv':
return isVpnEnable;
default:
throw Exception('wrong state');
}
}
ServicesState copyWith({ ServicesState copyWith({
final List<Service>? services, final List<Service>? services,
final List<String>? lockedServices, final List<String>? lockedServices,

View File

@ -1,7 +1,7 @@
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
@ -15,7 +15,8 @@ class ApiConfigModel {
String? get dnsProviderKey => _dnsProviderKey; String? get dnsProviderKey => _dnsProviderKey;
ServerProviderType? get serverProvider => _serverProvider; ServerProviderType? get serverProvider => _serverProvider;
DnsProviderType? get dnsProvider => _dnsProvider; DnsProviderType? get dnsProvider => _dnsProvider;
BackblazeCredential? get backblazeCredential => _backblazeCredential;
BackupsCredential? get backblazeCredential => _backblazeCredential;
ServerDomain? get serverDomain => _serverDomain; ServerDomain? get serverDomain => _serverDomain;
BackblazeBucket? get backblazeBucket => _backblazeBucket; BackblazeBucket? get backblazeBucket => _backblazeBucket;
@ -26,7 +27,7 @@ class ApiConfigModel {
ServerProviderType? _serverProvider; ServerProviderType? _serverProvider;
DnsProviderType? _dnsProvider; DnsProviderType? _dnsProvider;
ServerHostingDetails? _serverDetails; ServerHostingDetails? _serverDetails;
BackblazeCredential? _backblazeCredential; BackupsCredential? _backblazeCredential;
ServerDomain? _serverDomain; ServerDomain? _serverDomain;
BackblazeBucket? _backblazeBucket; BackblazeBucket? _backblazeBucket;
@ -60,7 +61,7 @@ class ApiConfigModel {
_serverLocation = serverLocation; _serverLocation = serverLocation;
} }
Future<void> storeBackblazeCredential(final BackblazeCredential value) async { Future<void> storeBackblazeCredential(final BackupsCredential value) async {
await _box.put(BNames.backblazeCredential, value); await _box.put(BNames.backblazeCredential, value);
_backblazeCredential = value; _backblazeCredential = value;
} }

View File

@ -0,0 +1,60 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
class Backup {
Backup.fromGraphQL(
final Query$AllBackupSnapshots$backup$allSnapshots snapshot,
) : this(
id: snapshot.id,
time: snapshot.createdAt,
serviceId: snapshot.service.id,
fallbackServiceName: snapshot.service.displayName,
);
Backup({
required this.time,
required this.id,
required this.serviceId,
required this.fallbackServiceName,
});
// Time of the backup
final DateTime time;
@JsonKey(name: 'short_id')
final String id;
final String serviceId;
final String fallbackServiceName;
}
class BackupConfiguration {
BackupConfiguration.fromGraphQL(
final Query$BackupConfiguration$backup$configuration configuration,
) : this(
// Provided by API as int of minutes
autobackupPeriod: configuration.autobackupPeriod != null
? Duration(minutes: configuration.autobackupPeriod!)
: null,
encryptionKey: configuration.encryptionKey,
isInitialized: configuration.isInitialized,
locationId: configuration.locationId,
locationName: configuration.locationName,
provider: BackupsProviderType.fromGraphQL(configuration.provider),
);
BackupConfiguration({
required this.autobackupPeriod,
required this.encryptionKey,
required this.isInitialized,
required this.locationId,
required this.locationName,
required this.provider,
});
final Duration? autobackupPeriod;
final String encryptionKey;
final bool isInitialized;
final String? locationId;
final String? locationName;
final BackupsProviderType provider;
}

View File

@ -12,3 +12,4 @@
100. DnsProvider 100. DnsProvider
101. ServerProvider 101. ServerProvider
102. UserType 102. UserType
103. BackupsProvider

View File

@ -9,6 +9,7 @@ class BackblazeBucket {
required this.bucketName, required this.bucketName,
required this.applicationKeyId, required this.applicationKeyId,
required this.applicationKey, required this.applicationKey,
required this.encryptionKey,
}); });
@HiveField(0) @HiveField(0)
@ -23,6 +24,9 @@ class BackblazeBucket {
@HiveField(3) @HiveField(3)
final String bucketName; final String bucketName;
@HiveField(4)
final String encryptionKey;
@override @override
String toString() => bucketName; String toString() => bucketName;
} }

View File

@ -21,13 +21,14 @@ class BackblazeBucketAdapter extends TypeAdapter<BackblazeBucket> {
bucketName: fields[3] as String, bucketName: fields[3] as String,
applicationKeyId: fields[1] as String, applicationKeyId: fields[1] as String,
applicationKey: fields[2] as String, applicationKey: fields[2] as String,
encryptionKey: fields[4] as String,
); );
} }
@override @override
void write(BinaryWriter writer, BackblazeBucket obj) { void write(BinaryWriter writer, BackblazeBucket obj) {
writer writer
..writeByte(4) ..writeByte(5)
..writeByte(0) ..writeByte(0)
..write(obj.bucketId) ..write(obj.bucketId)
..writeByte(1) ..writeByte(1)
@ -35,7 +36,9 @@ class BackblazeBucketAdapter extends TypeAdapter<BackblazeBucket> {
..writeByte(2) ..writeByte(2)
..write(obj.applicationKey) ..write(obj.applicationKey)
..writeByte(3) ..writeByte(3)
..write(obj.bucketName); ..write(obj.bucketName)
..writeByte(4)
..write(obj.encryptionKey);
} }
@override @override

View File

@ -1,27 +0,0 @@
import 'dart:convert';
import 'package:hive/hive.dart';
part 'backblaze_credential.g.dart';
@HiveType(typeId: 4)
class BackblazeCredential {
BackblazeCredential({required this.keyId, required this.applicationKey});
@HiveField(0)
final String keyId;
@HiveField(1)
final String applicationKey;
String get encodedApiKey => encodedBackblazeKey(keyId, applicationKey);
@override
String toString() => '$keyId: $encodedApiKey';
}
String encodedBackblazeKey(final String? keyId, final String? applicationKey) {
final String apiKey = '$keyId:$applicationKey';
final String encodedApiKey = base64.encode(utf8.encode(apiKey));
return encodedApiKey;
}

View File

@ -1,44 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backblaze_credential.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class BackblazeCredentialAdapter extends TypeAdapter<BackblazeCredential> {
@override
final int typeId = 4;
@override
BackblazeCredential read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return BackblazeCredential(
keyId: fields[0] as String,
applicationKey: fields[1] as String,
);
}
@override
void write(BinaryWriter writer, BackblazeCredential obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.keyId)
..writeByte(1)
..write(obj.applicationKey);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BackblazeCredentialAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@ -0,0 +1,63 @@
import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
part 'backups_credential.g.dart';
@HiveType(typeId: 4)
class BackupsCredential {
BackupsCredential({
required this.keyId,
required this.applicationKey,
required this.provider,
});
@HiveField(0)
final String keyId;
@HiveField(1)
final String applicationKey;
@HiveField(2, defaultValue: BackupsProviderType.backblaze)
final BackupsProviderType provider;
String get encodedApiKey => encodedBackblazeKey(keyId, applicationKey);
@override
String toString() => '$keyId: $encodedApiKey';
}
String encodedBackblazeKey(final String? keyId, final String? applicationKey) {
final String apiKey = '$keyId:$applicationKey';
final String encodedApiKey = base64.encode(utf8.encode(apiKey));
return encodedApiKey;
}
@HiveType(typeId: 103)
enum BackupsProviderType {
@HiveField(0)
none,
@HiveField(1)
memory,
@HiveField(2)
file,
@HiveField(3)
backblaze;
factory BackupsProviderType.fromGraphQL(final Enum$BackupProvider provider) =>
switch (provider) {
Enum$BackupProvider.NONE => none,
Enum$BackupProvider.MEMORY => memory,
Enum$BackupProvider.FILE => file,
Enum$BackupProvider.BACKBLAZE => backblaze,
Enum$BackupProvider.$unknown => none
};
Enum$BackupProvider toGraphQL() => switch (this) {
none => Enum$BackupProvider.NONE,
memory => Enum$BackupProvider.MEMORY,
file => Enum$BackupProvider.FILE,
backblaze => Enum$BackupProvider.BACKBLAZE,
};
}

View File

@ -0,0 +1,98 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backups_credential.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class BackupsCredentialAdapter extends TypeAdapter<BackupsCredential> {
@override
final int typeId = 4;
@override
BackupsCredential read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return BackupsCredential(
keyId: fields[0] as String,
applicationKey: fields[1] as String,
provider: fields[2] == null
? BackupsProviderType.backblaze
: fields[2] as BackupsProviderType,
);
}
@override
void write(BinaryWriter writer, BackupsCredential obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.keyId)
..writeByte(1)
..write(obj.applicationKey)
..writeByte(2)
..write(obj.provider);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BackupsCredentialAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class BackupsProviderTypeAdapter extends TypeAdapter<BackupsProviderType> {
@override
final int typeId = 103;
@override
BackupsProviderType read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return BackupsProviderType.none;
case 1:
return BackupsProviderType.memory;
case 2:
return BackupsProviderType.file;
case 3:
return BackupsProviderType.backblaze;
default:
return BackupsProviderType.none;
}
}
@override
void write(BinaryWriter writer, BackupsProviderType obj) {
switch (obj) {
case BackupsProviderType.none:
writer.writeByte(0);
break;
case BackupsProviderType.memory:
writer.writeByte(1);
break;
case BackupsProviderType.file:
writer.writeByte(2);
break;
case BackupsProviderType.backblaze:
writer.writeByte(3);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BackupsProviderTypeAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@ -0,0 +1,16 @@
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
class InitializeRepositoryInput {
InitializeRepositoryInput({
required this.provider,
required this.locationId,
required this.locationName,
required this.login,
required this.password,
});
final BackupsProviderType provider;
final String locationId;
final String locationName;
final String login;
final String password;
}

View File

@ -1,48 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'backup.g.dart';
@JsonSerializable()
class Backup {
factory Backup.fromJson(final Map<String, dynamic> json) =>
_$BackupFromJson(json);
Backup({required this.time, required this.id});
// Time of the backup
final DateTime time;
@JsonKey(name: 'short_id')
final String id;
}
enum BackupStatusEnum {
@JsonValue('NO_KEY')
noKey,
@JsonValue('NOT_INITIALIZED')
notInitialized,
@JsonValue('INITIALIZED')
initialized,
@JsonValue('BACKING_UP')
backingUp,
@JsonValue('RESTORING')
restoring,
@JsonValue('ERROR')
error,
@JsonValue('INITIALIZING')
initializing,
}
@JsonSerializable()
class BackupStatus {
factory BackupStatus.fromJson(final Map<String, dynamic> json) =>
_$BackupStatusFromJson(json);
BackupStatus({
required this.status,
required this.progress,
required this.errorMessage,
});
final BackupStatusEnum status;
final double progress;
@JsonKey(name: 'error_message')
final String? errorMessage;
}

View File

@ -1,40 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backup.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Backup _$BackupFromJson(Map<String, dynamic> json) => Backup(
time: DateTime.parse(json['time'] as String),
id: json['short_id'] as String,
);
Map<String, dynamic> _$BackupToJson(Backup instance) => <String, dynamic>{
'time': instance.time.toIso8601String(),
'short_id': instance.id,
};
BackupStatus _$BackupStatusFromJson(Map<String, dynamic> json) => BackupStatus(
status: $enumDecode(_$BackupStatusEnumEnumMap, json['status']),
progress: (json['progress'] as num).toDouble(),
errorMessage: json['error_message'] as String?,
);
Map<String, dynamic> _$BackupStatusToJson(BackupStatus instance) =>
<String, dynamic>{
'status': _$BackupStatusEnumEnumMap[instance.status]!,
'progress': instance.progress,
'error_message': instance.errorMessage,
};
const _$BackupStatusEnumEnumMap = {
BackupStatusEnum.noKey: 'NO_KEY',
BackupStatusEnum.notInitialized: 'NOT_INITIALIZED',
BackupStatusEnum.initialized: 'INITIALIZED',
BackupStatusEnum.backingUp: 'BACKING_UP',
BackupStatusEnum.restoring: 'RESTORING',
BackupStatusEnum.error: 'ERROR',
BackupStatusEnum.initializing: 'INITIALIZING',
};

View File

@ -1,5 +1,5 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
part 'dns_records.g.dart'; part 'dns_records.g.dart';
@ -16,7 +16,7 @@ class DnsRecord {
}); });
DnsRecord.fromGraphQL( DnsRecord.fromGraphQL(
final Fragment$dnsRecordFields record, final Fragment$fragmentDnsRecords record,
) : this( ) : this(
type: record.recordType, type: record.recordType,
name: record.name, name: record.name,

View File

@ -12,6 +12,7 @@ class ServerJob {
required this.description, required this.description,
required this.status, required this.status,
required this.uid, required this.uid,
required this.typeId,
required this.updatedAt, required this.updatedAt,
required this.createdAt, required this.createdAt,
this.error, this.error,
@ -21,7 +22,7 @@ class ServerJob {
this.finishedAt, this.finishedAt,
}); });
ServerJob.fromGraphQL(final Query$GetApiJobs$jobs$getJobs serverJob) ServerJob.fromGraphQL(final Fragment$basicApiJobsFields serverJob)
: this( : this(
createdAt: serverJob.createdAt, createdAt: serverJob.createdAt,
description: serverJob.description, description: serverJob.description,
@ -33,12 +34,14 @@ class ServerJob {
status: JobStatusEnum.fromString(serverJob.status), status: JobStatusEnum.fromString(serverJob.status),
statusText: serverJob.statusText, statusText: serverJob.statusText,
uid: serverJob.uid, uid: serverJob.uid,
typeId: serverJob.typeId,
updatedAt: serverJob.updatedAt, updatedAt: serverJob.updatedAt,
); );
final String name; final String name;
final String description; final String description;
final JobStatusEnum status; final JobStatusEnum status;
final String uid; final String uid;
final String typeId;
final DateTime updatedAt; final DateTime updatedAt;
final DateTime createdAt; final DateTime createdAt;

View File

@ -11,6 +11,7 @@ ServerJob _$ServerJobFromJson(Map<String, dynamic> json) => ServerJob(
description: json['description'] as String, description: json['description'] as String,
status: $enumDecode(_$JobStatusEnumEnumMap, json['status']), status: $enumDecode(_$JobStatusEnumEnumMap, json['status']),
uid: json['uid'] as String, uid: json['uid'] as String,
typeId: json['typeId'] as String,
updatedAt: DateTime.parse(json['updatedAt'] as String), updatedAt: DateTime.parse(json['updatedAt'] as String),
createdAt: DateTime.parse(json['createdAt'] as String), createdAt: DateTime.parse(json['createdAt'] as String),
error: json['error'] as String?, error: json['error'] as String?,
@ -27,6 +28,7 @@ Map<String, dynamic> _$ServerJobToJson(ServerJob instance) => <String, dynamic>{
'description': instance.description, 'description': instance.description,
'status': _$JobStatusEnumEnumMap[instance.status]!, 'status': _$JobStatusEnumEnumMap[instance.status]!,
'uid': instance.uid, 'uid': instance.uid,
'typeId': instance.typeId,
'updatedAt': instance.updatedAt.toIso8601String(), 'updatedAt': instance.updatedAt.toIso8601String(),
'createdAt': instance.createdAt.toIso8601String(), 'createdAt': instance.createdAt.toIso8601String(),
'error': instance.error, 'error': instance.error,

View File

@ -6,6 +6,8 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.
import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_size.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
class Service { class Service {
Service.fromGraphQL(final Query$AllServices$services$allServices service) Service.fromGraphQL(final Query$AllServices$services$allServices service)
: this( : this(
@ -15,6 +17,8 @@ class Service {
isEnabled: service.isEnabled, isEnabled: service.isEnabled,
isRequired: service.isRequired, isRequired: service.isRequired,
isMovable: service.isMovable, isMovable: service.isMovable,
canBeBackedUp: service.canBeBackedUp,
backupDescription: service.backupDescription,
status: ServiceStatus.fromGraphQL(service.status), status: ServiceStatus.fromGraphQL(service.status),
storageUsage: ServiceStorageUsage( storageUsage: ServiceStorageUsage(
used: DiskSize(byte: int.parse(service.storageUsage.usedSpace)), used: DiskSize(byte: int.parse(service.storageUsage.usedSpace)),
@ -24,7 +28,9 @@ class Service {
svgIcon: utf8.decode(base64.decode(service.svgIcon)), svgIcon: utf8.decode(base64.decode(service.svgIcon)),
dnsRecords: service.dnsRecords dnsRecords: service.dnsRecords
?.map( ?.map(
(final Fragment$dnsRecordFields record) => (
final Fragment$fragmentDnsRecords record,
) =>
DnsRecord.fromGraphQL(record), DnsRecord.fromGraphQL(record),
) )
.toList() ?? .toList() ??
@ -38,6 +44,8 @@ class Service {
required this.isEnabled, required this.isEnabled,
required this.isRequired, required this.isRequired,
required this.isMovable, required this.isMovable,
required this.canBeBackedUp,
required this.backupDescription,
required this.status, required this.status,
required this.storageUsage, required this.storageUsage,
required this.svgIcon, required this.svgIcon,
@ -71,6 +79,8 @@ class Service {
isEnabled: false, isEnabled: false,
isRequired: false, isRequired: false,
isMovable: false, isMovable: false,
canBeBackedUp: false,
backupDescription: '',
status: ServiceStatus.off, status: ServiceStatus.off,
storageUsage: ServiceStorageUsage( storageUsage: ServiceStorageUsage(
used: const DiskSize(byte: 0), used: const DiskSize(byte: 0),
@ -87,6 +97,8 @@ class Service {
final bool isEnabled; final bool isEnabled;
final bool isRequired; final bool isRequired;
final bool isMovable; final bool isMovable;
final bool canBeBackedUp;
final String backupDescription;
final ServiceStatus status; final ServiceStatus status;
final ServiceStorageUsage storageUsage; final ServiceStorageUsage storageUsage;
final String svgIcon; final String svgIcon;

View File

@ -5,7 +5,16 @@ import 'package:selfprivacy/logic/models/json/dns_records.dart';
export 'package:selfprivacy/logic/api_maps/generic_result.dart'; export 'package:selfprivacy/logic/api_maps/generic_result.dart';
abstract class DnsProvider { abstract class DnsProvider {
/// Returns an assigned enum value, respectively to which
/// provider implements [DnsProvider] interface.
DnsProviderType get type; DnsProviderType get type;
/// Tries to access an account linked to the provided token.
///
/// To generate a token for your account follow instructions of your
/// DNS provider respectfully.
///
/// If success, saves it for future usage.
Future<GenericResult<bool>> tryInitApiByToken(final String token); Future<GenericResult<bool>> tryInitApiByToken(final String token);
Future<GenericResult<String?>> getZoneId(final String domain); Future<GenericResult<String?>> getZoneId(final String domain);
Future<GenericResult<void>> removeDomainRecords({ Future<GenericResult<void>> removeDomainRecords({

View File

@ -12,7 +12,12 @@ import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/ui/helpers/modals.dart';
class JobsContent extends StatelessWidget { class JobsContent extends StatelessWidget {
const JobsContent({super.key}); const JobsContent({
required this.controller,
super.key,
});
final ScrollController controller;
@override @override
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
@ -119,9 +124,10 @@ class JobsContent extends StatelessWidget {
]; ];
} }
return ListView( return ListView(
controller: controller,
padding: paddingH15V0, padding: paddingH15V0,
children: [ children: [
const SizedBox(height: 15), const SizedBox(height: 16),
Center( Center(
child: Text( child: Text(
'jobs.title'.tr(), 'jobs.title'.tr(),

View File

@ -62,7 +62,16 @@ class _BrandFabState extends State<BrandFab>
// TODO: Make a hero animation to the screen // TODO: Make a hero animation to the screen
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (final BuildContext context) => const JobsContent(), useRootNavigator: true,
isScrollControlled: true,
builder: (final BuildContext context) => DraggableScrollableSheet(
expand: false,
maxChildSize: 0.9,
minChildSize: 0.4,
initialChildSize: 0.6,
builder: (final context, final scrollController) =>
JobsContent(controller: scrollController),
),
); );
}, },
isExtended: widget.extended, isExtended: widget.extended,

View File

@ -145,7 +145,17 @@ class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
onPressed: () { onPressed: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (final BuildContext context) => const JobsContent(), useRootNavigator: true,
isScrollControlled: true,
builder: (final BuildContext context) =>
DraggableScrollableSheet(
expand: false,
maxChildSize: 0.9,
minChildSize: 0.4,
initialChildSize: 0.6,
builder: (final context, final scrollController) =>
JobsContent(controller: scrollController),
),
); );
}, },
icon: Icon( icon: Icon(

View File

@ -1,241 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
import 'package:selfprivacy/logic/models/json/backup.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/components/cards/outlined_card.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@RoutePage()
class BackupDetailsPage extends StatefulWidget {
const BackupDetailsPage({super.key});
@override
State<BackupDetailsPage> createState() => _BackupDetailsPageState();
}
class _BackupDetailsPageState extends State<BackupDetailsPage>
with SingleTickerProviderStateMixin {
@override
Widget build(final BuildContext context) {
final bool isReady = context.watch<ServerInstallationCubit>().state
is ServerInstallationFinished;
final bool isBackupInitialized =
context.watch<BackupsCubit>().state.isInitialized;
final BackupStatusEnum backupStatus =
context.watch<BackupsCubit>().state.status;
final StateType providerState = isReady && isBackupInitialized
? (backupStatus == BackupStatusEnum.error
? StateType.warning
: StateType.stable)
: StateType.uninitialized;
final bool preventActions =
context.watch<BackupsCubit>().state.preventActions;
final double backupProgress = context.watch<BackupsCubit>().state.progress;
final String backupError = context.watch<BackupsCubit>().state.error;
final List<Backup> backups = context.watch<BackupsCubit>().state.backups;
final bool refreshing = context.watch<BackupsCubit>().state.refreshing;
return BrandHeroScreen(
heroIcon: BrandIcons.save,
heroTitle: 'backup.card_title'.tr(),
heroSubtitle: 'backup.description'.tr(),
children: [
if (isReady && !isBackupInitialized)
BrandButton.rised(
onPressed: preventActions
? null
: () async {
await context.read<BackupsCubit>().createBucket();
},
text: 'backup.initialize'.tr(),
),
if (backupStatus == BackupStatusEnum.initializing)
Text(
'backup.waiting_for_rebuild'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey)
OutlinedCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (backupStatus == BackupStatusEnum.initialized)
ListTile(
onTap: preventActions
? null
: () async {
await context.read<BackupsCubit>().createBackup();
},
leading: const Icon(
Icons.add_circle_outline_rounded,
),
title: Text(
'backup.create_new'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
),
if (backupStatus == BackupStatusEnum.backingUp)
ListTile(
title: Text(
'backup.creating'.tr(
args: [(backupProgress * 100).round().toString()],
),
style: Theme.of(context).textTheme.titleLarge,
),
subtitle: LinearProgressIndicator(
value: backupProgress,
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
if (backupStatus == BackupStatusEnum.restoring)
ListTile(
title: Text(
'backup.restoring'.tr(
args: [(backupProgress * 100).round().toString()],
),
style: Theme.of(context).textTheme.titleLarge,
),
subtitle: LinearProgressIndicator(
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
if (backupStatus == BackupStatusEnum.error)
ListTile(
leading: Icon(
Icons.error_outline,
color: Theme.of(context).colorScheme.error,
),
title: Text(
'backup.error_pending'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
),
],
),
),
const SizedBox(height: 16),
// Card with a list of existing backups
// Each list item has a date
// When clicked, starts the restore action
if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey)
OutlinedCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
leading: const Icon(
Icons.refresh,
),
title: Text(
'backup.restore'.tr(),
style: Theme.of(context).textTheme.titleLarge,
),
),
const Divider(
height: 1.0,
),
if (backups.isEmpty)
ListTile(
leading: const Icon(
Icons.error_outline,
),
title: Text('backup.no_backups'.tr()),
),
if (backups.isNotEmpty)
Column(
children: backups
.map(
(final Backup backup) => ListTile(
onTap: preventActions
? null
: () {
showPopUpAlert(
alertTitle: 'backup.restoring'.tr(),
description: 'backup.restore_alert'.tr(
args: [backup.time.toString()],
),
actionButtonTitle: 'modals.yes'.tr(),
actionButtonOnPressed: () => {
context
.read<BackupsCubit>()
.restoreBackup(backup.id)
},
);
},
title: Text(
'${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}',
),
),
)
.toList(),
),
],
),
),
const SizedBox(height: 16),
OutlinedCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(
'backup.refresh'.tr(),
),
onTap: refreshing
? null
: () => {context.read<BackupsCubit>().updateBackups()},
enabled: !refreshing,
),
if (providerState != StateType.uninitialized)
Column(
children: [
const Divider(
height: 1.0,
),
ListTile(
title: Text(
'backup.refetch_backups'.tr(),
),
onTap: preventActions
? null
: () => {
context
.read<BackupsCubit>()
.forceUpdateBackups()
},
),
const Divider(
height: 1.0,
),
ListTile(
title: Text(
'backup.reupload_key'.tr(),
),
onTap: preventActions
? null
: () => {context.read<BackupsCubit>().reuploadKey()},
),
],
),
],
),
),
if (backupStatus == BackupStatusEnum.error)
Text(
backupError.toString(),
style: Theme.of(context).textTheme.bodyMedium,
),
],
);
}
}

View File

@ -0,0 +1,308 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/json/server_job.dart';
import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/ui/pages/backups/change_period_modal.dart';
import 'package:selfprivacy/ui/pages/backups/create_backups_modal.dart';
import 'package:selfprivacy/ui/router/router.dart';
import 'package:selfprivacy/utils/extensions/duration.dart';
@RoutePage()
class BackupDetailsPage extends StatelessWidget {
const BackupDetailsPage({super.key});
@override
Widget build(final BuildContext context) {
final bool isReady = context.watch<ServerInstallationCubit>().state
is ServerInstallationFinished;
final BackupsState backupsState = context.watch<BackupsCubit>().state;
final bool isBackupInitialized = backupsState.isInitialized;
final StateType providerState = isReady && isBackupInitialized
? StateType.stable
: StateType.uninitialized;
final bool preventActions = backupsState.preventActions;
final List<Backup> backups = backupsState.backups;
final bool refreshing = backupsState.refreshing;
final List<Service> services =
context.watch<ServicesCubit>().state.servicesThatCanBeBackedUp;
final Duration? autobackupPeriod = backupsState.autobackupPeriod;
final List<ServerJob> backupJobs = context
.watch<ServerJobsCubit>()
.state
.backupJobList
.where((final job) => job.status != JobStatusEnum.finished)
.toList();
if (!isReady) {
return BrandHeroScreen(
heroIcon: BrandIcons.save,
heroTitle: 'backup.card_title'.tr(),
heroSubtitle: 'not_ready_card.in_menu'.tr(),
children: const [],
);
}
if (!isBackupInitialized) {
return BrandHeroScreen(
heroIcon: BrandIcons.save,
heroTitle: 'backup.card_title'.tr(),
heroSubtitle: 'backup.description'.tr(),
children: [
BrandButton.rised(
onPressed: preventActions
? null
: () async {
await context.read<BackupsCubit>().initializeBackups();
},
text: 'backup.initialize'.tr(),
),
],
);
}
return BrandHeroScreen(
heroIcon: BrandIcons.save,
heroTitle: 'backup.card_title'.tr(),
heroSubtitle: 'backup.description'.tr(),
children: [
ListTile(
onTap: preventActions
? null
: () {
showModalBottomSheet(
useRootNavigator: true,
context: context,
isScrollControlled: true,
builder: (final BuildContext context) =>
DraggableScrollableSheet(
expand: false,
maxChildSize: 0.9,
minChildSize: 0.4,
initialChildSize: 0.6,
builder: (final context, final scrollController) =>
CreateBackupsModal(
services: services,
scrollController: scrollController,
),
),
);
},
leading: const Icon(
Icons.add_circle_outline_rounded,
),
title: Text(
'backup.create_new'.tr(),
),
),
ListTile(
onTap: preventActions
? null
: () {
showModalBottomSheet(
useRootNavigator: true,
context: context,
isScrollControlled: true,
builder: (final BuildContext context) =>
DraggableScrollableSheet(
expand: false,
maxChildSize: 0.9,
minChildSize: 0.4,
initialChildSize: 0.6,
builder: (final context, final scrollController) =>
ChangeAutobackupsPeriodModal(
scrollController: scrollController,
),
),
);
},
leading: const Icon(
Icons.manage_history_outlined,
),
title: Text(
'backup.autobackup_period_title'.tr(),
),
subtitle: Text(
autobackupPeriod != null
? 'backup.autobackup_period_subtitle'.tr(
namedArgs: {
'period': autobackupPeriod.toPrettyString(context.locale)
},
)
: 'backup.autobackup_period_never'.tr(),
),
),
const SizedBox(height: 16),
if (backupJobs.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(
'backup.pending_jobs'.tr(),
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
),
for (final job in backupJobs)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ServerJobCard(
serverJob: job,
),
),
],
),
if (isBackupInitialized)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(
'backup.latest_snapshots'.tr(),
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
subtitle: Text(
'backup.latest_snapshots_subtitle'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
),
if (backups.isEmpty)
ListTile(
leading: const Icon(
Icons.error_outline,
),
title: Text('backup.no_backups'.tr()),
),
if (backups.isNotEmpty)
Column(
children: backups.take(15).map(
(final Backup backup) {
final service = context
.read<ServicesCubit>()
.state
.getServiceById(backup.serviceId);
return ListTile(
onTap: preventActions
? null
: () {
showPopUpAlert(
alertTitle: 'backup.restoring'.tr(),
description: 'backup.restore_alert'.tr(
args: [backup.time.toString()],
),
actionButtonTitle: 'modals.yes'.tr(),
actionButtonOnPressed: () => {
context
.read<BackupsCubit>()
.restoreBackup(backup.id)
},
);
},
title: Text(
'${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}',
),
subtitle: Text(
service?.displayName ?? backup.fallbackServiceName,
),
leading: service != null
? SvgPicture.string(
service.svgIcon,
height: 24,
width: 24,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onBackground,
BlendMode.srcIn,
),
)
: const Icon(
Icons.question_mark_outlined,
),
);
},
).toList(),
),
if (backups.isNotEmpty && backups.length > 15)
ListTile(
title: Text(
'backup.show_more'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
leading: const Icon(
Icons.arrow_drop_down,
),
onTap: () =>
context.pushRoute(BackupsListRoute(service: null)),
)
],
),
const SizedBox(height: 8),
const Divider(),
const SizedBox(height: 8),
ListTile(
title: Text(
'backup.refresh'.tr(),
),
onTap: refreshing
? null
: () => {context.read<BackupsCubit>().updateBackups()},
enabled: !refreshing,
leading: const Icon(
Icons.refresh_outlined,
),
),
if (providerState != StateType.uninitialized)
Column(
children: [
ListTile(
title: Text(
'backup.refetch_backups'.tr(),
),
subtitle: Text(
'backup.refetch_backups_subtitle'.tr(),
),
leading: const Icon(
Icons.cached_outlined,
),
onTap: preventActions
? null
: () => {context.read<BackupsCubit>().forceUpdateBackups()},
),
const SizedBox(height: 8),
const Divider(),
const SizedBox(height: 8),
ListTile(
title: Text(
'backup.reupload_key'.tr(),
),
subtitle: Text(
'backup.reupload_key_subtitle'.tr(),
),
leading: const Icon(
Icons.warning_amber_outlined,
),
onTap: preventActions
? null
: () => {context.read<BackupsCubit>().reuploadKey()},
),
],
),
],
);
}
}

View File

@ -0,0 +1,85 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
@RoutePage()
class BackupsListPage extends StatelessWidget {
const BackupsListPage({
required this.service,
super.key,
});
final Service? service;
@override
Widget build(final BuildContext context) {
// If the service is null, get all backups from state. If not null, call the
// serviceBackups(serviceId) on the backups state.
final List<Backup> backups = service == null
? context.watch<BackupsCubit>().state.backups
: context.watch<BackupsCubit>().state.serviceBackups(service!.id);
final bool preventActions =
context.watch<BackupsCubit>().state.preventActions;
return BrandHeroScreen(
heroTitle: 'backup.snapshots_title'.tr(),
children: [
if (backups.isEmpty)
Center(
child: Text(
'backup.no_backups'.tr(),
),
)
else
...backups.map((final Backup backup) {
final service = context
.read<ServicesCubit>()
.state
.getServiceById(backup.serviceId);
return ListTile(
onTap: preventActions
? null
: () {
showPopUpAlert(
alertTitle: 'backup.restoring'.tr(),
description: 'backup.restore_alert'.tr(
args: [backup.time.toString()],
),
actionButtonTitle: 'modals.yes'.tr(),
actionButtonOnPressed: () => {
context.read<BackupsCubit>().restoreBackup(backup.id)
},
);
},
title: Text(
'${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}',
),
subtitle: Text(
service?.displayName ?? backup.fallbackServiceName,
),
leading: service != null
? SvgPicture.string(
service.svgIcon,
height: 24,
width: 24,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onBackground,
BlendMode.srcIn,
),
)
: const Icon(
Icons.question_mark_outlined,
),
);
})
],
);
}
}

View File

@ -0,0 +1,108 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
import 'package:selfprivacy/utils/extensions/duration.dart';
class ChangeAutobackupsPeriodModal extends StatefulWidget {
const ChangeAutobackupsPeriodModal({
required this.scrollController,
super.key,
});
final ScrollController scrollController;
@override
State<ChangeAutobackupsPeriodModal> createState() =>
_ChangeAutobackupsPeriodModalState();
}
class _ChangeAutobackupsPeriodModalState
extends State<ChangeAutobackupsPeriodModal> {
// This is a modal with radio buttons to select the autobackup period
// Period might be none, selected from predefined list or custom
// Store in state the selected period
Duration? selectedPeriod;
static const List<Duration> autobackupPeriods = [
Duration(hours: 12),
Duration(days: 1),
Duration(days: 2),
Duration(days: 3),
Duration(days: 7),
];
// Set initial period to the one currently set
@override
void initState() {
super.initState();
selectedPeriod = context.read<BackupsCubit>().state.autobackupPeriod;
}
@override
Widget build(final BuildContext context) {
final Duration? initialAutobackupPeriod =
context.watch<BackupsCubit>().state.autobackupPeriod;
return ListView(
controller: widget.scrollController,
padding: const EdgeInsets.all(16),
children: [
const SizedBox(height: 16),
Text(
'backup.autobackup_period_title'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// Select all services tile
RadioListTile<Duration?>(
onChanged: (final Duration? value) {
setState(() {
selectedPeriod = value;
});
},
title: Text(
'backup.autobackup_period_disable'.tr(),
),
value: null,
groupValue: selectedPeriod,
),
const Divider(
height: 1.0,
),
...autobackupPeriods.map(
(final Duration period) => RadioListTile<Duration?>(
onChanged: (final Duration? value) {
setState(() {
selectedPeriod = value;
});
},
title: Text(
'backup.autobackup_period_every'.tr(
namedArgs: {'period': period.toPrettyString(context.locale)},
),
),
value: period,
groupValue: selectedPeriod,
),
),
const SizedBox(height: 16),
// Create backup button
FilledButton(
onPressed: selectedPeriod == initialAutobackupPeriod
? null
: () {
context
.read<BackupsCubit>()
.setAutobackupPeriod(selectedPeriod);
Navigator.of(context).pop();
},
child: Text(
'backup.autobackup_set_period'.tr(),
),
),
],
);
}
}

View File

@ -0,0 +1,161 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart';
import 'package:selfprivacy/logic/models/json/server_job.dart';
import 'package:selfprivacy/logic/models/service.dart';
class CreateBackupsModal extends StatefulWidget {
const CreateBackupsModal({
required this.services,
required this.scrollController,
super.key,
});
final List<Service> services;
final ScrollController scrollController;
@override
State<CreateBackupsModal> createState() => _CreateBackupsModalState();
}
class _CreateBackupsModalState extends State<CreateBackupsModal> {
// Store in state the selected services to backup
List<Service> selectedServices = [];
// Select all services on modal open
@override
void initState() {
super.initState();
final List<String> busyServices = context
.read<ServerJobsCubit>()
.state
.backupJobList
.where(
(final ServerJob job) =>
job.status == JobStatusEnum.running ||
job.status == JobStatusEnum.created,
)
.map((final ServerJob job) => job.typeId.split('.')[1])
.toList();
selectedServices.addAll(
widget.services
.where((final Service service) => !busyServices.contains(service.id)),
);
}
@override
Widget build(final BuildContext context) {
final List<String> busyServices = context
.watch<ServerJobsCubit>()
.state
.backupJobList
.where(
(final ServerJob job) =>
job.status == JobStatusEnum.running ||
job.status == JobStatusEnum.created,
)
.map((final ServerJob job) => job.typeId.split('.')[1])
.toList();
return ListView(
controller: widget.scrollController,
padding: const EdgeInsets.all(16),
children: [
const SizedBox(height: 16),
Text(
'backup.create_new_select_heading'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// Select all services tile
CheckboxListTile(
onChanged: (final bool? value) {
setState(() {
if (value ?? true) {
setState(() {
selectedServices.clear();
selectedServices.addAll(
widget.services.where(
(final service) => !busyServices.contains(service.id),
),
);
});
} else {
selectedServices.clear();
}
});
},
title: Text(
'backup.select_all'.tr(),
),
secondary: const Icon(
Icons.checklist_outlined,
),
value: selectedServices.length >=
widget.services.length - busyServices.length,
),
const Divider(
height: 1.0,
),
...widget.services.map(
(final Service service) {
final bool busy = busyServices.contains(service.id);
return CheckboxListTile(
onChanged: !busy
? (final bool? value) {
setState(() {
if (value ?? true) {
setState(() {
selectedServices.add(service);
});
} else {
setState(() {
selectedServices.remove(service);
});
}
});
}
: null,
title: Text(
service.displayName,
),
subtitle: Text(
busy ? 'backup.service_busy'.tr() : service.backupDescription,
),
secondary: SvgPicture.string(
service.svgIcon,
height: 24,
width: 24,
colorFilter: ColorFilter.mode(
busy
? Theme.of(context).colorScheme.outlineVariant
: Theme.of(context).colorScheme.onBackground,
BlendMode.srcIn,
),
),
value: selectedServices.contains(service),
);
},
),
const SizedBox(height: 16),
// Create backup button
FilledButton(
onPressed: selectedServices.isEmpty
? null
: () {
context
.read<BackupsCubit>()
.createMultipleBackups(selectedServices);
Navigator.of(context).pop();
},
child: Text(
'backup.start'.tr(),
),
),
],
);
}
}

View File

@ -44,11 +44,10 @@ class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
), ),
SwitchListTile( SwitchListTile(
title: Text('developer_settings.ignore_tls'.tr()), title: Text('developer_settings.ignore_tls'.tr()),
subtitle: subtitle: Text('developer_settings.ignore_tls_description'.tr()),
Text('developer_settings.ignore_tls_description'.tr()),
value: TlsOptions.verifyCertificate, value: TlsOptions.verifyCertificate,
onChanged: (final bool value) => setState( onChanged: (final bool value) => setState(
() => TlsOptions.verifyCertificate = value, () => TlsOptions.verifyCertificate = value,
), ),
), ),
Padding( Padding(

View File

@ -97,16 +97,15 @@ class _ProvidersPageState extends State<ProvidersPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// TODO: When backups are fixed, show this card // TODO: When backups are fixed, show this card
if (isBackupInitialized) _Card(
_Card( state: isBackupInitialized
state: isBackupInitialized ? StateType.stable
? StateType.stable : StateType.uninitialized,
: StateType.uninitialized, icon: BrandIcons.save,
icon: BrandIcons.save, title: 'backup.card_title'.tr(),
title: 'backup.card_title'.tr(), subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '',
subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '', onTap: () => context.pushRoute(const BackupDetailsRoute()),
onTap: () => context.pushRoute(const BackupDetailsRoute()), ),
),
], ],
), ),
); );

View File

@ -107,7 +107,7 @@ class _SelectTimezoneState extends State<SelectTimezone> {
Duration( Duration(
milliseconds: location.currentTimeZone.offset, milliseconds: location.currentTimeZone.offset,
) )
.toDayHourMinuteFormat() .toTimezoneOffsetFormat()
.contains(timezoneFilterValue!), .contains(timezoneFilterValue!),
) )
.toList() .toList()
@ -137,7 +137,7 @@ class _SelectTimezoneState extends State<SelectTimezone> {
location.name, location.name,
), ),
subtitle: Text( subtitle: Text(
'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}', 'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}',
), ),
onTap: () { onTap: () {
context.read<ServerDetailsCubit>().repository.setTimezone( context.read<ServerDetailsCubit>().repository.setTimezone(

View File

@ -177,7 +177,17 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
context.router.popUntilRoot(); context.router.popUntilRoot();
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (final BuildContext context) => const JobsContent(), useRootNavigator: true,
isScrollControlled: true,
builder: (final BuildContext context) =>
DraggableScrollableSheet(
expand: false,
maxChildSize: 0.9,
minChildSize: 0.4,
initialChildSize: 0.6,
builder: (final context, final scrollController) =>
JobsContent(controller: scrollController),
),
); );
}, },
), ),

View File

@ -141,6 +141,19 @@ class _ServicePageState extends State<ServicePage> {
), ),
enabled: !serviceDisabled && !serviceLocked, enabled: !serviceDisabled && !serviceLocked,
), ),
if (service.canBeBackedUp)
ListTile(
iconColor: Theme.of(context).colorScheme.onBackground,
// Open page ServicesMigrationPage
onTap: () => context.pushRoute(
BackupsListRoute(service: service),
),
leading: const Icon(Icons.settings_backup_restore_outlined),
title: Text(
'service_page.snapshots'.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
),
], ],
); );
} }

View File

@ -3,7 +3,8 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/disk_status.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart'; import 'package:selfprivacy/ui/pages/backups/backup_details.dart';
import 'package:selfprivacy/ui/pages/backups/backups_list.dart';
import 'package:selfprivacy/ui/pages/devices/devices.dart'; import 'package:selfprivacy/ui/pages/devices/devices.dart';
import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart'; import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart';
import 'package:selfprivacy/ui/pages/more/about_application.dart'; import 'package:selfprivacy/ui/pages/more/about_application.dart';
@ -96,6 +97,7 @@ class RootRouter extends _$RootRouter {
AutoRoute(page: ServerDetailsRoute.page), AutoRoute(page: ServerDetailsRoute.page),
AutoRoute(page: DnsDetailsRoute.page), AutoRoute(page: DnsDetailsRoute.page),
AutoRoute(page: BackupDetailsRoute.page), AutoRoute(page: BackupDetailsRoute.page),
AutoRoute(page: BackupsListRoute.page),
AutoRoute(page: ServerStorageRoute.page), AutoRoute(page: ServerStorageRoute.page),
AutoRoute(page: ExtendingVolumeRoute.page), AutoRoute(page: ExtendingVolumeRoute.page),
], ],
@ -141,6 +143,8 @@ String getRouteTitle(final String routeName) {
return 'server.card_title'; return 'server.card_title';
case 'BackupDetailsRoute': case 'BackupDetailsRoute':
return 'backup.card_title'; return 'backup.card_title';
case 'BackupsListRoute':
return 'backup.snapshots_title';
case 'ServerStorageRoute': case 'ServerStorageRoute':
return 'storage.card_title'; return 'storage.card_title';
case 'ExtendingVolumeRoute': case 'ExtendingVolumeRoute':

View File

@ -15,16 +15,103 @@ abstract class _$RootRouter extends RootStackRouter {
@override @override
final Map<String, PageFactory> pagesMap = { final Map<String, PageFactory> pagesMap = {
BackupDetailsRoute.name: (routeData) { DevicesRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const BackupDetailsPage(), child: const DevicesScreen(),
); );
}, },
RootRoute.name: (routeData) { DnsDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: WrappedRoute(child: const RootPage()), child: const DnsDetailsPage(),
);
},
AppSettingsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AppSettingsPage(),
);
},
DeveloperSettingsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DeveloperSettingsPage(),
);
},
AboutApplicationRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AboutApplicationPage(),
);
},
MoreRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const MorePage(),
);
},
ConsoleRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ConsolePage(),
);
},
OnboardingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const OnboardingPage(),
);
},
ProvidersRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ProvidersPage(),
);
},
RecoveryKeyRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryKeyPage(),
);
},
ServerDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ServerDetailsScreen(),
);
},
ServicesMigrationRoute.name: (routeData) {
final args = routeData.argsAs<ServicesMigrationRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: ServicesMigrationPage(
services: args.services,
diskStatus: args.diskStatus,
isMigration: args.isMigration,
key: args.key,
),
);
},
ServerStorageRoute.name: (routeData) {
final args = routeData.argsAs<ServerStorageRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: ServerStoragePage(
diskStatus: args.diskStatus,
key: args.key,
),
);
},
ExtendingVolumeRoute.name: (routeData) {
final args = routeData.argsAs<ExtendingVolumeRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: ExtendingVolumePage(
diskVolumeToResize: args.diskVolumeToResize,
diskStatus: args.diskStatus,
key: args.key,
),
); );
}, },
ServiceRoute.name: (routeData) { ServiceRoute.name: (routeData) {
@ -43,10 +130,16 @@ abstract class _$RootRouter extends RootStackRouter {
child: const ServicesPage(), child: const ServicesPage(),
); );
}, },
ServerDetailsRoute.name: (routeData) { InitializingRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const ServerDetailsScreen(), child: const InitializingPage(),
);
},
RecoveryRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryRouting(),
); );
}, },
UsersRoute.name: (routeData) { UsersRoute.name: (routeData) {
@ -71,274 +164,59 @@ abstract class _$RootRouter extends RootStackRouter {
), ),
); );
}, },
AppSettingsRoute.name: (routeData) { RootRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const AppSettingsPage(), child: WrappedRoute(child: const RootPage()),
); );
}, },
DeveloperSettingsRoute.name: (routeData) { BackupDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const DeveloperSettingsPage(), child: const BackupDetailsPage(),
); );
}, },
MoreRoute.name: (routeData) { BackupsListRoute.name: (routeData) {
final args = routeData.argsAs<BackupsListRouteArgs>();
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const MorePage(), child: BackupsListPage(
); service: args.service,
},
AboutApplicationRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AboutApplicationPage(),
);
},
ConsoleRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ConsolePage(),
);
},
ProvidersRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ProvidersPage(),
);
},
RecoveryKeyRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryKeyPage(),
);
},
DnsDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DnsDetailsPage(),
);
},
RecoveryRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryRouting(),
);
},
InitializingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const InitializingPage(),
);
},
ServerStorageRoute.name: (routeData) {
final args = routeData.argsAs<ServerStorageRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: ServerStoragePage(
diskStatus: args.diskStatus,
key: args.key, key: args.key,
), ),
); );
}, },
ExtendingVolumeRoute.name: (routeData) {
final args = routeData.argsAs<ExtendingVolumeRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: ExtendingVolumePage(
diskVolumeToResize: args.diskVolumeToResize,
diskStatus: args.diskStatus,
key: args.key,
),
);
},
ServicesMigrationRoute.name: (routeData) {
final args = routeData.argsAs<ServicesMigrationRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: ServicesMigrationPage(
services: args.services,
diskStatus: args.diskStatus,
isMigration: args.isMigration,
key: args.key,
),
);
},
DevicesRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DevicesScreen(),
);
},
OnboardingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const OnboardingPage(),
);
},
}; };
} }
/// generated route for /// generated route for
/// [BackupDetailsPage] /// [DevicesScreen]
class BackupDetailsRoute extends PageRouteInfo<void> { class DevicesRoute extends PageRouteInfo<void> {
const BackupDetailsRoute({List<PageRouteInfo>? children}) const DevicesRoute({List<PageRouteInfo>? children})
: super( : super(
BackupDetailsRoute.name, DevicesRoute.name,
initialChildren: children, initialChildren: children,
); );
static const String name = 'BackupDetailsRoute'; static const String name = 'DevicesRoute';
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [RootPage] /// [DnsDetailsPage]
class RootRoute extends PageRouteInfo<void> { class DnsDetailsRoute extends PageRouteInfo<void> {
const RootRoute({List<PageRouteInfo>? children}) const DnsDetailsRoute({List<PageRouteInfo>? children})
: super( : super(
RootRoute.name, DnsDetailsRoute.name,
initialChildren: children, initialChildren: children,
); );
static const String name = 'RootRoute'; static const String name = 'DnsDetailsRoute';
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for
/// [ServicePage]
class ServiceRoute extends PageRouteInfo<ServiceRouteArgs> {
ServiceRoute({
required String serviceId,
Key? key,
List<PageRouteInfo>? children,
}) : super(
ServiceRoute.name,
args: ServiceRouteArgs(
serviceId: serviceId,
key: key,
),
initialChildren: children,
);
static const String name = 'ServiceRoute';
static const PageInfo<ServiceRouteArgs> page =
PageInfo<ServiceRouteArgs>(name);
}
class ServiceRouteArgs {
const ServiceRouteArgs({
required this.serviceId,
this.key,
});
final String serviceId;
final Key? key;
@override
String toString() {
return 'ServiceRouteArgs{serviceId: $serviceId, key: $key}';
}
}
/// generated route for
/// [ServicesPage]
class ServicesRoute extends PageRouteInfo<void> {
const ServicesRoute({List<PageRouteInfo>? children})
: super(
ServicesRoute.name,
initialChildren: children,
);
static const String name = 'ServicesRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [ServerDetailsScreen]
class ServerDetailsRoute extends PageRouteInfo<void> {
const ServerDetailsRoute({List<PageRouteInfo>? children})
: super(
ServerDetailsRoute.name,
initialChildren: children,
);
static const String name = 'ServerDetailsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [UsersPage]
class UsersRoute extends PageRouteInfo<void> {
const UsersRoute({List<PageRouteInfo>? children})
: super(
UsersRoute.name,
initialChildren: children,
);
static const String name = 'UsersRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [NewUserPage]
class NewUserRoute extends PageRouteInfo<void> {
const NewUserRoute({List<PageRouteInfo>? children})
: super(
NewUserRoute.name,
initialChildren: children,
);
static const String name = 'NewUserRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [UserDetailsPage]
class UserDetailsRoute extends PageRouteInfo<UserDetailsRouteArgs> {
UserDetailsRoute({
required String login,
Key? key,
List<PageRouteInfo>? children,
}) : super(
UserDetailsRoute.name,
args: UserDetailsRouteArgs(
login: login,
key: key,
),
initialChildren: children,
);
static const String name = 'UserDetailsRoute';
static const PageInfo<UserDetailsRouteArgs> page =
PageInfo<UserDetailsRouteArgs>(name);
}
class UserDetailsRouteArgs {
const UserDetailsRouteArgs({
required this.login,
this.key,
});
final String login;
final Key? key;
@override
String toString() {
return 'UserDetailsRouteArgs{login: $login, key: $key}';
}
}
/// generated route for /// generated route for
/// [AppSettingsPage] /// [AppSettingsPage]
class AppSettingsRoute extends PageRouteInfo<void> { class AppSettingsRoute extends PageRouteInfo<void> {
@ -367,20 +245,6 @@ class DeveloperSettingsRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for
/// [MorePage]
class MoreRoute extends PageRouteInfo<void> {
const MoreRoute({List<PageRouteInfo>? children})
: super(
MoreRoute.name,
initialChildren: children,
);
static const String name = 'MoreRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for /// generated route for
/// [AboutApplicationPage] /// [AboutApplicationPage]
class AboutApplicationRoute extends PageRouteInfo<void> { class AboutApplicationRoute extends PageRouteInfo<void> {
@ -395,6 +259,20 @@ class AboutApplicationRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for
/// [MorePage]
class MoreRoute extends PageRouteInfo<void> {
const MoreRoute({List<PageRouteInfo>? children})
: super(
MoreRoute.name,
initialChildren: children,
);
static const String name = 'MoreRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for /// generated route for
/// [ConsolePage] /// [ConsolePage]
class ConsoleRoute extends PageRouteInfo<void> { class ConsoleRoute extends PageRouteInfo<void> {
@ -409,6 +287,20 @@ class ConsoleRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for
/// [OnboardingPage]
class OnboardingRoute extends PageRouteInfo<void> {
const OnboardingRoute({List<PageRouteInfo>? children})
: super(
OnboardingRoute.name,
initialChildren: children,
);
static const String name = 'OnboardingRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for /// generated route for
/// [ProvidersPage] /// [ProvidersPage]
class ProvidersRoute extends PageRouteInfo<void> { class ProvidersRoute extends PageRouteInfo<void> {
@ -438,45 +330,65 @@ class RecoveryKeyRoute extends PageRouteInfo<void> {
} }
/// generated route for /// generated route for
/// [DnsDetailsPage] /// [ServerDetailsScreen]
class DnsDetailsRoute extends PageRouteInfo<void> { class ServerDetailsRoute extends PageRouteInfo<void> {
const DnsDetailsRoute({List<PageRouteInfo>? children}) const ServerDetailsRoute({List<PageRouteInfo>? children})
: super( : super(
DnsDetailsRoute.name, ServerDetailsRoute.name,
initialChildren: children, initialChildren: children,
); );
static const String name = 'DnsDetailsRoute'; static const String name = 'ServerDetailsRoute';
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [RecoveryRouting] /// [ServicesMigrationPage]
class RecoveryRoute extends PageRouteInfo<void> { class ServicesMigrationRoute extends PageRouteInfo<ServicesMigrationRouteArgs> {
const RecoveryRoute({List<PageRouteInfo>? children}) ServicesMigrationRoute({
: super( required List<Service> services,
RecoveryRoute.name, required DiskStatus diskStatus,
required bool isMigration,
Key? key,
List<PageRouteInfo>? children,
}) : super(
ServicesMigrationRoute.name,
args: ServicesMigrationRouteArgs(
services: services,
diskStatus: diskStatus,
isMigration: isMigration,
key: key,
),
initialChildren: children, initialChildren: children,
); );
static const String name = 'RecoveryRoute'; static const String name = 'ServicesMigrationRoute';
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<ServicesMigrationRouteArgs> page =
PageInfo<ServicesMigrationRouteArgs>(name);
} }
/// generated route for class ServicesMigrationRouteArgs {
/// [InitializingPage] const ServicesMigrationRouteArgs({
class InitializingRoute extends PageRouteInfo<void> { required this.services,
const InitializingRoute({List<PageRouteInfo>? children}) required this.diskStatus,
: super( required this.isMigration,
InitializingRoute.name, this.key,
initialChildren: children, });
);
static const String name = 'InitializingRoute'; final List<Service> services;
static const PageInfo<void> page = PageInfo<void>(name); final DiskStatus diskStatus;
final bool isMigration;
final Key? key;
@override
String toString() {
return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}';
}
} }
/// generated route for /// generated route for
@ -561,77 +473,213 @@ class ExtendingVolumeRouteArgs {
} }
/// generated route for /// generated route for
/// [ServicesMigrationPage] /// [ServicePage]
class ServicesMigrationRoute extends PageRouteInfo<ServicesMigrationRouteArgs> { class ServiceRoute extends PageRouteInfo<ServiceRouteArgs> {
ServicesMigrationRoute({ ServiceRoute({
required List<Service> services, required String serviceId,
required DiskStatus diskStatus,
required bool isMigration,
Key? key, Key? key,
List<PageRouteInfo>? children, List<PageRouteInfo>? children,
}) : super( }) : super(
ServicesMigrationRoute.name, ServiceRoute.name,
args: ServicesMigrationRouteArgs( args: ServiceRouteArgs(
services: services, serviceId: serviceId,
diskStatus: diskStatus,
isMigration: isMigration,
key: key, key: key,
), ),
initialChildren: children, initialChildren: children,
); );
static const String name = 'ServicesMigrationRoute'; static const String name = 'ServiceRoute';
static const PageInfo<ServicesMigrationRouteArgs> page = static const PageInfo<ServiceRouteArgs> page =
PageInfo<ServicesMigrationRouteArgs>(name); PageInfo<ServiceRouteArgs>(name);
} }
class ServicesMigrationRouteArgs { class ServiceRouteArgs {
const ServicesMigrationRouteArgs({ const ServiceRouteArgs({
required this.services, required this.serviceId,
required this.diskStatus,
required this.isMigration,
this.key, this.key,
}); });
final List<Service> services; final String serviceId;
final DiskStatus diskStatus;
final bool isMigration;
final Key? key; final Key? key;
@override @override
String toString() { String toString() {
return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}'; return 'ServiceRouteArgs{serviceId: $serviceId, key: $key}';
} }
} }
/// generated route for /// generated route for
/// [DevicesScreen] /// [ServicesPage]
class DevicesRoute extends PageRouteInfo<void> { class ServicesRoute extends PageRouteInfo<void> {
const DevicesRoute({List<PageRouteInfo>? children}) const ServicesRoute({List<PageRouteInfo>? children})
: super( : super(
DevicesRoute.name, ServicesRoute.name,
initialChildren: children, initialChildren: children,
); );
static const String name = 'DevicesRoute'; static const String name = 'ServicesRoute';
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for /// generated route for
/// [OnboardingPage] /// [InitializingPage]
class OnboardingRoute extends PageRouteInfo<void> { class InitializingRoute extends PageRouteInfo<void> {
const OnboardingRoute({List<PageRouteInfo>? children}) const InitializingRoute({List<PageRouteInfo>? children})
: super( : super(
OnboardingRoute.name, InitializingRoute.name,
initialChildren: children, initialChildren: children,
); );
static const String name = 'OnboardingRoute'; static const String name = 'InitializingRoute';
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for
/// [RecoveryRouting]
class RecoveryRoute extends PageRouteInfo<void> {
const RecoveryRoute({List<PageRouteInfo>? children})
: super(
RecoveryRoute.name,
initialChildren: children,
);
static const String name = 'RecoveryRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [UsersPage]
class UsersRoute extends PageRouteInfo<void> {
const UsersRoute({List<PageRouteInfo>? children})
: super(
UsersRoute.name,
initialChildren: children,
);
static const String name = 'UsersRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [NewUserPage]
class NewUserRoute extends PageRouteInfo<void> {
const NewUserRoute({List<PageRouteInfo>? children})
: super(
NewUserRoute.name,
initialChildren: children,
);
static const String name = 'NewUserRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [UserDetailsPage]
class UserDetailsRoute extends PageRouteInfo<UserDetailsRouteArgs> {
UserDetailsRoute({
required String login,
Key? key,
List<PageRouteInfo>? children,
}) : super(
UserDetailsRoute.name,
args: UserDetailsRouteArgs(
login: login,
key: key,
),
initialChildren: children,
);
static const String name = 'UserDetailsRoute';
static const PageInfo<UserDetailsRouteArgs> page =
PageInfo<UserDetailsRouteArgs>(name);
}
class UserDetailsRouteArgs {
const UserDetailsRouteArgs({
required this.login,
this.key,
});
final String login;
final Key? key;
@override
String toString() {
return 'UserDetailsRouteArgs{login: $login, key: $key}';
}
}
/// generated route for
/// [RootPage]
class RootRoute extends PageRouteInfo<void> {
const RootRoute({List<PageRouteInfo>? children})
: super(
RootRoute.name,
initialChildren: children,
);
static const String name = 'RootRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [BackupDetailsPage]
class BackupDetailsRoute extends PageRouteInfo<void> {
const BackupDetailsRoute({List<PageRouteInfo>? children})
: super(
BackupDetailsRoute.name,
initialChildren: children,
);
static const String name = 'BackupDetailsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [BackupsListPage]
class BackupsListRoute extends PageRouteInfo<BackupsListRouteArgs> {
BackupsListRoute({
required Service? service,
Key? key,
List<PageRouteInfo>? children,
}) : super(
BackupsListRoute.name,
args: BackupsListRouteArgs(
service: service,
key: key,
),
initialChildren: children,
);
static const String name = 'BackupsListRoute';
static const PageInfo<BackupsListRouteArgs> page =
PageInfo<BackupsListRouteArgs>(name);
}
class BackupsListRouteArgs {
const BackupsListRouteArgs({
required this.service,
this.key,
});
final Service? service;
final Key? key;
@override
String toString() {
return 'BackupsListRouteArgs{service: $service, key: $key}';
}
}

View File

@ -1,13 +1,12 @@
// ignore_for_file: unnecessary_this // ignore_for_file: unnecessary_this
extension DurationFormatter on Duration { import 'dart:ui';
String toDayHourMinuteSecondFormat() => [
this.inHours.remainder(24),
this.inMinutes.remainder(60),
this.inSeconds.remainder(60)
].map((final int seg) => seg.toString().padLeft(2, '0')).join(':');
String toDayHourMinuteFormat() { import 'package:duration/duration.dart';
import 'package:duration/locale.dart';
extension DurationFormatter on Duration {
String toTimezoneOffsetFormat() {
final designator = this >= Duration.zero ? '+' : '-'; final designator = this >= Duration.zero ? '+' : '-';
final Iterable<String> segments = [ final Iterable<String> segments = [
@ -18,15 +17,10 @@ extension DurationFormatter on Duration {
return '$designator${segments.first}:${segments.last}'; return '$designator${segments.first}:${segments.last}';
} }
// WAT: https://flutterigniter.com/how-to-format-duration/ String toPrettyString(final Locale locale) =>
String toHoursMinutesSecondsFormat() => prettyDuration(this, locale: getDurationLocale(locale));
this.toString().split('.').first.padLeft(8, '0');
String toDayHourMinuteFormat2() {
final Iterable<String> segments = [
this.inHours.remainder(24),
this.inMinutes.remainder(60),
].map((final int seg) => seg.toString().padLeft(2, '0'));
return '${segments.first} h ${segments.last} min';
}
} }
DurationLocale getDurationLocale(final Locale locale) =>
DurationLocale.fromLanguageCode(locale.languageCode) ??
const EnglishDurationLocale();

View File

@ -3,12 +3,3 @@ import 'package:flutter/material.dart';
Route materialRoute(final Widget widget) => MaterialPageRoute( Route materialRoute(final Widget widget) => MaterialPageRoute(
builder: (final BuildContext context) => widget, builder: (final BuildContext context) => widget,
); );
Route noAnimationRoute(final Widget widget) => PageRouteBuilder(
pageBuilder: (
final BuildContext context,
final Animation<double> animation1,
final Animation<double> animation2,
) =>
widget,
);

View File

@ -297,6 +297,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.2" version: "5.1.2"
duration:
dependency: "direct main"
description:
name: duration
sha256: d0b29d0a345429e3986ac56d60e4aef65b37d11e653022b2b9a4b361332b777f
url: "https://pub.dev"
source: hosted
version: "3.0.12"
dynamic_color: dynamic_color:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -15,6 +15,7 @@ dependencies:
cubit_form: ^2.0.1 cubit_form: ^2.0.1
device_info_plus: ^9.0.2 device_info_plus: ^9.0.2
dio: ^5.1.2 dio: ^5.1.2
duration: 3.0.12
dynamic_color: ^1.6.5 dynamic_color: ^1.6.5
easy_localization: ^3.0.2 easy_localization: ^3.0.2
either_option: ^2.0.1-dev.1 either_option: ^2.0.1-dev.1