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": {
"card_title": "Backup",
"card_subtitle": "Manage your backups",
"description": "Will save your day in case of incident: hackers attack, server deletion, etc.",
"reupload_key": "Force reupload key",
"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?",
"refresh": "Refresh status",
"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": {
"card_title": "Server Storage",
@ -210,6 +231,7 @@
"enable": "Enable service",
"move": "Move to another volume",
"uses": "Uses {usage} on {volume}",
"snapshots": "Backup snapshots",
"status": {
"active": "Up and running",
"inactive": "Stopped",
@ -514,4 +536,4 @@
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
"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:hive_flutter/hive_flutter.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_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
@ -15,12 +15,13 @@ class HiveConfig {
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(ServerHostingDetailsAdapter());
Hive.registerAdapter(ServerDomainAdapter());
Hive.registerAdapter(BackblazeCredentialAdapter());
Hive.registerAdapter(BackupsCredentialAdapter());
Hive.registerAdapter(BackblazeBucketAdapter());
Hive.registerAdapter(ServerVolumeAdapter());
Hive.registerAdapter(UserTypeAdapter());
Hive.registerAdapter(DnsProviderTypeAdapter());
Hive.registerAdapter(ServerProviderTypeAdapter());
Hive.registerAdapter(BackupsProviderTypeAdapter());
await Hive.openBox(BNames.appSettingsBox);
@ -110,7 +111,7 @@ class BNames {
/// A [ServerHostingDetails] field of [serverInstallationBox] box.
static String serverDetails = 'hetznerServer';
/// A [BackblazeCredential] field of [serverInstallationBox] box.
/// A [BackupsCredential] field of [serverInstallationBox] box.
static String backblazeCredential = 'backblazeKey';
/// 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 {
storage {
volumes {
@ -53,17 +47,7 @@ mutation MigrateToBinds($input: MigrateToBindsInput!) {
migrateToBinds(input: $input) {
...basicMutationReturnFields
job {
createdAt
description
error
finishedAt
name
progress
result
status
statusText
uid
updatedAt
...basicApiJobsFields
}
}
}

View File

@ -1,54 +1,65 @@
type Alert {
message: String!
severity: Severity!
timestamp: DateTime
title: String!
message: String!
timestamp: DateTime
}
type Api {
devices: [ApiDevice!]!
recoveryKey: ApiRecoveryKeyStatus!
version: String!
recoveryKey: ApiRecoveryKeyStatus!
devices: [ApiDevice!]!
}
type ApiDevice {
name: String!
creationDate: DateTime!
isCaller: Boolean!
name: String!
}
type ApiJob {
createdAt: DateTime!
description: String!
error: String
finishedAt: DateTime
uid: String!
typeId: String!
name: String!
progress: Int
result: String
description: String!
status: String!
statusText: String
uid: String!
progress: Int
createdAt: DateTime!
updatedAt: DateTime!
finishedAt: DateTime
error: String
result: String
}
type ApiKeyMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int!
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 {
creationDate: DateTime
exists: Boolean!
valid: Boolean!
creationDate: DateTime
expirationDate: DateTime
usesLeft: Int
valid: Boolean!
}
type AutoUpgradeOptions {
allowReboot: Boolean!
enable: Boolean!
allowReboot: Boolean!
}
input AutoUpgradeSettingsInput {
@ -57,53 +68,102 @@ input AutoUpgradeSettingsInput {
}
type AutoUpgradeSettingsMutationReturn implements MutationReturnInterface {
allowReboot: Boolean!
success: Boolean!
message: String!
code: Int!
enableAutoUpgrade: Boolean!
message: String!
success: Boolean!
allowReboot: 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)"""
scalar DateTime
type DeviceApiTokenMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean!
message: String!
code: Int!
token: String
}
enum DnsProvider {
CLOUDFLARE,
DESEC,
CLOUDFLARE
DIGITALOCEAN
DESEC
}
type DnsRecord {
content: String!
name: String!
priority: Int
recordType: String!
name: String!
content: String!
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!
job: ApiJob
message: String!
success: Boolean!
}
type GenericMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean!
message: String!
code: Int!
}
input InitializeRepositoryInput {
provider: BackupProvider!
locationId: String!
locationName: String!
login: String!
password: String!
}
type Job {
getJob(jobId: String!): ApiJob
getJobs: [ApiJob!]!
getJob(jobId: String!): ApiJob
}
type JobMutations {
removeJob(jobId: String!): GenericMutationReturn!
}
input MigrateToBindsInput {
@ -120,52 +180,60 @@ input MoveServiceInput {
}
type Mutation {
addSshKey(sshInput: SshMutationInput!): UserMutationReturn!
authorizeWithNewDeviceApiKey(input: UseNewDeviceKeyInput!): DeviceApiTokenMutationReturn!
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn!
changeTimezone(timezone: String!): TimezoneMutationReturn!
createUser(user: UserMutationInput!): UserMutationReturn!
deleteDeviceApiToken(device: String!): GenericMutationReturn!
deleteUser(username: String!): GenericMutationReturn!
disableService(serviceId: String!): ServiceMutationReturn!
enableService(serviceId: String!): ServiceMutationReturn!
getNewDeviceApiKey: ApiKeyMutationReturn!
getNewRecoveryApiKey(limits: RecoveryKeyLimitsInput = null): ApiKeyMutationReturn!
invalidateNewDeviceApiKey: GenericMutationReturn!
migrateToBinds(input: MigrateToBindsInput!): GenericJobButationReturn!
mountVolume(name: String!): GenericMutationReturn!
moveService(input: MoveServiceInput!): ServiceJobMutationReturn!
pullRepositoryChanges: GenericMutationReturn!
rebootSystem: GenericMutationReturn!
refreshDeviceApiToken: DeviceApiTokenMutationReturn!
removeJob(jobId: String!): GenericMutationReturn!
removeSshKey(sshInput: SshMutationInput!): UserMutationReturn!
resizeVolume(name: String!): GenericMutationReturn!
restartService(serviceId: String!): ServiceMutationReturn!
runSystemRebuild: GenericMutationReturn!
runSystemRollback: GenericMutationReturn!
runSystemUpgrade: GenericMutationReturn!
startService(serviceId: String!): ServiceMutationReturn!
stopService(serviceId: String!): ServiceMutationReturn!
getNewRecoveryApiKey(limits: RecoveryKeyLimitsInput = null): ApiKeyMutationReturn! @deprecated(reason: "Use `api.get_new_recovery_api_key` instead")
useRecoveryApiKey(input: UseRecoveryKeyInput!): DeviceApiTokenMutationReturn! @deprecated(reason: "Use `api.use_recovery_api_key` instead")
refreshDeviceApiToken: DeviceApiTokenMutationReturn! @deprecated(reason: "Use `api.refresh_device_api_token` instead")
deleteDeviceApiToken(device: String!): GenericMutationReturn! @deprecated(reason: "Use `api.delete_device_api_token` instead")
getNewDeviceApiKey: ApiKeyMutationReturn! @deprecated(reason: "Use `api.get_new_device_api_key` instead")
invalidateNewDeviceApiKey: GenericMutationReturn! @deprecated(reason: "Use `api.invalidate_new_device_api_key` instead")
authorizeWithNewDeviceApiKey(input: UseNewDeviceKeyInput!): DeviceApiTokenMutationReturn! @deprecated(reason: "Use `api.authorize_with_new_device_api_key` instead")
changeTimezone(timezone: String!): TimezoneMutationReturn! @deprecated(reason: "Use `system.change_timezone` instead")
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn! @deprecated(reason: "Use `system.change_auto_upgrade_settings` instead")
runSystemRebuild: GenericMutationReturn! @deprecated(reason: "Use `system.run_system_rebuild` instead")
runSystemRollback: GenericMutationReturn! @deprecated(reason: "Use `system.run_system_rollback` instead")
runSystemUpgrade: GenericMutationReturn! @deprecated(reason: "Use `system.run_system_upgrade` instead")
rebootSystem: GenericMutationReturn! @deprecated(reason: "Use `system.reboot_system` instead")
pullRepositoryChanges: GenericMutationReturn! @deprecated(reason: "Use `system.pull_repository_changes` instead")
createUser(user: UserMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.create_user` instead")
deleteUser(username: String!): GenericMutationReturn! @deprecated(reason: "Use `users.delete_user` instead")
updateUser(user: UserMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.update_user` instead")
addSshKey(sshInput: SshMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.add_ssh_key` instead")
removeSshKey(sshInput: SshMutationInput!): UserMutationReturn! @deprecated(reason: "Use `users.remove_ssh_key` instead")
resizeVolume(name: String!): GenericMutationReturn! @deprecated(reason: "Use `storage.resize_volume` instead")
mountVolume(name: String!): GenericMutationReturn! @deprecated(reason: "Use `storage.mount_volume` instead")
unmountVolume(name: String!): GenericMutationReturn! @deprecated(reason: "Use `storage.unmount_volume` instead")
migrateToBinds(input: MigrateToBindsInput!): GenericJobMutationReturn! @deprecated(reason: "Use `storage.migrate_to_binds` instead")
enableService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.enable_service` instead")
disableService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.disable_service` instead")
stopService(serviceId: String!): ServiceMutationReturn! @deprecated(reason: "Use `services.stop_service` instead")
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!
unmountVolume(name: String!): GenericMutationReturn!
updateUser(user: UserMutationInput!): UserMutationReturn!
useRecoveryApiKey(input: UseRecoveryKeyInput!): DeviceApiTokenMutationReturn!
}
interface MutationReturnInterface {
code: Int!
message: String!
success: Boolean!
message: String!
code: Int!
}
type Query {
api: Api!
jobs: Job!
services: Services!
storage: Storage!
system: System!
users: Users!
storage: Storage!
jobs: Job!
services: Services!
backup: Backup!
}
input RecoveryKeyLimitsInput {
@ -179,61 +247,79 @@ enum ServerProvider {
}
type Service {
description: String!
displayName: String!
dnsRecords: [DnsRecord!]
id: String!
isEnabled: Boolean!
displayName: String!
description: String!
svgIcon: String!
isMovable: Boolean!
isRequired: Boolean!
isEnabled: Boolean!
canBeBackedUp: Boolean!
backupDescription: String!
status: ServiceStatusEnum!
storageUsage: ServiceStorageUsage!
svgIcon: String!
url: String
dnsRecords: [DnsRecord!]
storageUsage: ServiceStorageUsage!
backupSnapshots: [SnapshotInfo!]
}
type ServiceJobMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int!
job: ApiJob
message: String!
service: Service
success: Boolean!
}
type ServiceMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
service: Service
success: Boolean!
message: String!
code: Int!
service: Service
}
enum ServiceStatusEnum {
ACTIVATING
ACTIVE
DEACTIVATING
FAILED
INACTIVE
OFF
RELOADING
INACTIVE
FAILED
ACTIVATING
DEACTIVATING
OFF
}
type ServiceStorageUsage implements StorageUsageInterface {
service: Service
title: String!
usedSpace: String!
volume: StorageVolume
title: String!
service: Service
}
type Services {
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 {
CRITICAL
ERROR
INFO
SUCCESS
WARNING
ERROR
CRITICAL
SUCCESS
}
type SnapshotInfo {
id: String!
service: Service!
createdAt: DateTime!
}
input SshMutationInput {
@ -251,22 +337,29 @@ type Storage {
volumes: [StorageVolume!]!
}
type StorageMutations {
resizeVolume(name: String!): GenericMutationReturn!
mountVolume(name: String!): GenericMutationReturn!
unmountVolume(name: String!): GenericMutationReturn!
migrateToBinds(input: MigrateToBindsInput!): GenericJobMutationReturn!
}
interface StorageUsageInterface {
title: String!
usedSpace: String!
volume: StorageVolume
title: String!
}
type StorageVolume {
freeSpace: String!
model: String
name: String!
root: Boolean!
serial: String
totalSpace: String!
freeSpace: String!
usedSpace: String!
root: Boolean!
name: String!
model: String
serial: String
type: String!
usages: [StorageUsageInterface!]!
usedSpace: String!
}
type Subscription {
@ -274,12 +367,12 @@ type Subscription {
}
type System {
busy: Boolean!
status: Alert!
domainInfo: SystemDomainInfo!
settings: SystemSettings!
info: SystemInfo!
provider: SystemProviderInfo!
settings: SystemSettings!
status: Alert!
busy: Boolean!
workingDirectory: String!
}
@ -291,14 +384,24 @@ type SystemDomainInfo {
}
type SystemInfo {
pythonVersion: String!
systemVersion: String!
pythonVersion: String!
usingBinds: Boolean!
}
type SystemMutations {
changeTimezone(timezone: String!): TimezoneMutationReturn!
changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn!
runSystemRebuild: GenericMutationReturn!
runSystemRollback: GenericMutationReturn!
runSystemUpgrade: GenericMutationReturn!
rebootSystem: GenericMutationReturn!
pullRepositoryChanges: GenericMutationReturn!
}
type SystemProviderInfo {
id: String!
provider: ServerProvider!
id: String!
}
type SystemSettings {
@ -308,9 +411,9 @@ type SystemSettings {
}
type TimezoneMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean!
message: String!
code: Int!
timezone: String
}
@ -325,9 +428,9 @@ input UseRecoveryKeyInput {
}
type User {
sshKeys: [String!]!
userType: UserType!
username: String!
sshKeys: [String!]!
}
input UserMutationInput {
@ -336,9 +439,9 @@ input UserMutationInput {
}
type UserMutationReturn implements MutationReturnInterface {
code: Int!
message: String!
success: Boolean!
message: String!
code: Int!
user: User
}
@ -353,10 +456,10 @@ type Users {
getUser(username: String!): User
}
fragment dnsRecordFields on DnsRecord {
content
name
priority
recordType
ttl
}
type UsersMutations {
createUser(user: UserMutationInput!): UserMutationReturn!
deleteUser(username: String!): GenericMutationReturn!
updateUser(user: UserMutationInput!): UserMutationReturn!
addSshKey(sshInput: SshMutationInput!): UserMutationReturn!
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';
class Input$AutoUpgradeSettingsInput {
@ -143,6 +141,190 @@ class _CopyWithStubImpl$Input$AutoUpgradeSettingsInput<TRes>
_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 {
factory Input$MigrateToBindsInput({
required String emailBlockDevice,
@ -1096,16 +1278,48 @@ class _CopyWithStubImpl$Input$UserMutationInput<TRes>
_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) {
switch (e) {
case Enum$DnsProvider.CLOUDFLARE:
return r'CLOUDFLARE';
case Enum$DnsProvider.DESEC:
return r'DESEC';
case Enum$DnsProvider.DIGITALOCEAN:
return r'DIGITALOCEAN';
case Enum$DnsProvider.DESEC:
return r'DESEC';
case Enum$DnsProvider.$unknown:
return r'$unknown';
}
@ -1115,10 +1329,10 @@ Enum$DnsProvider fromJson$Enum$DnsProvider(String value) {
switch (value) {
case r'CLOUDFLARE':
return Enum$DnsProvider.CLOUDFLARE;
case r'DESEC':
return Enum$DnsProvider.DESEC;
case r'DIGITALOCEAN':
return Enum$DnsProvider.DIGITALOCEAN;
case r'DESEC':
return Enum$DnsProvider.DESEC;
default:
return Enum$DnsProvider.$unknown;
}
@ -1149,32 +1363,32 @@ Enum$ServerProvider fromJson$Enum$ServerProvider(String value) {
}
enum Enum$ServiceStatusEnum {
ACTIVATING,
ACTIVE,
DEACTIVATING,
FAILED,
INACTIVE,
OFF,
RELOADING,
INACTIVE,
FAILED,
ACTIVATING,
DEACTIVATING,
OFF,
$unknown
}
String toJson$Enum$ServiceStatusEnum(Enum$ServiceStatusEnum e) {
switch (e) {
case Enum$ServiceStatusEnum.ACTIVATING:
return r'ACTIVATING';
case Enum$ServiceStatusEnum.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:
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:
return r'$unknown';
}
@ -1182,39 +1396,39 @@ String toJson$Enum$ServiceStatusEnum(Enum$ServiceStatusEnum e) {
Enum$ServiceStatusEnum fromJson$Enum$ServiceStatusEnum(String value) {
switch (value) {
case r'ACTIVATING':
return Enum$ServiceStatusEnum.ACTIVATING;
case r'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':
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:
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) {
switch (e) {
case Enum$Severity.CRITICAL:
return r'CRITICAL';
case Enum$Severity.ERROR:
return r'ERROR';
case Enum$Severity.INFO:
return r'INFO';
case Enum$Severity.SUCCESS:
return r'SUCCESS';
case Enum$Severity.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:
return r'$unknown';
}
@ -1222,16 +1436,16 @@ String toJson$Enum$Severity(Enum$Severity e) {
Enum$Severity fromJson$Enum$Severity(String value) {
switch (value) {
case r'CRITICAL':
return Enum$Severity.CRITICAL;
case r'ERROR':
return Enum$Severity.ERROR;
case r'INFO':
return Enum$Severity.INFO;
case r'SUCCESS':
return Enum$Severity.SUCCESS;
case r'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:
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>>{
'MutationReturnInterface': {
'ApiKeyMutationReturn',
'AutoUpgradeSettingsMutationReturn',
'DeviceApiTokenMutationReturn',
'GenericJobButationReturn',
'GenericBackupConfigReturn',
'GenericJobMutationReturn',
'GenericMutationReturn',
'ServiceJobMutationReturn',
'ServiceMutationReturn',

View File

@ -4,6 +4,21 @@ fragment basicMutationReturnFields on MutationReturnInterface{
success
}
fragment basicApiJobsFields on ApiJob{
createdAt
description
error
finishedAt
name
progress
result
status
statusText
uid
typeId
updatedAt
}
query GetApiVersion {
api {
version
@ -13,17 +28,7 @@ query GetApiVersion {
query GetApiJobs {
jobs {
getJobs {
createdAt
description
error
finishedAt
name
progress
result
status
statusText
uid
updatedAt
...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 SystemSettings {
system {
settings {
@ -28,6 +22,14 @@ query SystemIsUsingBinds {
}
}
fragment fragmentDnsRecords on DnsRecord {
recordType
name
content
ttl
priority
}
query DomainInfo {
system {
domainInfo {
@ -35,7 +37,7 @@ query DomainInfo {
hostname
provider
requiredDnsRecords {
...dnsRecordFields
...fragmentDnsRecords
}
}
}

View File

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

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{
username
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/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/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/schema.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/users.graphql.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_domain.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/backup.dart';
import 'package:selfprivacy/logic/models/backup.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/recovery_token_status.dart';
@ -31,9 +32,16 @@ part 'server_actions_api.dart';
part 'services_api.dart';
part 'users_api.dart';
part 'volume_api.dart';
part 'backups_api.dart';
class ServerApi extends GraphQLApiMap
with VolumeApi, JobsApi, ServerActionsApi, ServicesApi, UsersApi {
with
VolumeApi,
JobsApi,
ServerActionsApi,
ServicesApi,
UsersApi,
BackupsApi {
ServerApi({
this.hasLogger = false,
this.isWithToken = true,
@ -288,8 +296,10 @@ class ServerApi extends GraphQLApiMap
}
records = response.parsedData!.system.domainInfo.requiredDnsRecords
.map<DnsRecord>(
(final Fragment$dnsRecordFields fragment) =>
DnsRecord.fromGraphQL(fragment),
(
final Fragment$fragmentDnsRecords record,
) =>
DnsRecord.fromGraphQL(record),
)
.toList();
} catch (e) {
@ -509,22 +519,4 @@ class ServerApi extends GraphQLApiMap
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: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/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
export 'package:selfprivacy/logic/api_maps/generic_result.dart';
@ -36,7 +36,7 @@ class BackblazeApi extends RestApiMap {
responseType: ResponseType.json,
);
if (isWithToken) {
final BackblazeCredential? backblazeCredential =
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
final String token = backblazeCredential!.applicationKey;
options.headers = {'Authorization': 'Basic $token'};
@ -56,7 +56,7 @@ class BackblazeApi extends RestApiMap {
Future<BackblazeApiAuth> getAuthorizationToken() async {
final Dio client = await getClient();
final BackblazeCredential? backblazeCredential =
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
if (backblazeCredential == null) {
throw Exception('Backblaze credential is null');
@ -121,7 +121,7 @@ class BackblazeApi extends RestApiMap {
// Create bucket
Future<String> createBucket(final String bucketName) async {
final BackblazeApiAuth auth = await getAuthorizationToken();
final BackblazeCredential? backblazeCredential =
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
final Dio client = await getClient();
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/cubit/app_config_dependent/authentication_dependend_cubit.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';
@ -24,107 +27,85 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
Future<void> load() async {
if (serverInstallationCubit.state is ServerInstallationFinished) {
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
if (bucket == null) {
emit(
const BackupsState(
isInitialized: false,
preventActions: false,
refreshing: false,
),
);
} else {
final BackupStatus status = await api.getBackupStatus();
switch (status.status) {
case BackupStatusEnum.noKey:
case BackupStatusEnum.notInitialized:
emit(
BackupsState(
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));
}
final BackupConfiguration? backupConfig =
await api.getBackupsConfiguration();
final List<Backup> backups = await api.getBackups();
backups.sort((final a, final b) => b.time.compareTo(a.time));
emit(
state.copyWith(
backblazeBucket: bucket,
isInitialized: backupConfig?.isInitialized,
autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero,
backups: backups,
preventActions: false,
refreshing: false,
),
);
print(state);
}
}
Future<void> createBucket() async {
Future<void> initializeBackups() async {
emit(state.copyWith(preventActions: true));
final String domain = serverInstallationCubit.state.serverDomain!.domainName
.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? encryptionKey =
(await api.getBackupsConfiguration())?.encryptionKey;
if (encryptionKey == null) {
getIt<NavigationService>()
.showSnackBar("Couldn't get encryption key from your server.");
emit(state.copyWith(preventActions: false));
return;
}
final String bucketId = await backblaze.createBucket(bucketName);
final BackblazeApplicationKey key = await backblaze.createKey(bucketId);
final BackblazeBucket bucket = BackblazeBucket(
bucketId: bucketId,
bucketName: bucketName,
applicationKey: key.applicationKey,
applicationKeyId: key.applicationKeyId,
final BackblazeBucket bucket;
if (state.backblazeBucket == null) {
final String domain = serverInstallationCubit
.state.serverDomain!.domainName
.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);
await api.uploadBackblazeConfig(bucket);
await updateBackups();
emit(state.copyWith(isInitialized: true, preventActions: false));
emit(state.copyWith(preventActions: false));
}
Future<void> reuploadKey() async {
@ -132,37 +113,48 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
if (bucket == null) {
emit(state.copyWith(isInitialized: false));
print('bucket is null');
} else {
await api.uploadBackblazeConfig(bucket);
emit(state.copyWith(isInitialized: true, preventActions: false));
getIt<NavigationService>().showSnackBar('backup.reuploaded_key');
print('bucket is not null');
final GenericResult result = await api.initializeRepository(
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) {
switch (status) {
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);
}
}
@Deprecated("we don't have states")
Duration refreshTimeFromState() => const Duration(seconds: 60);
Future<void> updateBackups({final bool useTimer = false}) async {
emit(state.copyWith(refreshing: true));
final List<Backup> backups = await api.getBackups();
final BackupStatus status = await api.getBackupStatus();
final backups = await api.getBackups();
backups.sort((final a, final b) => b.time.compareTo(a.time));
final backupConfig = await api.getBackupsConfiguration();
emit(
state.copyWith(
backups: backups,
progress: status.progress,
status: status.status,
error: status.errorMessage,
refreshTimer: refreshTimeFromState(status.status),
refreshTimer: refreshTimeFromState(),
refreshing: false,
isInitialized: backupConfig?.isInitialized ?? false,
autobackupPeriod: backupConfig?.autobackupPeriod,
),
);
if (useTimer) {
@ -172,14 +164,23 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
Future<void> forceUpdateBackups() async {
emit(state.copyWith(preventActions: true));
await api.forceBackupListReload();
getIt<NavigationService>().showSnackBar('backup.refetching_list'.tr());
await api.forceBackupListReload();
emit(state.copyWith(preventActions: false));
}
Future<void> createBackup() async {
Future<void> createMultipleBackups(final List<Service> services) async {
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();
emit(state.copyWith(preventActions: false));
}
@ -190,6 +191,26 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
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
void clear() async {
emit(const BackupsState());

View File

@ -4,53 +4,54 @@ class BackupsState extends ServerInstallationDependendState {
const BackupsState({
this.isInitialized = false,
this.backups = const [],
this.progress = 0.0,
this.status = BackupStatusEnum.noKey,
this.preventActions = true,
this.error = '',
this.refreshTimer = const Duration(seconds: 60),
this.refreshing = true,
this.autobackupPeriod,
this.backblazeBucket,
});
final bool isInitialized;
final List<Backup> backups;
final double progress;
final BackupStatusEnum status;
final bool preventActions;
final String error;
final Duration refreshTimer;
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
List<Object> get props => [
isInitialized,
backups,
progress,
preventActions,
status,
error,
refreshTimer,
refreshing
refreshing,
];
BackupsState copyWith({
final bool? isInitialized,
final List<Backup>? backups,
final double? progress,
final BackupStatusEnum? status,
final bool? preventActions,
final String? error,
final Duration? refreshTimer,
final bool? refreshing,
final Duration? autobackupPeriod,
final BackblazeBucket? backblazeBucket,
}) =>
BackupsState(
isInitialized: isInitialized ?? this.isInitialized,
backups: backups ?? this.backups,
progress: progress ?? this.progress,
status: status ?? this.status,
preventActions: preventActions ?? this.preventActions,
error: error ?? this.error,
refreshTimer: refreshTimer ?? this.refreshTimer,
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/logic/api_maps/rest_maps/backblaze.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';
class BackblazeFormCubit extends FormCubit {

View File

@ -5,20 +5,19 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.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/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/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_domain.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/models/server_basic_info.dart';
import 'package:selfprivacy/logic/models/server_provider_location.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';
export 'package:provider/provider.dart';
@ -195,9 +194,10 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
}
void setBackblazeKey(final String keyId, final String applicationKey) async {
final BackblazeCredential backblazeCredential = BackblazeCredential(
final BackupsCredential backblazeCredential = BackupsCredential(
keyId: keyId,
applicationKey: applicationKey,
provider: BackupsProviderType.backblaze,
);
await repository.saveBackblazeKey(backblazeCredential);
if (state is ServerInstallationRecovery) {
@ -699,7 +699,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
provider: dnsProviderType,
),
);
await repository.setDnsApiToken(token);
// await repository.setDnsApiToken(token);
emit(
dataState.copyWith(
serverDomain: ServerDomain(
@ -714,7 +714,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
}
void finishRecoveryProcess(
final BackblazeCredential backblazeCredential,
final BackupsCredential backblazeCredential,
) async {
await repository.saveIsServerStarted(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/tls_options.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_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
@ -46,7 +46,7 @@ class ServerInstallationRepository {
final DnsProviderType? dnsProvider = getIt<ApiConfigModel>().dnsProvider;
final ServerProviderType? serverProvider =
getIt<ApiConfigModel>().serverProvider;
final BackblazeCredential? backblazeCredential =
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
final ServerHostingDetails? serverDetails =
getIt<ApiConfigModel>().serverDetails;
@ -170,12 +170,14 @@ class ServerInstallationRepository {
Future<String?> getDomainId(final String token, final String domain) async {
final result =
await ProvidersController.currentDnsProvider!.tryInitApiByToken(token);
return result.success
? (await ProvidersController.currentDnsProvider!.getZoneId(
domain,
))
.data
: null;
if (!result.success) {
return null;
}
await setDnsApiToken(token);
return (await ProvidersController.currentDnsProvider!.getZoneId(
domain,
))
.data;
}
Future<Map<String, bool>> isDnsAddressesMatch(
@ -519,7 +521,7 @@ class ServerInstallationRepository {
}
Future<void> saveBackblazeKey(
final BackblazeCredential backblazeCredential,
final BackupsCredential backblazeCredential,
) async {
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
}

View File

@ -32,7 +32,7 @@ abstract class ServerInstallationState extends Equatable {
final String? providerApiToken;
final String? dnsApiToken;
final String? serverTypeIdentificator;
final BackblazeCredential? backblazeCredential;
final BackupsCredential? backblazeCredential;
final ServerDomain? serverDomain;
final User? rootUser;
final ServerHostingDetails? serverDetails;
@ -167,7 +167,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
final String? providerApiToken,
final String? serverTypeIdentificator,
final String? dnsApiToken,
final BackblazeCredential? backblazeCredential,
final BackupsCredential? backblazeCredential,
final ServerDomain? serverDomain,
final User? rootUser,
final ServerHostingDetails? serverDetails,
@ -237,7 +237,7 @@ class ServerInstallationFinished extends ServerInstallationState {
required String super.providerApiToken,
required String super.serverTypeIdentificator,
required String super.dnsApiToken,
required BackblazeCredential super.backblazeCredential,
required BackupsCredential super.backblazeCredential,
required ServerDomain super.serverDomain,
required User super.rootUser,
required ServerHostingDetails super.serverDetails,
@ -324,7 +324,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
final String? providerApiToken,
final String? serverTypeIdentificator,
final String? dnsApiToken,
final BackblazeCredential? backblazeCredential,
final BackupsCredential? backblazeCredential,
final ServerDomain? serverDomain,
final User? rootUser,
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(
(final job) =>
job.status == JobStatusEnum.finished ||

View File

@ -12,40 +12,15 @@ class ServicesState extends ServerInstallationDependendState {
final List<Service> services;
final List<String> lockedServices;
List<Service> get servicesThatCanBeBackedUp => services
.where(
(final service) => service.canBeBackedUp,
)
.toList();
bool isServiceLocked(final String 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) {
final service = services.firstWhere(
(final service) => service.id == id,
@ -63,23 +38,6 @@ class ServicesState extends ServerInstallationDependendState {
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({
final List<Service>? services,
final List<String>? lockedServices,

View File

@ -1,7 +1,7 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.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_domain.dart';
@ -15,7 +15,8 @@ class ApiConfigModel {
String? get dnsProviderKey => _dnsProviderKey;
ServerProviderType? get serverProvider => _serverProvider;
DnsProviderType? get dnsProvider => _dnsProvider;
BackblazeCredential? get backblazeCredential => _backblazeCredential;
BackupsCredential? get backblazeCredential => _backblazeCredential;
ServerDomain? get serverDomain => _serverDomain;
BackblazeBucket? get backblazeBucket => _backblazeBucket;
@ -26,7 +27,7 @@ class ApiConfigModel {
ServerProviderType? _serverProvider;
DnsProviderType? _dnsProvider;
ServerHostingDetails? _serverDetails;
BackblazeCredential? _backblazeCredential;
BackupsCredential? _backblazeCredential;
ServerDomain? _serverDomain;
BackblazeBucket? _backblazeBucket;
@ -60,7 +61,7 @@ class ApiConfigModel {
_serverLocation = serverLocation;
}
Future<void> storeBackblazeCredential(final BackblazeCredential value) async {
Future<void> storeBackblazeCredential(final BackupsCredential value) async {
await _box.put(BNames.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
101. ServerProvider
102. UserType
103. BackupsProvider

View File

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

View File

@ -21,13 +21,14 @@ class BackblazeBucketAdapter extends TypeAdapter<BackblazeBucket> {
bucketName: fields[3] as String,
applicationKeyId: fields[1] as String,
applicationKey: fields[2] as String,
encryptionKey: fields[4] as String,
);
}
@override
void write(BinaryWriter writer, BackblazeBucket obj) {
writer
..writeByte(4)
..writeByte(5)
..writeByte(0)
..write(obj.bucketId)
..writeByte(1)
@ -35,7 +36,9 @@ class BackblazeBucketAdapter extends TypeAdapter<BackblazeBucket> {
..writeByte(2)
..write(obj.applicationKey)
..writeByte(3)
..write(obj.bucketName);
..write(obj.bucketName)
..writeByte(4)
..write(obj.encryptionKey);
}
@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: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';
@ -16,7 +16,7 @@ class DnsRecord {
});
DnsRecord.fromGraphQL(
final Fragment$dnsRecordFields record,
final Fragment$fragmentDnsRecords record,
) : this(
type: record.recordType,
name: record.name,

View File

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

View File

@ -11,6 +11,7 @@ ServerJob _$ServerJobFromJson(Map<String, dynamic> json) => ServerJob(
description: json['description'] as String,
status: $enumDecode(_$JobStatusEnumEnumMap, json['status']),
uid: json['uid'] as String,
typeId: json['typeId'] as String,
updatedAt: DateTime.parse(json['updatedAt'] as String),
createdAt: DateTime.parse(json['createdAt'] as String),
error: json['error'] as String?,
@ -27,6 +28,7 @@ Map<String, dynamic> _$ServerJobToJson(ServerJob instance) => <String, dynamic>{
'description': instance.description,
'status': _$JobStatusEnumEnumMap[instance.status]!,
'uid': instance.uid,
'typeId': instance.typeId,
'updatedAt': instance.updatedAt.toIso8601String(),
'createdAt': instance.createdAt.toIso8601String(),
'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/json/dns_records.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
class Service {
Service.fromGraphQL(final Query$AllServices$services$allServices service)
: this(
@ -15,6 +17,8 @@ class Service {
isEnabled: service.isEnabled,
isRequired: service.isRequired,
isMovable: service.isMovable,
canBeBackedUp: service.canBeBackedUp,
backupDescription: service.backupDescription,
status: ServiceStatus.fromGraphQL(service.status),
storageUsage: ServiceStorageUsage(
used: DiskSize(byte: int.parse(service.storageUsage.usedSpace)),
@ -24,7 +28,9 @@ class Service {
svgIcon: utf8.decode(base64.decode(service.svgIcon)),
dnsRecords: service.dnsRecords
?.map(
(final Fragment$dnsRecordFields record) =>
(
final Fragment$fragmentDnsRecords record,
) =>
DnsRecord.fromGraphQL(record),
)
.toList() ??
@ -38,6 +44,8 @@ class Service {
required this.isEnabled,
required this.isRequired,
required this.isMovable,
required this.canBeBackedUp,
required this.backupDescription,
required this.status,
required this.storageUsage,
required this.svgIcon,
@ -71,6 +79,8 @@ class Service {
isEnabled: false,
isRequired: false,
isMovable: false,
canBeBackedUp: false,
backupDescription: '',
status: ServiceStatus.off,
storageUsage: ServiceStorageUsage(
used: const DiskSize(byte: 0),
@ -87,6 +97,8 @@ class Service {
final bool isEnabled;
final bool isRequired;
final bool isMovable;
final bool canBeBackedUp;
final String backupDescription;
final ServiceStatus status;
final ServiceStorageUsage storageUsage;
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';
abstract class DnsProvider {
/// Returns an assigned enum value, respectively to which
/// provider implements [DnsProvider] interface.
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<String?>> getZoneId(final String domain);
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';
class JobsContent extends StatelessWidget {
const JobsContent({super.key});
const JobsContent({
required this.controller,
super.key,
});
final ScrollController controller;
@override
Widget build(final BuildContext context) {
@ -119,9 +124,10 @@ class JobsContent extends StatelessWidget {
];
}
return ListView(
controller: controller,
padding: paddingH15V0,
children: [
const SizedBox(height: 15),
const SizedBox(height: 16),
Center(
child: Text(
'jobs.title'.tr(),

View File

@ -62,7 +62,16 @@ class _BrandFabState extends State<BrandFab>
// TODO: Make a hero animation to the screen
showModalBottomSheet(
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,

View File

@ -145,7 +145,17 @@ class _HeroSliverAppBarState extends State<HeroSliverAppBar> {
onPressed: () {
showModalBottomSheet(
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(

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(
title: Text('developer_settings.ignore_tls'.tr()),
subtitle:
Text('developer_settings.ignore_tls_description'.tr()),
subtitle: Text('developer_settings.ignore_tls_description'.tr()),
value: TlsOptions.verifyCertificate,
onChanged: (final bool value) => setState(
() => TlsOptions.verifyCertificate = value,
() => TlsOptions.verifyCertificate = value,
),
),
Padding(

View File

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

View File

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

View File

@ -177,7 +177,17 @@ class _ServicesMigrationPageState extends State<ServicesMigrationPage> {
context.router.popUntilRoot();
showModalBottomSheet(
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,
),
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:selfprivacy/logic/models/disk_status.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/dns_details/dns_details.dart';
import 'package:selfprivacy/ui/pages/more/about_application.dart';
@ -96,6 +97,7 @@ class RootRouter extends _$RootRouter {
AutoRoute(page: ServerDetailsRoute.page),
AutoRoute(page: DnsDetailsRoute.page),
AutoRoute(page: BackupDetailsRoute.page),
AutoRoute(page: BackupsListRoute.page),
AutoRoute(page: ServerStorageRoute.page),
AutoRoute(page: ExtendingVolumeRoute.page),
],
@ -141,6 +143,8 @@ String getRouteTitle(final String routeName) {
return 'server.card_title';
case 'BackupDetailsRoute':
return 'backup.card_title';
case 'BackupsListRoute':
return 'backup.snapshots_title';
case 'ServerStorageRoute':
return 'storage.card_title';
case 'ExtendingVolumeRoute':

View File

@ -15,16 +15,103 @@ abstract class _$RootRouter extends RootStackRouter {
@override
final Map<String, PageFactory> pagesMap = {
BackupDetailsRoute.name: (routeData) {
DevicesRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const BackupDetailsPage(),
child: const DevicesScreen(),
);
},
RootRoute.name: (routeData) {
DnsDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
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) {
@ -43,10 +130,16 @@ abstract class _$RootRouter extends RootStackRouter {
child: const ServicesPage(),
);
},
ServerDetailsRoute.name: (routeData) {
InitializingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ServerDetailsScreen(),
child: const InitializingPage(),
);
},
RecoveryRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryRouting(),
);
},
UsersRoute.name: (routeData) {
@ -71,274 +164,59 @@ abstract class _$RootRouter extends RootStackRouter {
),
);
},
AppSettingsRoute.name: (routeData) {
RootRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AppSettingsPage(),
child: WrappedRoute(child: const RootPage()),
);
},
DeveloperSettingsRoute.name: (routeData) {
BackupDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DeveloperSettingsPage(),
child: const BackupDetailsPage(),
);
},
MoreRoute.name: (routeData) {
BackupsListRoute.name: (routeData) {
final args = routeData.argsAs<BackupsListRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const MorePage(),
);
},
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,
child: BackupsListPage(
service: args.service,
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
/// [BackupDetailsPage]
class BackupDetailsRoute extends PageRouteInfo<void> {
const BackupDetailsRoute({List<PageRouteInfo>? children})
/// [DevicesScreen]
class DevicesRoute extends PageRouteInfo<void> {
const DevicesRoute({List<PageRouteInfo>? children})
: super(
BackupDetailsRoute.name,
DevicesRoute.name,
initialChildren: children,
);
static const String name = 'BackupDetailsRoute';
static const String name = 'DevicesRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [RootPage]
class RootRoute extends PageRouteInfo<void> {
const RootRoute({List<PageRouteInfo>? children})
/// [DnsDetailsPage]
class DnsDetailsRoute extends PageRouteInfo<void> {
const DnsDetailsRoute({List<PageRouteInfo>? children})
: super(
RootRoute.name,
DnsDetailsRoute.name,
initialChildren: children,
);
static const String name = 'RootRoute';
static const String name = 'DnsDetailsRoute';
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
/// [AppSettingsPage]
class AppSettingsRoute extends PageRouteInfo<void> {
@ -367,20 +245,6 @@ class DeveloperSettingsRoute extends PageRouteInfo<void> {
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
/// [AboutApplicationPage]
class AboutApplicationRoute extends PageRouteInfo<void> {
@ -395,6 +259,20 @@ class AboutApplicationRoute extends PageRouteInfo<void> {
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
/// [ConsolePage]
class ConsoleRoute extends PageRouteInfo<void> {
@ -409,6 +287,20 @@ class ConsoleRoute extends PageRouteInfo<void> {
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
/// [ProvidersPage]
class ProvidersRoute extends PageRouteInfo<void> {
@ -438,45 +330,65 @@ class RecoveryKeyRoute extends PageRouteInfo<void> {
}
/// generated route for
/// [DnsDetailsPage]
class DnsDetailsRoute extends PageRouteInfo<void> {
const DnsDetailsRoute({List<PageRouteInfo>? children})
/// [ServerDetailsScreen]
class ServerDetailsRoute extends PageRouteInfo<void> {
const ServerDetailsRoute({List<PageRouteInfo>? children})
: super(
DnsDetailsRoute.name,
ServerDetailsRoute.name,
initialChildren: children,
);
static const String name = 'DnsDetailsRoute';
static const String name = 'ServerDetailsRoute';
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,
/// [ServicesMigrationPage]
class ServicesMigrationRoute extends PageRouteInfo<ServicesMigrationRouteArgs> {
ServicesMigrationRoute({
required List<Service> services,
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,
);
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
/// [InitializingPage]
class InitializingRoute extends PageRouteInfo<void> {
const InitializingRoute({List<PageRouteInfo>? children})
: super(
InitializingRoute.name,
initialChildren: children,
);
class ServicesMigrationRouteArgs {
const ServicesMigrationRouteArgs({
required this.services,
required this.diskStatus,
required this.isMigration,
this.key,
});
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
@ -561,77 +473,213 @@ class ExtendingVolumeRouteArgs {
}
/// generated route for
/// [ServicesMigrationPage]
class ServicesMigrationRoute extends PageRouteInfo<ServicesMigrationRouteArgs> {
ServicesMigrationRoute({
required List<Service> services,
required DiskStatus diskStatus,
required bool isMigration,
/// [ServicePage]
class ServiceRoute extends PageRouteInfo<ServiceRouteArgs> {
ServiceRoute({
required String serviceId,
Key? key,
List<PageRouteInfo>? children,
}) : super(
ServicesMigrationRoute.name,
args: ServicesMigrationRouteArgs(
services: services,
diskStatus: diskStatus,
isMigration: isMigration,
ServiceRoute.name,
args: ServiceRouteArgs(
serviceId: serviceId,
key: key,
),
initialChildren: children,
);
static const String name = 'ServicesMigrationRoute';
static const String name = 'ServiceRoute';
static const PageInfo<ServicesMigrationRouteArgs> page =
PageInfo<ServicesMigrationRouteArgs>(name);
static const PageInfo<ServiceRouteArgs> page =
PageInfo<ServiceRouteArgs>(name);
}
class ServicesMigrationRouteArgs {
const ServicesMigrationRouteArgs({
required this.services,
required this.diskStatus,
required this.isMigration,
class ServiceRouteArgs {
const ServiceRouteArgs({
required this.serviceId,
this.key,
});
final List<Service> services;
final DiskStatus diskStatus;
final bool isMigration;
final String serviceId;
final Key? key;
@override
String toString() {
return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}';
return 'ServiceRouteArgs{serviceId: $serviceId, key: $key}';
}
}
/// generated route for
/// [DevicesScreen]
class DevicesRoute extends PageRouteInfo<void> {
const DevicesRoute({List<PageRouteInfo>? children})
/// [ServicesPage]
class ServicesRoute extends PageRouteInfo<void> {
const ServicesRoute({List<PageRouteInfo>? children})
: super(
DevicesRoute.name,
ServicesRoute.name,
initialChildren: children,
);
static const String name = 'DevicesRoute';
static const String name = 'ServicesRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [OnboardingPage]
class OnboardingRoute extends PageRouteInfo<void> {
const OnboardingRoute({List<PageRouteInfo>? children})
/// [InitializingPage]
class InitializingRoute extends PageRouteInfo<void> {
const InitializingRoute({List<PageRouteInfo>? children})
: super(
OnboardingRoute.name,
InitializingRoute.name,
initialChildren: children,
);
static const String name = 'OnboardingRoute';
static const String name = 'InitializingRoute';
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
extension DurationFormatter on Duration {
String toDayHourMinuteSecondFormat() => [
this.inHours.remainder(24),
this.inMinutes.remainder(60),
this.inSeconds.remainder(60)
].map((final int seg) => seg.toString().padLeft(2, '0')).join(':');
import 'dart:ui';
String toDayHourMinuteFormat() {
import 'package:duration/duration.dart';
import 'package:duration/locale.dart';
extension DurationFormatter on Duration {
String toTimezoneOffsetFormat() {
final designator = this >= Duration.zero ? '+' : '-';
final Iterable<String> segments = [
@ -18,15 +17,10 @@ extension DurationFormatter on Duration {
return '$designator${segments.first}:${segments.last}';
}
// WAT: https://flutterigniter.com/how-to-format-duration/
String toHoursMinutesSecondsFormat() =>
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';
}
String toPrettyString(final Locale locale) =>
prettyDuration(this, locale: getDurationLocale(locale));
}
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(
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"
source: hosted
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:
dependency: "direct main"
description:

View File

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