feat(backups): Allow to change snapshot rotation settings #332

Merged
inex merged 2 commits from rotation-settings into master 2023-09-10 11:20:19 +03:00
13 changed files with 1980 additions and 310 deletions

View File

@ -227,6 +227,64 @@
"explicit": "Created by your explicit request", "explicit": "Created by your explicit request",
"pre_restore": "Created as a precaution before risky restore", "pre_restore": "Created as a precaution before risky restore",
"unknown": "Unknown" "unknown": "Unknown"
},
"rotation_quotas_title": "Snapshot rotation settings",
"set_rotation_quotas": "Set new rotation quotas",
"quotas_set": "New backup rotation quotas set",
"quota_titles": {
"last": "How many latest backups to keep",
"daily": "How many daily backups to keep",
"weekly": "How many weekly backups to keep",
"monthly": "How many monthly backups to keep",
"yearly": "How many yearly backups to keep"
},
"quota_subtitles": {
"no_effect": "This rule has no effect because another rule will keep more backups",
"last": {
"zero": "Rule is disabled",
"one": "Last {} backup will be kept regardless of its age",
"two": "Last {} backups will be kept regardless of their age",
"few": "Last {} backups will be kept regardless of their age",
"many": "Last {} backups will be kept regardless of their age",
"other": "Last {} backups will be kept regardless of their age"
},
"last_infinite": "All backups will be kept",
"daily": {
"zero": "Rule is disabled",
"one": "Last {} daily backup will be kept",
"two": "Last {} daily backups will be kept",
"few": "Last {} daily backups will be kept",
"many": "Last {} daily backups will be kept",
"other": "Last {} daily backups will be kept"
},
"daily_infinite": "All daily backups will be kept",
"weekly": {
"zero": "Rule is disabled",
"one": "Last {} weekly backup will be kept",
"two": "Last {} weekly backups will be kept",
"few": "Last {} weekly backups will be kept",
"many": "Last {} weekly backups will be kept",
"other": "Last {} weekly backups will be kept"
},
"weekly_infinite": "All weekly backups will be kept",
"monthly": {
"zero": "Rule is disabled",
"one": "Last {} monthly backup will be kept",
"two": "Last {} monthly backups will be kept",
"few": "Last {} monthly backups will be kept",
"many": "Last {} monthly backups will be kept",
"other": "Last {} monthly backups will be kept"
},
"monthly_infinite": "All monthly backups will be kept",
"yearly": {
"zero": "Rule is disabled",
"one": "Last {} yearly backup will be kept",
"two": "Last {} yearly backups will be kept",
"few": "Last {} yearly backups will be kept",
"many": "Last {} yearly backups will be kept",
"other": "Last {} yearly backups will be kept"
},
"yearly_infinite": "All yearly backups will be kept"
} }
}, },
"storage": { "storage": {

View File

@ -66,16 +66,16 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1662096612, "lastModified": 1693250523,
"narHash": "sha256-R+Q8l5JuyJryRPdiIaYpO5O3A55rT+/pItBrKcy7LM4=", "narHash": "sha256-y3up5gXMTbnCsXrNEB5j+7TVantDLUYyQLu/ueiXuyg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "21de2b973f9fee595a7a1ac4693efff791245c34", "rev": "3efb0f6f404ec8dae31bdb1a9b17705ce0d6986e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
"ref": "nixpkgs-unstable", "ref": "nixos-unstable",
"type": "indirect" "type": "indirect"
} }
}, },

View File

@ -1,7 +1,7 @@
{ {
nixConfig.bash-prompt = "\[selfprivacy\]$ "; nixConfig.bash-prompt = "\[selfprivacy\]$ ";
inputs.nixpkgs.url = "nixpkgs/nixpkgs-unstable"; inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils"; inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.nixgl.url = "github:guibou/nixGL"; inputs.nixgl.url = "github:guibou/nixGL";
@ -9,19 +9,48 @@
flake-utils.lib.eachDefaultSystem (system: flake-utils.lib.eachDefaultSystem (system:
let let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true; config.allowUnfree = true;
config.android_sdk.accept_license = true; config.android_sdk.accept_license = true;
system = "x86_64-linux";
overlays = [ nixgl.overlay ]; overlays = [ nixgl.overlay ];
}; };
androidComposition = pkgs.androidenv.composeAndroidPackages { androidComposition = pkgs.androidenv.composeAndroidPackages {
toolsVersion = "26.1.1"; platformToolsVersion = "34.0.4";
platformToolsVersion = "33.0.2"; buildToolsVersions = [ "34.0.0" ];
buildToolsVersions = [ "30.0.3" ]; platformVersions = [ "34" "33" "32" "31" "30" ];
platformVersions = [ "31" "30" "29" ];
}; };
spAndroidStudio = pkgs.symlinkJoin {
name = "spAndroidStudio";
paths = with pkgs; [
android-studio
flutter.unwrapped
# dart
gnumake
check
pkg-config
glibc
android-tools
jdk
git
];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/flutter \
--prefix ANDROID_SDK_ROOT=${androidComposition.androidsdk}/libexec/android-sdk \
--prefix ANDROID_HOME=${androidComposition.androidsdk}/libexec/android-sdk \
--prefix ANDROID_JAVA_HOME=${pkgs.jdk.home}
wrapProgram $out/bin/android-studio \
--prefix FLUTTER_SDK=${pkgs.flutter.unwrapped} \
--prefix ANDROID_SDKz_ROOT=${androidComposition.androidsdk}/libexec/android-sdk \
--prefix ANDROID_HOME=${androidComposition.androidsdk}/libexec/android-sdk \
--prefix ANDROID_JAVA_HOME=${pkgs.jdk.home}
'';
};
buildDeps = with pkgs; [ buildDeps = with pkgs; [
gtk3 gtk3
glib glib
@ -62,23 +91,23 @@
openjdk11_headless openjdk11_headless
clang clang
]; ];
releaseDerivation = pkgs.flutter.mkFlutterApp rec { releaseDerivation = pkgs.flutter.mkFlutterApp rec {
pname = "selfprivacy"; pname = "selfprivacy";
version = "0.6.0"; version = "0.6.0";
vendorHash = "sha256-7cbiAyIlaz3HqEsZN/nZxaLZjseJv5CmiIHqsoGa4ZI="; vendorHash = "sha256-7cbiAyIlaz3HqEsZN/nZxaLZjseJv5CmiIHqsoGa4ZI=";
nativeBuildInputs = [ pkgs.nixgl.auto.nixGLDefault ]; nativeBuildInputs = [ pkgs.nixgl.auto.nixGLDefault ];
src = ./.; src = ./.;
desktopItem = pkgs.makeDesktopItem { desktopItem = pkgs.makeDesktopItem {
name = "${pname}"; name = "${pname}";
exec = "@out@/bin/${pname}"; exec = "@out@/bin/${pname}";
desktopName = "SelfPrivacy"; desktopName = "SelfPrivacy";
}; };
postInstall = '' postInstall = ''
rm $out/bin/$pname rm $out/bin/$pname
@ -86,7 +115,7 @@
patchShebangs $out/bin/$pname patchShebangs $out/bin/$pname
chmod +x $out/bin/$pname chmod +x $out/bin/$pname
wrapProgram $out/bin/$pname --set PATH ${pkgs.lib.makeBinPath [ pkgs.xdg-user-dirs ]} wrapProgram $out/bin/$pname --set PATH ${pkgs.lib.makeBinPath [ pkgs.xdg-user-dirs ]}
mkdir -p $out/share/applications mkdir -p $out/share/applications
cp $desktopItem/share/applications/*.desktop $out/share/applications cp $desktopItem/share/applications/*.desktop $out/share/applications
substituteInPlace $out/share/applications/*.desktop --subst-var out substituteInPlace $out/share/applications/*.desktop --subst-var out

View File

@ -7,6 +7,13 @@ query BackupConfiguration {
locationId locationId
locationName locationName
provider provider
autobackupQuotas {
last
daily
weekly
monthly
yearly
}
} }
} }
} }
@ -36,6 +43,13 @@ fragment genericBackupConfigReturn on GenericBackupConfigReturn {
autobackupPeriod autobackupPeriod
locationName locationName
locationId locationId
autobackupQuotas {
last
daily
weekly
monthly
yearly
}
} }
} }
@ -66,6 +80,14 @@ mutation SetAutobackupPeriod($period: Int = null) {
} }
} }
mutation setAutobackupQuotas($quotas: AutobackupQuotasInput!) {
backup {
setAutobackupQuotas(quotas: $quotas) {
...genericBackupConfigReturn
}
}
}
mutation RemoveRepository { mutation RemoveRepository {
backup { backup {
removeRepository { removeRepository {

File diff suppressed because it is too large Load Diff

View File

@ -143,6 +143,51 @@ mixin BackupsApi on GraphQLApiMap {
return result; return result;
} }
Future<GenericResult> setAutobackupQuotas(
final AutobackupQuotas quotas,
) async {
QueryResult<Mutation$setAutobackupQuotas> response;
GenericResult? result;
try {
final GraphQLClient client = await getClient();
final variables = Variables$Mutation$setAutobackupQuotas(
quotas: Input$AutobackupQuotasInput(
last: quotas.last,
daily: quotas.daily,
weekly: quotas.weekly,
monthly: quotas.monthly,
yearly: quotas.yearly,
),
);
final options =
Options$Mutation$setAutobackupQuotas(variables: variables);
response = await client.mutate$setAutobackupQuotas(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 { Future<GenericResult> removeRepository() async {
try { try {
final GraphQLClient client = await getClient(); final GraphQLClient client = await getClient();

View File

@ -36,6 +36,7 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
backblazeBucket: bucket, backblazeBucket: bucket,
isInitialized: backupConfig?.isInitialized, isInitialized: backupConfig?.isInitialized,
autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero, autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero,
autobackupQuotas: backupConfig?.autobackupQuotas,
backups: backups, backups: backups,
preventActions: false, preventActions: false,
refreshing: false, refreshing: false,
@ -168,6 +169,7 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
refreshing: false, refreshing: false,
isInitialized: backupConfig?.isInitialized ?? false, isInitialized: backupConfig?.isInitialized ?? false,
autobackupPeriod: backupConfig?.autobackupPeriod, autobackupPeriod: backupConfig?.autobackupPeriod,
autobackupQuotas: backupConfig?.autobackupQuotas,
), ),
); );
if (useTimer) { if (useTimer) {
@ -227,6 +229,25 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
await updateBackups(); await updateBackups();
} }
Future<void> setAutobackupQuotas(final AutobackupQuotas quotas) async {
emit(state.copyWith(preventActions: true));
final result = await api.setAutobackupQuotas(quotas);
if (result.success == false) {
getIt<NavigationService>()
.showSnackBar(result.message ?? 'Unknown error');
emit(state.copyWith(preventActions: false));
} else {
getIt<NavigationService>().showSnackBar('backup.quotas_set'.tr());
emit(
state.copyWith(
preventActions: false,
autobackupQuotas: quotas,
),
);
}
await updateBackups();
NaiJi marked this conversation as resolved

Even with false? Not only with true?

Even with false? Not only with true?
Review

just to make sure we get the latest state from the server

just to make sure we get the latest state from the server
}
Future<void> forgetSnapshot(final String snapshotId) async { Future<void> forgetSnapshot(final String snapshotId) async {
final result = await api.forgetSnapshot(snapshotId); final result = await api.forgetSnapshot(snapshotId);
if (!result.success) { if (!result.success) {

View File

@ -9,6 +9,7 @@ class BackupsState extends ServerInstallationDependendState {
this.refreshing = true, this.refreshing = true,
this.autobackupPeriod, this.autobackupPeriod,
this.backblazeBucket, this.backblazeBucket,
this.autobackupQuotas,
}); });
final bool isInitialized; final bool isInitialized;
@ -18,6 +19,7 @@ class BackupsState extends ServerInstallationDependendState {
final bool refreshing; final bool refreshing;
final Duration? autobackupPeriod; final Duration? autobackupPeriod;
final BackblazeBucket? backblazeBucket; final BackblazeBucket? backblazeBucket;
final AutobackupQuotas? autobackupQuotas;
List<Backup> serviceBackups(final String serviceId) => backups List<Backup> serviceBackups(final String serviceId) => backups
.where((final backup) => backup.serviceId == serviceId) .where((final backup) => backup.serviceId == serviceId)
@ -40,6 +42,7 @@ class BackupsState extends ServerInstallationDependendState {
final bool? refreshing, final bool? refreshing,
final Duration? autobackupPeriod, final Duration? autobackupPeriod,
final BackblazeBucket? backblazeBucket, final BackblazeBucket? backblazeBucket,
final AutobackupQuotas? autobackupQuotas,
}) => }) =>
BackupsState( BackupsState(
isInitialized: isInitialized ?? this.isInitialized, isInitialized: isInitialized ?? this.isInitialized,
@ -53,5 +56,6 @@ class BackupsState extends ServerInstallationDependendState {
? null ? null
: autobackupPeriod ?? this.autobackupPeriod, : autobackupPeriod ?? this.autobackupPeriod,
backblazeBucket: backblazeBucket ?? this.backblazeBucket, backblazeBucket: backblazeBucket ?? this.backblazeBucket,
autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas,
); );
} }

View File

@ -53,6 +53,9 @@ class BackupConfiguration {
locationId: configuration.locationId, locationId: configuration.locationId,
locationName: configuration.locationName, locationName: configuration.locationName,
provider: BackupsProviderType.fromGraphQL(configuration.provider), provider: BackupsProviderType.fromGraphQL(configuration.provider),
autobackupQuotas: AutobackupQuotas.fromGraphQL(
configuration.autobackupQuotas,
),
); );
BackupConfiguration({ BackupConfiguration({
@ -62,6 +65,7 @@ class BackupConfiguration {
required this.locationId, required this.locationId,
required this.locationName, required this.locationName,
required this.provider, required this.provider,
required this.autobackupQuotas,
}); });
final Duration? autobackupPeriod; final Duration? autobackupPeriod;
@ -70,6 +74,49 @@ class BackupConfiguration {
final String? locationId; final String? locationId;
final String? locationName; final String? locationName;
final BackupsProviderType provider; final BackupsProviderType provider;
final AutobackupQuotas autobackupQuotas;
}
class AutobackupQuotas {
AutobackupQuotas.fromGraphQL(
final Query$BackupConfiguration$backup$configuration$autobackupQuotas
autobackupQuotas,
) : this(
last: autobackupQuotas.last,
daily: autobackupQuotas.daily,
weekly: autobackupQuotas.weekly,
monthly: autobackupQuotas.monthly,
yearly: autobackupQuotas.yearly,
);
AutobackupQuotas({
required this.last,
required this.daily,
required this.weekly,
required this.monthly,
required this.yearly,
});
final int last;
final int daily;
final int weekly;
final int monthly;
final int yearly;
AutobackupQuotas copyWith({
final int? last,
final int? daily,
final int? weekly,
final int? monthly,
final int? yearly,
}) =>
AutobackupQuotas(
last: last ?? this.last,
daily: daily ?? this.daily,
weekly: weekly ?? this.weekly,
monthly: monthly ?? this.monthly,
yearly: yearly ?? this.yearly,
);
} }
enum BackupRestoreStrategy { enum BackupRestoreStrategy {

View File

@ -16,6 +16,7 @@ import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/ui/pages/backups/change_period_modal.dart'; import 'package:selfprivacy/ui/pages/backups/change_period_modal.dart';
import 'package:selfprivacy/ui/pages/backups/change_rotation_quotas_modal.dart';
import 'package:selfprivacy/ui/pages/backups/copy_encryption_key_modal.dart'; import 'package:selfprivacy/ui/pages/backups/copy_encryption_key_modal.dart';
import 'package:selfprivacy/ui/pages/backups/create_backups_modal.dart'; import 'package:selfprivacy/ui/pages/backups/create_backups_modal.dart';
import 'package:selfprivacy/ui/pages/backups/snapshot_modal.dart'; import 'package:selfprivacy/ui/pages/backups/snapshot_modal.dart';
@ -168,6 +169,38 @@ class BackupDetailsPage extends StatelessWidget {
: 'backup.autobackup_period_never'.tr(), : 'backup.autobackup_period_never'.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) =>
ChangeRotationQuotasModal(
scrollController: scrollController,
),
),
);
},
leading: Icon(
Icons.auto_delete_outlined,
color: overrideColor,
),
title: Text(
'backup.rotation_quotas_title'.tr(),
style: TextStyle(
color: overrideColor,
),
),
),
ListTile( ListTile(
onTap: preventActions onTap: preventActions
? null ? null

View File

@ -0,0 +1,250 @@
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/logic/models/backup.dart';
class ChangeRotationQuotasModal extends StatefulWidget {
const ChangeRotationQuotasModal({
required this.scrollController,
super.key,
});
final ScrollController scrollController;
@override
State<ChangeRotationQuotasModal> createState() =>
_ChangeRotationQuotasModalState();
}
enum QuotaUnits {
last,
daily,
weekly,
monthly,
yearly,
}
class _ChangeRotationQuotasModalState extends State<ChangeRotationQuotasModal> {
AutobackupQuotas selectedQuotas = AutobackupQuotas(
last: 3,
daily: 7,
weekly: 4,
monthly: 6,
yearly: -1,
);
// Set initial period to the one currently set
@override
void initState() {
super.initState();
selectedQuotas =
context.read<BackupsCubit>().state.autobackupQuotas ?? selectedQuotas;
}
String generateSubtitle(final int value, final QuotaUnits unit) {
switch (unit) {
case QuotaUnits.last:
return value == -1
? 'backup.quota_subtitles.last_infinite'.tr()
: 'backup.quota_subtitles.last'.plural(value);
case QuotaUnits.daily:
if (selectedQuotas.last == -1) {
return 'backup.quota_subtitles.no_effect'.tr();
}
return value == -1
? 'backup.quota_subtitles.daily_infinite'.tr()
: 'backup.quota_subtitles.daily'.plural(value);
case QuotaUnits.weekly:
if (selectedQuotas.last == -1 || selectedQuotas.daily == -1) {
return 'backup.quota_subtitles.no_effect'.tr();
}
return value == -1
? 'backup.quota_subtitles.weekly_infinite'.tr()
: 'backup.quota_subtitles.weekly'.plural(value);
case QuotaUnits.monthly:
if (selectedQuotas.last == -1 || selectedQuotas.daily == -1) {
return 'backup.quota_subtitles.no_effect'.tr();
}
return value == -1
? 'backup.quota_subtitles.monthly_infinite'.tr()
: 'backup.quota_subtitles.monthly'.plural(value);
case QuotaUnits.yearly:
if (selectedQuotas.last == -1 || selectedQuotas.daily == -1) {
return 'backup.quota_subtitles.no_effect'.tr();
}
return value == -1
? 'backup.quota_subtitles.yearly_infinite'.tr()
: 'backup.quota_subtitles.yearly'.plural(value);
}
}
@override
Widget build(final BuildContext context) {
final AutobackupQuotas? initialAutobackupQuotas =
context.watch<BackupsCubit>().state.autobackupQuotas;
return ListView(
controller: widget.scrollController,
padding: const EdgeInsets.all(16),
children: [
const SizedBox(height: 16),
Text(
'backup.rotation_quotas_title'.tr(),
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'backup.quotas_only_applied_to_autobackups'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// Accordions for each quota type. When tapped allows to enter a new int value
// for the quota.
QuotaSelectionTile(
title: 'backup.quota_titles.last'.tr(),
subtitle: generateSubtitle(selectedQuotas.last, QuotaUnits.last),
value: selectedQuotas.last,
min: 1,
max: 30,
callback: (final double value) {
setState(() {
if (value == 31) {
selectedQuotas = selectedQuotas.copyWith(last: -1);
return;
}
selectedQuotas = selectedQuotas.copyWith(last: value.toInt());
});
},
),
QuotaSelectionTile(
title: 'backup.quota_titles.daily'.tr(),
subtitle: generateSubtitle(selectedQuotas.daily, QuotaUnits.daily),
value: selectedQuotas.daily,
min: 0,
max: 30,
callback: (final double value) {
setState(() {
if (value == 31) {
selectedQuotas = selectedQuotas.copyWith(daily: -1);
return;
}
selectedQuotas = selectedQuotas.copyWith(daily: value.toInt());
});
},
),
QuotaSelectionTile(
title: 'backup.quota_titles.weekly'.tr(),
subtitle: generateSubtitle(selectedQuotas.weekly, QuotaUnits.weekly),
value: selectedQuotas.weekly,
min: 0,
max: 15,
callback: (final double value) {
setState(() {
if (value == 16) {
selectedQuotas = selectedQuotas.copyWith(weekly: -1);
return;
}
selectedQuotas = selectedQuotas.copyWith(weekly: value.toInt());
});
},
),
QuotaSelectionTile(
title: 'backup.quota_titles.monthly'.tr(),
subtitle:
generateSubtitle(selectedQuotas.monthly, QuotaUnits.monthly),
value: selectedQuotas.monthly,
min: 0,
max: 24,
callback: (final double value) {
setState(() {
if (value == 25) {
selectedQuotas = selectedQuotas.copyWith(monthly: -1);
return;
}
selectedQuotas = selectedQuotas.copyWith(monthly: value.toInt());
});
},
),
QuotaSelectionTile(
title: 'backup.quota_titles.yearly'.tr(),
subtitle: generateSubtitle(selectedQuotas.yearly, QuotaUnits.yearly),
value: selectedQuotas.yearly,
min: 0,
max: 5,
callback: (final double value) {
setState(() {
if (value == 6) {
selectedQuotas = selectedQuotas.copyWith(yearly: -1);
return;
}
selectedQuotas = selectedQuotas.copyWith(yearly: value.toInt());
});
},
),
const SizedBox(height: 16),
FilledButton(
onPressed: selectedQuotas == initialAutobackupQuotas
? null
: () {
context
.read<BackupsCubit>()
.setAutobackupQuotas(selectedQuotas);
Navigator.of(context).pop();
},
child: Text(
'backup.set_rotation_quotas'.tr(),
),
),
],
);
}
}
class QuotaSelectionTile extends StatelessWidget {
const QuotaSelectionTile({
required this.title,
required this.subtitle,
required this.value,
required this.min,
required this.max,
required this.callback,
super.key,
});
final String title;
final String subtitle;
final int value;
final int min;
final int max;
final void Function(double) callback;
@override
Widget build(final BuildContext context) => ExpansionTile(
title: Text(title),
subtitle: Text(subtitle),
trailing: Text(
value == -1 ? '' : value.toString(),
style: Theme.of(context).textTheme.headlineMedium,
),
children: [
// Discrete slider to select the new value
if (value >= -1 && value <= max)
Slider(
value: value == -1 ? max + 1 : value.toDouble(),
min: min.toDouble(),
max: (max + 1).toDouble(),
divisions: max - min + 1,
label: value == -1 ? '' : value.toString(),
onChanged: callback,
),
if (value < -1 || value > max)
Text(
'Manually set to $value',
style: Theme.of(context).textTheme.headlineSmall,
),
],
);
}

View File

@ -320,7 +320,7 @@ class InitializingPage extends StatelessWidget {
Widget _stepDomain(final ServerInstallationCubit initializingCubit) => Widget _stepDomain(final ServerInstallationCubit initializingCubit) =>
BlocProvider( BlocProvider(
create: (final context) => DomainSetupCubit(initializingCubit)..load(), create: (final context) => DomainSetupCubit(initializingCubit)..load(),
child: DomainPicker(), child: const DomainPicker(),
); );
Widget _stepUser(final ServerInstallationCubit initializingCubit) => Widget _stepUser(final ServerInstallationCubit initializingCubit) =>

View File

@ -15,18 +15,6 @@ abstract class _$RootRouter extends RootStackRouter {
@override @override
final Map<String, PageFactory> pagesMap = { final Map<String, PageFactory> pagesMap = {
OnboardingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const OnboardingPage(),
);
},
BackupDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const BackupDetailsPage(),
);
},
BackupsListRoute.name: (routeData) { BackupsListRoute.name: (routeData) {
final args = routeData.argsAs<BackupsListRouteArgs>(); final args = routeData.argsAs<BackupsListRouteArgs>();
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
@ -37,10 +25,58 @@ abstract class _$RootRouter extends RootStackRouter {
), ),
); );
}, },
RecoveryKeyRoute.name: (routeData) { BackupDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const RecoveryKeyPage(), child: const BackupDetailsPage(),
);
},
DevicesRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DevicesScreen(),
);
},
DnsDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DnsDetailsPage(),
);
},
AboutApplicationRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AboutApplicationPage(),
);
},
AppSettingsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AppSettingsPage(),
);
},
DeveloperSettingsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DeveloperSettingsPage(),
);
},
ConsoleRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ConsolePage(),
);
},
MoreRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const MorePage(),
);
},
OnboardingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const OnboardingPage(),
); );
}, },
ProvidersRoute.name: (routeData) { ProvidersRoute.name: (routeData) {
@ -49,6 +85,18 @@ abstract class _$RootRouter extends RootStackRouter {
child: const ProvidersPage(), child: const ProvidersPage(),
); );
}, },
RecoveryKeyRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryKeyPage(),
);
},
RootRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: WrappedRoute(child: const RootPage()),
);
},
ServerDetailsRoute.name: (routeData) { ServerDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
@ -67,17 +115,6 @@ abstract class _$RootRouter extends RootStackRouter {
), ),
); );
}, },
ExtendingVolumeRoute.name: (routeData) {
final args = routeData.argsAs<ExtendingVolumeRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: ExtendingVolumePage(
diskVolumeToResize: args.diskVolumeToResize,
diskStatus: args.diskStatus,
key: args.key,
),
);
},
ServerStorageRoute.name: (routeData) { ServerStorageRoute.name: (routeData) {
final args = routeData.argsAs<ServerStorageRouteArgs>(); final args = routeData.argsAs<ServerStorageRouteArgs>();
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
@ -88,52 +125,15 @@ abstract class _$RootRouter extends RootStackRouter {
), ),
); );
}, },
DevicesRoute.name: (routeData) { ExtendingVolumeRoute.name: (routeData) {
final args = routeData.argsAs<ExtendingVolumeRouteArgs>();
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const DevicesScreen(), child: ExtendingVolumePage(
); diskVolumeToResize: args.diskVolumeToResize,
}, diskStatus: args.diskStatus,
RootRoute.name: (routeData) { key: args.key,
return AutoRoutePage<dynamic>( ),
routeData: routeData,
child: WrappedRoute(child: const RootPage()),
);
},
AboutApplicationRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AboutApplicationPage(),
);
},
ConsoleRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const ConsolePage(),
);
},
MoreRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const MorePage(),
);
},
AppSettingsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const AppSettingsPage(),
);
},
DeveloperSettingsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DeveloperSettingsPage(),
);
},
DnsDetailsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const DnsDetailsPage(),
); );
}, },
ServiceRoute.name: (routeData) { ServiceRoute.name: (routeData) {
@ -152,6 +152,18 @@ abstract class _$RootRouter extends RootStackRouter {
child: const ServicesPage(), child: const ServicesPage(),
); );
}, },
InitializingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const InitializingPage(),
);
},
RecoveryRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryRouting(),
);
},
UsersRoute.name: (routeData) { UsersRoute.name: (routeData) {
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
@ -174,49 +186,9 @@ abstract class _$RootRouter extends RootStackRouter {
), ),
); );
}, },
InitializingRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const InitializingPage(),
);
},
RecoveryRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const RecoveryRouting(),
);
},
}; };
} }
/// 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
/// [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 /// generated route for
/// [BackupsListPage] /// [BackupsListPage]
class BackupsListRoute extends PageRouteInfo<BackupsListRouteArgs> { class BackupsListRoute extends PageRouteInfo<BackupsListRouteArgs> {
@ -256,15 +228,127 @@ class BackupsListRouteArgs {
} }
/// generated route for /// generated route for
/// [RecoveryKeyPage] /// [BackupDetailsPage]
class RecoveryKeyRoute extends PageRouteInfo<void> { class BackupDetailsRoute extends PageRouteInfo<void> {
const RecoveryKeyRoute({List<PageRouteInfo>? children}) const BackupDetailsRoute({List<PageRouteInfo>? children})
: super( : super(
RecoveryKeyRoute.name, BackupDetailsRoute.name,
initialChildren: children, initialChildren: children,
); );
static const String name = 'RecoveryKeyRoute'; static const String name = 'BackupDetailsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [DevicesScreen]
class DevicesRoute extends PageRouteInfo<void> {
const DevicesRoute({List<PageRouteInfo>? children})
: super(
DevicesRoute.name,
initialChildren: children,
);
static const String name = 'DevicesRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [DnsDetailsPage]
class DnsDetailsRoute extends PageRouteInfo<void> {
const DnsDetailsRoute({List<PageRouteInfo>? children})
: super(
DnsDetailsRoute.name,
initialChildren: children,
);
static const String name = 'DnsDetailsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [AboutApplicationPage]
class AboutApplicationRoute extends PageRouteInfo<void> {
const AboutApplicationRoute({List<PageRouteInfo>? children})
: super(
AboutApplicationRoute.name,
initialChildren: children,
);
static const String name = 'AboutApplicationRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [AppSettingsPage]
class AppSettingsRoute extends PageRouteInfo<void> {
const AppSettingsRoute({List<PageRouteInfo>? children})
: super(
AppSettingsRoute.name,
initialChildren: children,
);
static const String name = 'AppSettingsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [DeveloperSettingsPage]
class DeveloperSettingsRoute extends PageRouteInfo<void> {
const DeveloperSettingsRoute({List<PageRouteInfo>? children})
: super(
DeveloperSettingsRoute.name,
initialChildren: children,
);
static const String name = 'DeveloperSettingsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [ConsolePage]
class ConsoleRoute extends PageRouteInfo<void> {
const ConsoleRoute({List<PageRouteInfo>? children})
: super(
ConsoleRoute.name,
initialChildren: children,
);
static const String name = 'ConsoleRoute';
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
/// [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); static const PageInfo<void> page = PageInfo<void>(name);
} }
@ -283,6 +367,34 @@ class ProvidersRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for
/// [RecoveryKeyPage]
class RecoveryKeyRoute extends PageRouteInfo<void> {
const RecoveryKeyRoute({List<PageRouteInfo>? children})
: super(
RecoveryKeyRoute.name,
initialChildren: children,
);
static const String name = 'RecoveryKeyRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// 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 /// generated route for
/// [ServerDetailsScreen] /// [ServerDetailsScreen]
class ServerDetailsRoute extends PageRouteInfo<void> { class ServerDetailsRoute extends PageRouteInfo<void> {
@ -345,6 +457,44 @@ class ServicesMigrationRouteArgs {
} }
} }
/// generated route for
/// [ServerStoragePage]
class ServerStorageRoute extends PageRouteInfo<ServerStorageRouteArgs> {
ServerStorageRoute({
required DiskStatus diskStatus,
Key? key,
List<PageRouteInfo>? children,
}) : super(
ServerStorageRoute.name,
args: ServerStorageRouteArgs(
diskStatus: diskStatus,
key: key,
),
initialChildren: children,
);
static const String name = 'ServerStorageRoute';
static const PageInfo<ServerStorageRouteArgs> page =
PageInfo<ServerStorageRouteArgs>(name);
}
class ServerStorageRouteArgs {
const ServerStorageRouteArgs({
required this.diskStatus,
this.key,
});
final DiskStatus diskStatus;
final Key? key;
@override
String toString() {
return 'ServerStorageRouteArgs{diskStatus: $diskStatus, key: $key}';
}
}
/// generated route for /// generated route for
/// [ExtendingVolumePage] /// [ExtendingVolumePage]
class ExtendingVolumeRoute extends PageRouteInfo<ExtendingVolumeRouteArgs> { class ExtendingVolumeRoute extends PageRouteInfo<ExtendingVolumeRouteArgs> {
@ -388,156 +538,6 @@ class ExtendingVolumeRouteArgs {
} }
} }
/// generated route for
/// [ServerStoragePage]
class ServerStorageRoute extends PageRouteInfo<ServerStorageRouteArgs> {
ServerStorageRoute({
required DiskStatus diskStatus,
Key? key,
List<PageRouteInfo>? children,
}) : super(
ServerStorageRoute.name,
args: ServerStorageRouteArgs(
diskStatus: diskStatus,
key: key,
),
initialChildren: children,
);
static const String name = 'ServerStorageRoute';
static const PageInfo<ServerStorageRouteArgs> page =
PageInfo<ServerStorageRouteArgs>(name);
}
class ServerStorageRouteArgs {
const ServerStorageRouteArgs({
required this.diskStatus,
this.key,
});
final DiskStatus diskStatus;
final Key? key;
@override
String toString() {
return 'ServerStorageRouteArgs{diskStatus: $diskStatus, key: $key}';
}
}
/// generated route for
/// [DevicesScreen]
class DevicesRoute extends PageRouteInfo<void> {
const DevicesRoute({List<PageRouteInfo>? children})
: super(
DevicesRoute.name,
initialChildren: children,
);
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})
: super(
RootRoute.name,
initialChildren: children,
);
static const String name = 'RootRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [AboutApplicationPage]
class AboutApplicationRoute extends PageRouteInfo<void> {
const AboutApplicationRoute({List<PageRouteInfo>? children})
: super(
AboutApplicationRoute.name,
initialChildren: children,
);
static const String name = 'AboutApplicationRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [ConsolePage]
class ConsoleRoute extends PageRouteInfo<void> {
const ConsoleRoute({List<PageRouteInfo>? children})
: super(
ConsoleRoute.name,
initialChildren: children,
);
static const String name = 'ConsoleRoute';
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
/// [AppSettingsPage]
class AppSettingsRoute extends PageRouteInfo<void> {
const AppSettingsRoute({List<PageRouteInfo>? children})
: super(
AppSettingsRoute.name,
initialChildren: children,
);
static const String name = 'AppSettingsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [DeveloperSettingsPage]
class DeveloperSettingsRoute extends PageRouteInfo<void> {
const DeveloperSettingsRoute({List<PageRouteInfo>? children})
: super(
DeveloperSettingsRoute.name,
initialChildren: children,
);
static const String name = 'DeveloperSettingsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [DnsDetailsPage]
class DnsDetailsRoute extends PageRouteInfo<void> {
const DnsDetailsRoute({List<PageRouteInfo>? children})
: super(
DnsDetailsRoute.name,
initialChildren: children,
);
static const String name = 'DnsDetailsRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for /// generated route for
/// [ServicePage] /// [ServicePage]
class ServiceRoute extends PageRouteInfo<ServiceRouteArgs> { class ServiceRoute extends PageRouteInfo<ServiceRouteArgs> {
@ -590,6 +590,34 @@ class ServicesRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<void> page = PageInfo<void>(name);
} }
/// generated route for
/// [InitializingPage]
class InitializingRoute extends PageRouteInfo<void> {
const InitializingRoute({List<PageRouteInfo>? children})
: super(
InitializingRoute.name,
initialChildren: children,
);
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 /// generated route for
/// [UsersPage] /// [UsersPage]
class UsersRoute extends PageRouteInfo<void> { class UsersRoute extends PageRouteInfo<void> {
@ -655,31 +683,3 @@ class UserDetailsRouteArgs {
return 'UserDetailsRouteArgs{login: $login, key: $key}'; return 'UserDetailsRouteArgs{login: $login, key: $key}';
} }
} }
/// generated route for
/// [InitializingPage]
class InitializingRoute extends PageRouteInfo<void> {
const InitializingRoute({List<PageRouteInfo>? children})
: super(
InitializingRoute.name,
initialChildren: children,
);
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);
}