diff --git a/web/repertory/lib/constants.dart b/web/repertory/lib/constants.dart index 2d3f1c98..9500067e 100644 --- a/web/repertory/lib/constants.dart +++ b/web/repertory/lib/constants.dart @@ -1,8 +1,9 @@ -const String addMountTitle = 'New Mount Settings'; -const String appTitle = 'Repertory Management Portal'; +const addMountTitle = 'New Mount Settings'; +const appTitle = 'Repertory Management Portal'; const databaseTypeList = ['rocksdb', 'sqlite']; const downloadTypeList = ['default', 'direct', 'ring_buffer']; const eventLevelList = ['critical', 'error', 'warn', 'info', 'debug', 'trace']; +const padding = 15.0; const protocolTypeList = ['http', 'https']; const providerTypeList = ['Encrypt', 'Remote', 'S3', 'Sia']; const ringBufferSizeList = ['128', '256', '512', '1024', '2048']; diff --git a/web/repertory/lib/helpers.dart b/web/repertory/lib/helpers.dart index 8321351a..b5808937 100644 --- a/web/repertory/lib/helpers.dart +++ b/web/repertory/lib/helpers.dart @@ -1,32 +1,50 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import 'package:repertory/constants.dart' as constants; typedef Validator = bool Function(String); -bool containsRestrictedChar(String value) { - const invalidChars = [ - '!', - '"', - '\$', - '&', - '\'', - '(', - ')', - '*', - ';', - '<', - '>', - '?', - '[', - ']', - '`', - '{', - '}', - '|', - ]; - return invalidChars.firstWhereOrNull((char) => value.contains(char)) != null; +// ignore: prefer_function_declarations_over_variables +final Validator noRestrictedChars = (value) { + return [ + '!', + '"', + '\$', + '&', + "'", + '(', + ')', + '*', + ';', + '<', + '>', + '?', + '[', + ']', + '`', + '{', + '}', + '|', + ].firstWhereOrNull((char) => value.contains(char)) == + null; +}; + +// ignore: prefer_function_declarations_over_variables +final Validator notEmptyValidator = (value) => value.isNotEmpty; + +// ignore: prefer_function_declarations_over_variables +final Validator trimNotEmptyValidator = (value) => value.trim().isNotEmpty; + +createUriValidator({host, port}) { + return (value) => + Uri.tryParse('http://${host ?? value}:${port ?? value}/') != null; } +createHostNameOrIpValidators() => [ + trimNotEmptyValidator, + createUriValidator(port: 9000), +]; + Map createDefaultSettings(String mountType) { switch (mountType) { case 'Encrypt': @@ -80,16 +98,19 @@ String getBaseUri() { List getSettingValidators(String settingPath) { switch (settingPath) { case 'ApiAuth': - return [(value) => value.isNotEmpty]; + return [notEmptyValidator]; + case 'DatabaseType': + return [(value) => constants.databaseTypeList.contains(value)]; + case 'PreferredDownloadType': + return [(value) => constants.downloadTypeList.contains(value)]; + case 'EventLevel': + return [(value) => constants.eventLevelList.contains(value)]; case 'EncryptConfig.EncryptionToken': - return [(value) => value.isNotEmpty]; + return [notEmptyValidator]; case 'EncryptConfig.Path': - return [ - (value) => value.trim().isNotEmpty, - (value) => !containsRestrictedChar(value), - ]; + return [trimNotEmptyValidator, noRestrictedChars]; case 'HostConfig.ApiPassword': - return [(value) => value.isNotEmpty]; + return [notEmptyValidator]; case 'HostConfig.ApiPort': return [ (value) { @@ -100,29 +121,32 @@ List getSettingValidators(String settingPath) { return (intValue > 0 && intValue < 65536); }, - (value) => Uri.tryParse('http://localhost:$value/') != null, + createUriValidator(host: 'localhost'), ]; case 'HostConfig.HostNameOrIp': - return [ - (value) => value.trim().isNotEmpty, - (value) => Uri.tryParse('http://$value:9000/') != null, - ]; + return createHostNameOrIpValidators(); case 'HostConfig.Protocol': - return [(value) => value == "http" || value == "https"]; + return [(value) => constants.protocolTypeList.contains(value)]; case 'RemoteConfig.EncryptionToken': - return [(value) => value.isNotEmpty]; + return [notEmptyValidator]; + case 'RemoteConfig.HostNameOrIp': + return createHostNameOrIpValidators(); case 'RemoteMount.EncryptionToken': - return [(value) => value.isNotEmpty]; + return [notEmptyValidator]; + case 'RemoteMount.HostNameOrIp': + return createHostNameOrIpValidators(); + case 'RingBufferFileSize': + return [(value) => constants.ringBufferSizeList.contains(value)]; case 'S3Config.AccessKey': - return [(value) => value.trim().isNotEmpty]; + return [trimNotEmptyValidator]; case 'S3Config.Bucket': - return [(value) => value.trim().isNotEmpty]; + return [trimNotEmptyValidator]; case 'S3Config.SecretKey': - return [(value) => value.trim().isNotEmpty]; + return [trimNotEmptyValidator]; case 'S3Config.URL': - return [(value) => Uri.tryParse(value) != null]; + return [trimNotEmptyValidator, (value) => Uri.tryParse(value) != null]; case 'SiaConfig.Bucket': - return [(value) => value.trim().isNotEmpty]; + return [trimNotEmptyValidator]; } return []; @@ -146,18 +170,19 @@ bool validateSettings( String? rootKey, }) { settings.forEach((key, value) { - final checkKey = rootKey == null ? key : '$rootKey.$key'; + final settingKey = rootKey == null ? key : '$rootKey.$key'; if (value is Map) { validateSettings( value as Map, failed, - rootKey: checkKey, + rootKey: settingKey, ); } else { - for (var validator in getSettingValidators(checkKey)) { - if (!validator(value.toString())) { - failed.add(checkKey); + for (var validator in getSettingValidators(settingKey)) { + if (validator(value.toString())) { + continue; } + failed.add(settingKey); } } }); diff --git a/web/repertory/lib/screens/add_mount_screen.dart b/web/repertory/lib/screens/add_mount_screen.dart index ccb4ad5f..87a0c844 100644 --- a/web/repertory/lib/screens/add_mount_screen.dart +++ b/web/repertory/lib/screens/add_mount_screen.dart @@ -1,7 +1,8 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import 'package:repertory/constants.dart'; +import 'package:repertory/constants.dart' as constants; import 'package:repertory/helpers.dart'; import 'package:repertory/models/mount.dart'; import 'package:repertory/models/mount_list.dart'; @@ -17,8 +18,6 @@ class AddMountScreen extends StatefulWidget { } class _AddMountScreenState extends State { - static const _padding = 15.0; - Mount? _mount; final _mountNameController = TextEditingController(); String _mountType = ""; @@ -50,7 +49,7 @@ class _AddMountScreenState extends State { ], ), body: Padding( - padding: const EdgeInsets.all(_padding), + padding: const EdgeInsets.all(constants.padding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -58,43 +57,44 @@ class _AddMountScreenState extends State { children: [ Card( child: Padding( - padding: const EdgeInsets.all(_padding), + padding: const EdgeInsets.all(constants.padding), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const Text('Provider Type'), - const SizedBox(width: _padding), + const SizedBox(width: constants.padding), DropdownButton( value: _mountType, onChanged: (mountType) => _handleChange(mountType ?? ''), items: - providerTypeList.map>(( - item, - ) { - return DropdownMenuItem( - value: item, - child: Text(item), - ); - }).toList(), + constants.providerTypeList + .map>((item) { + return DropdownMenuItem( + value: item, + child: Text(item), + ); + }) + .toList(), ), ], ), ), ), - if (_mountType.isNotEmpty) const SizedBox(height: _padding), + if (_mountType.isNotEmpty) + const SizedBox(height: constants.padding), if (_mountType.isNotEmpty) Card( child: Padding( - padding: const EdgeInsets.all(_padding), + padding: const EdgeInsets.all(constants.padding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const Text('Configuration Name'), - const SizedBox(width: _padding), + const SizedBox(width: constants.padding), TextField( autofocus: true, controller: _mountNameController, @@ -112,7 +112,7 @@ class _AddMountScreenState extends State { Expanded( child: Card( child: Padding( - padding: const EdgeInsets.all(_padding), + padding: const EdgeInsets.all(constants.padding), child: MountSettingsWidget( isAdd: true, mount: _mount!, @@ -123,33 +123,60 @@ class _AddMountScreenState extends State { ), ), if (_mount != null) - ElevatedButton.icon( - onPressed: () { - List failed = []; - if (!validateSettings(_settings[_mountType]!, failed)) { - for (var key in failed) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - "'$key' is not valid", - textAlign: TextAlign.center, - ), - ), + Builder( + builder: (context) { + return ElevatedButton.icon( + onPressed: () { + final mountList = Provider.of( + context, + listen: false, ); - } - return; - } - Provider.of(context, listen: false).add( - _mountType, - _mountNameController.text, - _settings[_mountType]!, + List failed = []; + if (!validateSettings(_settings[_mountType]!, failed)) { + for (var key in failed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "'$key' is not valid", + textAlign: TextAlign.center, + ), + ), + ); + } + return; + } + + final existingMount = mountList.items.firstWhereOrNull( + (item) => + item.name.toLowerCase() == + _mountNameController.text.toLowerCase(), + ); + + if (existingMount != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "'${_mountNameController.text}' already exists", + textAlign: TextAlign.center, + ), + ), + ); + return; + } + + mountList.add( + _mountType, + _mountNameController.text, + _settings[_mountType]!, + ); + + Navigator.pop(context); + }, + label: const Text('Add'), + icon: Icon(Icons.add), ); - - Navigator.pop(context); }, - label: const Text('Add'), - icon: Icon(Icons.add), ), ], ), diff --git a/web/repertory/lib/widgets/mount_settings.dart b/web/repertory/lib/widgets/mount_settings.dart index 6d8cbe2a..feabe18c 100644 --- a/web/repertory/lib/widgets/mount_settings.dart +++ b/web/repertory/lib/widgets/mount_settings.dart @@ -1,7 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:repertory/constants.dart'; +import 'package:repertory/constants.dart' as constants; import 'package:repertory/helpers.dart' show Validator, getSettingValidators; import 'package:repertory/models/mount.dart'; import 'package:settings_ui/settings_ui.dart'; @@ -26,8 +26,6 @@ class MountSettingsWidget extends StatefulWidget { } class _MountSettingsWidgetState extends State { - static const _padding = 15.0; - void _addBooleanSetting(list, root, key, value, isAdvanced) { if (!isAdvanced || widget.showAdvanced) { list.add( @@ -264,7 +262,7 @@ class _MountSettingsWidgetState extends State { obscuringCharacter: '*', onChanged: (value) => updatedValue1 = value, ), - const SizedBox(height: _padding), + const SizedBox(height: constants.padding), TextField( autofocus: false, controller: TextEditingController(text: updatedValue2), @@ -409,7 +407,7 @@ class _MountSettingsWidgetState extends State { widget.settings, key, value, - databaseTypeList, + constants.databaseTypeList, Icons.dataset, true, ); @@ -456,7 +454,7 @@ class _MountSettingsWidgetState extends State { widget.settings, key, value, - eventLevelList, + constants.eventLevelList, Icons.event, false, ); @@ -528,7 +526,7 @@ class _MountSettingsWidgetState extends State { widget.settings, key, value, - downloadTypeList, + constants.downloadTypeList, Icons.download, false, ); @@ -553,7 +551,7 @@ class _MountSettingsWidgetState extends State { widget.settings, key, value, - ringBufferSizeList, + constants.ringBufferSizeList, 512, Icons.animation, false, @@ -733,7 +731,7 @@ class _MountSettingsWidgetState extends State { widget.settings[key], subKey, subValue, - protocolTypeList, + constants.protocolTypeList, Icons.http, true, );