Create management portal in Flutter #39

This commit is contained in:
Scott E. Graves 2025-03-07 11:36:43 -06:00
parent 5ed74aa5af
commit 1d37822451
3 changed files with 243 additions and 35 deletions

View File

@ -1,31 +1,28 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
String formatMountName(String type, String name) { typedef Validator = bool Function(String);
if (type == 'remote') {
return name.replaceAll('_', ':');
}
return name; bool containsRestrictedChar(String value) {
} const invalidChars = [
'!',
String getBaseUri() { '"',
if (kDebugMode || !kIsWeb) { '\$',
return 'http://127.0.0.1:30000'; '&',
} '\'',
'(',
return Uri.base.origin; ')',
} '*',
';',
String initialCaps(String txt) { '<',
if (txt.isEmpty) { '>',
return txt; '?',
} '`',
'{',
if (txt.length == 1) { '|',
return txt[0].toUpperCase(); '}',
} ];
return invalidChars.firstWhereOrNull((char) => value.contains(char)) != null;
return txt[0].toUpperCase() + txt.substring(1).toLowerCase();
} }
Map<String, dynamic> createDefaultSettings(String mountType) { Map<String, dynamic> createDefaultSettings(String mountType) {
@ -52,7 +49,7 @@ Map<String, dynamic> createDefaultSettings(String mountType) {
return { return {
'HostConfig': { 'HostConfig': {
'ApiPassword': '', 'ApiPassword': '',
'ApiPort': '9980', 'ApiPort': 9980,
'HostNameOrIp': 'localhost', 'HostNameOrIp': 'localhost',
}, },
'SiaConfig': {'Bucket': 'default'}, 'SiaConfig': {'Bucket': 'default'},
@ -61,3 +58,105 @@ Map<String, dynamic> createDefaultSettings(String mountType) {
return {}; return {};
} }
String formatMountName(String type, String name) {
if (type == 'remote') {
return name.replaceAll('_', ':');
}
return name;
}
String getBaseUri() {
if (kDebugMode || !kIsWeb) {
return 'http://127.0.0.1:30000';
}
return Uri.base.origin;
}
List<Validator> getSettingValidators(String settingPath) {
switch (settingPath) {
case 'EncryptConfig.EncryptionToken':
return [(value) => value.isNotEmpty];
case 'EncryptConfig.Path':
return [
(value) => value.trim().isNotEmpty,
(value) => !containsRestrictedChar(value),
];
case 'HostConfig.ApiPassword':
return [(value) => value.isNotEmpty];
case 'HostConfig.ApiPort':
return [
(value) {
int? intValue = int.tryParse(value);
if (intValue == null) {
return false;
}
return (intValue > 0 && intValue < 65536);
},
(value) => Uri.tryParse('http://localhost:$value/') != null,
];
case 'HostConfig.HostNameOrIp':
return [
(value) => value.trim().isNotEmpty,
(value) => Uri.tryParse('http://$value:9000/') != null,
];
case 'HostConfig.Protocol':
return [(value) => value == "http" || value == "https"];
case 'S3Config.AccessKey':
return [(value) => value.isNotEmpty];
case 'S3Config.Bucket':
return [(value) => value.trim().isNotEmpty];
case 'S3Config.SecretKey':
return [(value) => value.isNotEmpty];
case 'S3Config.URL':
return [(value) => Uri.tryParse(value) != null];
case 'SiaConfig.Bucket':
return [(value) => value.trim().isNotEmpty];
}
return [];
}
String initialCaps(String txt) {
if (txt.isEmpty) {
return txt;
}
if (txt.length == 1) {
return txt[0].toUpperCase();
}
return txt[0].toUpperCase() + txt.substring(1).toLowerCase();
}
bool validateSettings(
Map<String, dynamic> settings,
List<String> failed, {
String? rootKey,
}) {
settings.forEach((key, value) {
if (value is Map) {
validateSettings(
value as Map<String, dynamic>,
failed,
rootKey: rootKey == null ? key : '$rootKey.$key',
);
} else {
final validators = getSettingValidators(key);
for (var validator in validators) {
if (!validator(value.toString())) {
if (rootKey == null) {
failed.add(key);
} else {
failed.add('$rootKey.$key');
}
}
}
}
});
return failed.isEmpty;
}

View File

@ -121,11 +121,17 @@ class _AddMountScreenState extends State<AddMountScreen> {
if (_mount != null) if (_mount != null)
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () { onPressed: () {
List<String> failed = [];
if (!validateSettings(_settings[_mountType]!, failed)) {
return;
}
Provider.of<MountList>(context, listen: false).add( Provider.of<MountList>(context, listen: false).add(
_mountType, _mountType,
_mountNameController.text, _mountNameController.text,
_settings[_mountType]!, _settings[_mountType]!,
); );
Navigator.pop(context); Navigator.pop(context);
}, },
label: const Text('Add'), label: const Text('Add'),

View File

@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:repertory/constants.dart'; import 'package:repertory/constants.dart';
import 'package:repertory/helpers.dart' show Validator, getSettingValidators;
import 'package:repertory/models/mount.dart'; import 'package:repertory/models/mount.dart';
import 'package:settings_ui/settings_ui.dart'; import 'package:settings_ui/settings_ui.dart';
@ -48,7 +49,14 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
} }
} }
void _addIntSetting(list, root, key, value, isAdvanced) { void _addIntSetting(
list,
root,
key,
value,
isAdvanced, {
List<Validator> validators = const [],
}) {
if (!isAdvanced || widget.showAdvanced) { if (!isAdvanced || widget.showAdvanced) {
list.add( list.add(
SettingsTile.navigation( SettingsTile.navigation(
@ -69,6 +77,15 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
TextButton( TextButton(
child: Text('OK'), child: Text('OK'),
onPressed: () { onPressed: () {
final result = validators.firstWhereOrNull(
(func) => !func(updatedValue),
);
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$key must be valid')),
);
return;
}
setState(() { setState(() {
root[key] = int.parse(updatedValue); root[key] = int.parse(updatedValue);
widget.onChanged?.call(widget.settings); widget.onChanged?.call(widget.settings);
@ -176,7 +193,15 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
} }
} }
void _addStringSetting(list, root, key, value, icon, isAdvanced) { void _addStringSetting(
list,
root,
key,
value,
icon,
isAdvanced, {
List<Validator> validators = const [],
}) {
if (!isAdvanced || widget.showAdvanced) { if (!isAdvanced || widget.showAdvanced) {
list.add( list.add(
SettingsTile.navigation( SettingsTile.navigation(
@ -197,6 +222,16 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
TextButton( TextButton(
child: Text('OK'), child: Text('OK'),
onPressed: () { onPressed: () {
final result = validators.firstWhereOrNull(
(func) => !func(updatedValue),
);
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$key must be valid')),
);
return;
}
setState(() { setState(() {
root[key] = updatedValue; root[key] = updatedValue;
widget.onChanged?.call(widget.settings); widget.onChanged?.call(widget.settings);
@ -234,7 +269,14 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
if (key == 'ApiAuth') { if (key == 'ApiAuth') {
_addPasswordSetting(commonSettings, widget.settings, key, value, true); _addPasswordSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'ApiPort') { } else if (key == 'ApiPort') {
_addIntSetting(commonSettings, widget.settings, key, value, true); _addIntSetting(
commonSettings,
widget.settings,
key,
value,
true,
validators: getSettingValidators(key),
);
} else if (key == 'ApiUser') { } else if (key == 'ApiUser') {
_addStringSetting( _addStringSetting(
commonSettings, commonSettings,
@ -243,6 +285,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
value, value,
Icons.person, Icons.person,
true, true,
validators: getSettingValidators(key),
); );
} else if (key == 'DatabaseType') { } else if (key == 'DatabaseType') {
_addListSetting( _addListSetting(
@ -255,7 +298,14 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
true, true,
); );
} else if (key == 'DownloadTimeoutSeconds') { } else if (key == 'DownloadTimeoutSeconds') {
_addIntSetting(commonSettings, widget.settings, key, value, true); _addIntSetting(
commonSettings,
widget.settings,
key,
value,
true,
validators: getSettingValidators(key),
);
} else if (key == 'EnableDownloadTimeout') { } else if (key == 'EnableDownloadTimeout') {
_addBooleanSetting(commonSettings, widget.settings, key, value, true); _addBooleanSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'EnableDriveEvents') { } else if (key == 'EnableDriveEvents') {
@ -271,15 +321,43 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
false, false,
); );
} else if (key == 'EvictionDelayMinutes') { } else if (key == 'EvictionDelayMinutes') {
_addIntSetting(commonSettings, widget.settings, key, value, true); _addIntSetting(
commonSettings,
widget.settings,
key,
value,
true,
validators: getSettingValidators(key),
);
} else if (key == 'EvictionUseAccessedTime') { } else if (key == 'EvictionUseAccessedTime') {
_addBooleanSetting(commonSettings, widget.settings, key, value, true); _addBooleanSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'MaxCacheSizeBytes') { } else if (key == 'MaxCacheSizeBytes') {
_addIntSetting(commonSettings, widget.settings, key, value, false); _addIntSetting(
commonSettings,
widget.settings,
key,
value,
false,
validators: getSettingValidators(key),
);
} else if (key == 'MaxUploadCount') { } else if (key == 'MaxUploadCount') {
_addIntSetting(commonSettings, widget.settings, key, value, true); _addIntSetting(
commonSettings,
widget.settings,
key,
value,
true,
validators: getSettingValidators(key),
);
} else if (key == 'OnlineCheckRetrySeconds') { } else if (key == 'OnlineCheckRetrySeconds') {
_addIntSetting(commonSettings, widget.settings, key, value, true); _addIntSetting(
commonSettings,
widget.settings,
key,
value,
true,
validators: getSettingValidators(key),
);
} else if (key == 'PreferredDownloadType') { } else if (key == 'PreferredDownloadType') {
_addListSetting( _addListSetting(
commonSettings, commonSettings,
@ -291,7 +369,14 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
false, false,
); );
} else if (key == 'RetryReadCount') { } else if (key == 'RetryReadCount') {
_addIntSetting(commonSettings, widget.settings, key, value, true); _addIntSetting(
commonSettings,
widget.settings,
key,
value,
true,
validators: getSettingValidators(key),
);
} else if (key == 'RingBufferFileSize') { } else if (key == 'RingBufferFileSize') {
_addIntListSetting( _addIntListSetting(
commonSettings, commonSettings,
@ -321,6 +406,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.folder, Icons.folder,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} }
}); });
@ -334,6 +420,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.support_agent, Icons.support_agent,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'ApiPassword') { } else if (subKey == 'ApiPassword') {
_addPasswordSetting( _addPasswordSetting(
@ -350,6 +437,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'ApiUser') { } else if (subKey == 'ApiUser') {
_addStringSetting( _addStringSetting(
@ -359,6 +447,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.person, Icons.person,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'HostNameOrIp') { } else if (subKey == 'HostNameOrIp') {
_addStringSetting( _addStringSetting(
@ -377,6 +466,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.route, Icons.route,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'Protocol') { } else if (subKey == 'Protocol') {
_addListSetting( _addListSetting(
@ -395,6 +485,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} }
}); });
@ -407,6 +498,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'EncryptionToken') { } else if (subKey == 'EncryptionToken') {
_addPasswordSetting( _addPasswordSetting(
@ -424,6 +516,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.computer, Icons.computer,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'MaxConnections') { } else if (subKey == 'MaxConnections') {
_addIntSetting( _addIntSetting(
@ -432,6 +525,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'ReceiveTimeoutMs') { } else if (subKey == 'ReceiveTimeoutMs') {
_addIntSetting( _addIntSetting(
@ -440,6 +534,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'SendTimeoutMs') { } else if (subKey == 'SendTimeoutMs') {
_addIntSetting( _addIntSetting(
@ -448,6 +543,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} }
}); });
@ -470,6 +566,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'ClientPoolSize') { } else if (subKey == 'ClientPoolSize') {
_addIntSetting( _addIntSetting(
@ -478,6 +575,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'EncryptionToken') { } else if (subKey == 'EncryptionToken') {
_addPasswordSetting( _addPasswordSetting(
@ -507,6 +605,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.folder, Icons.folder,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'EncryptionToken') { } else if (subKey == 'EncryptionToken') {
_addPasswordSetting( _addPasswordSetting(
@ -524,6 +623,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.map, Icons.map,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'SecretKey') { } else if (subKey == 'SecretKey') {
_addPasswordSetting( _addPasswordSetting(
@ -540,6 +640,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subKey, subKey,
subValue, subValue,
true, true,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'URL') { } else if (subKey == 'URL') {
_addStringSetting( _addStringSetting(
@ -549,6 +650,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.http, Icons.http,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} else if (subKey == 'UsePathStyle') { } else if (subKey == 'UsePathStyle') {
_addBooleanSetting( _addBooleanSetting(
@ -578,6 +680,7 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
subValue, subValue,
Icons.folder, Icons.folder,
false, false,
validators: getSettingValidators('$key.$subKey'),
); );
} }
}); });