Create management portal in Flutter #39

This commit is contained in:
Scott E. Graves 2025-03-05 08:17:27 -06:00
parent e35f43af97
commit b74160bfb3
10 changed files with 136 additions and 56 deletions

View File

@ -12,6 +12,7 @@
### Changes from v2.0.4-rc ### Changes from v2.0.4-rc
* Continue documentation updates * Continue documentation updates
* Require `--name,-na` option for encryption provider
## v2.0.4-rc ## v2.0.4-rc

View File

@ -36,9 +36,8 @@ template <typename drive> inline void help(std::vector<const char *> args) {
std::cout << " -di,--drive_information Display mounted drive " std::cout << " -di,--drive_information Display mounted drive "
"information" "information"
<< std::endl; << std::endl;
std::cout std::cout << " -na,--name Unique configuration "
<< " -na,--name Unique name for S3 or Sia " "name [Required for Encrypt, S3 and Sia]"
"instance [Required]"
<< std::endl; << std::endl;
std::cout << " -s3,--s3 Enables S3 mode" std::cout << " -s3,--s3 Enables S3 mode"
<< std::endl; << std::endl;

View File

@ -105,7 +105,8 @@ auto main(int argc, char **argv) -> int {
} }
} }
} }
} else if ((prov == provider_type::s3) || (prov == provider_type::sia)) { } else if ((prov == provider_type::s3) || (prov == provider_type::sia) ||
(prov == provider_type::encrypt)) {
std::string data; std::string data;
res = utils::cli::parse_string_option( res = utils::cli::parse_string_option(
args, utils::cli::options::name_option, data); args, utils::cli::options::name_option, data);
@ -115,9 +116,9 @@ auto main(int argc, char **argv) -> int {
if (prov == provider_type::sia) { if (prov == provider_type::sia) {
unique_id = "default"; unique_id = "default";
} else { } else {
std::cerr << "Name of " std::cerr << "Configuration name for '"
<< app_config::get_provider_display_name(prov) << app_config::get_provider_display_name(prov)
<< " instance not provided" << std::endl; << "' was not provided" << std::endl;
res = exit_code::invalid_syntax; res = exit_code::invalid_syntax;
} }
} }

View File

@ -1,4 +1,6 @@
autofocus
cupertino cupertino
cupertinoicons cupertinoicons
fromargb fromargb
onetwothree onetwothree
rocksdb

View File

@ -1,11 +1,9 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:repertory/constants.dart' as constants; import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart'; import 'package:repertory/helpers.dart';
import 'package:repertory/models/mount.dart'; import 'package:repertory/models/mount.dart';
import 'package:repertory/models/mount_list.dart'; import 'package:repertory/models/mount_list.dart';
import 'package:repertory/types/mount_config.dart';
import 'package:repertory/widgets/add_mount_widget.dart'; import 'package:repertory/widgets/add_mount_widget.dart';
import 'package:repertory/widgets/mount_list_widget.dart'; import 'package:repertory/widgets/mount_list_widget.dart';
import 'package:repertory/widgets/mount_settings.dart'; import 'package:repertory/widgets/mount_settings.dart';
@ -20,7 +18,7 @@ class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
@override @override
Widget build(BuildContext context) { Widget build(context) {
return MaterialApp( return MaterialApp(
title: constants.appTitle, title: constants.appTitle,
theme: ThemeData( theme: ThemeData(
@ -78,11 +76,22 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
bool _allowAdd = true; bool _allowAdd = true;
String _mountType = "S3"; String? _apiAuth;
String? _bucket;
String _mountType = "Encrypt";
String _mountName = ""; String _mountName = "";
String? _path;
void _resetData() {
_apiAuth = null;
_bucket = null;
_mountType = "Encrypt";
_mountName = "";
_path = null;
}
@override @override
Widget build(BuildContext context) { Widget build(context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
@ -96,25 +105,20 @@ class _MyHomePageState extends State<MyHomePage> {
? () { ? () {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('Add Mount'), title: const Text('Add Mount'),
content: Consumer<MountList>( content: Consumer<MountList>(
builder: (_, mountList, __) { builder: (_, MountList mountList, __) {
return AddMountWidget( return AddMountWidget(
allowEncrypt:
mountList.items.firstWhereOrNull(
(MountConfig item) =>
item.type.toLowerCase() == "encrypt",
) ==
null,
mountType: _mountType, mountType: _mountType,
onNameChanged: (mountName) { onApiAuthChanged: (apiAuth) => _apiAuth = apiAuth,
_mountName = mountName ?? ""; onBucketChanged: (bucket) => _bucket = bucket,
}, onNameChanged:
onTypeChanged: (mountType) { (mountName) => _mountName = mountName ?? "",
_mountType = mountType ?? "S3"; onPathChanged: (path) => _path = path,
}, onTypeChanged:
(mountType) => _mountType = mountType ?? "S3",
); );
}, },
), ),
@ -122,8 +126,7 @@ class _MyHomePageState extends State<MyHomePage> {
TextButton( TextButton(
child: const Text('Cancel'), child: const Text('Cancel'),
onPressed: () { onPressed: () {
_mountType = "S3"; _resetData();
_mountName = "";
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
@ -133,10 +136,15 @@ class _MyHomePageState extends State<MyHomePage> {
setState(() => _allowAdd = false); setState(() => _allowAdd = false);
Provider.of<MountList>(context, listen: false) Provider.of<MountList>(context, listen: false)
.add(_mountType, _mountName) .add(
_mountType,
_mountName,
apiAuth: _apiAuth,
bucket: _bucket,
path: _path,
)
.then((_) { .then((_) {
_mountType = "S3"; _resetData();
_mountName = "";
setState(() { setState(() {
_allowAdd = true; _allowAdd = true;
}); });
@ -159,4 +167,13 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
); );
} }
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
} }

View File

@ -48,11 +48,17 @@ class MountList with ChangeNotifier {
}); });
} }
Future<void> add(String type, String name) async { Future<void> add(
String type,
String name, {
String? apiAuth,
String? bucket,
String? path,
}) async {
await http.post( await http.post(
Uri.parse( Uri.parse(
Uri.encodeFull( Uri.encodeFull(
'${getBaseUri()}/api/v1/add_mount?name=$name&type=$type', '${getBaseUri()}/api/v1/add_mount?name=$name&type=$type&bucket=$bucket&path=$path&apiAuth=$apiAuth',
), ),
), ),
); );

View File

@ -1,30 +1,30 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AddMountWidget extends StatefulWidget { class AddMountWidget extends StatefulWidget {
final bool allowEncrypt;
final String mountType; final String mountType;
final void Function(String? newApiAuth) onApiAuthChanged;
final void Function(String? newBucket) onBucketChanged;
final void Function(String? newName) onNameChanged; final void Function(String? newName) onNameChanged;
final void Function(String? newPath) onPathChanged;
final void Function(String? newType) onTypeChanged; final void Function(String? newType) onTypeChanged;
final _items = <String>["S3", "Sia"]; const AddMountWidget({
AddMountWidget({
super.key, super.key,
required this.allowEncrypt,
required this.mountType, required this.mountType,
required this.onApiAuthChanged,
required this.onBucketChanged,
required this.onNameChanged, required this.onNameChanged,
required this.onPathChanged,
required this.onTypeChanged, required this.onTypeChanged,
}) { });
if (allowEncrypt) {
_items.insert(0, "Encrypt");
}
}
@override @override
State<AddMountWidget> createState() => _AddMountWidgetState(); State<AddMountWidget> createState() => _AddMountWidgetState();
} }
class _AddMountWidgetState extends State<AddMountWidget> { class _AddMountWidgetState extends State<AddMountWidget> {
static const _items = <String>["Encrypt", "S3", "Sia"];
String? _mountType; String? _mountType;
@override @override
@ -35,6 +35,7 @@ class _AddMountWidgetState extends State<AddMountWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mountTypeLower = _mountType?.toLowerCase();
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -62,7 +63,7 @@ class _AddMountWidgetState extends State<AddMountWidget> {
widget.onTypeChanged(value); widget.onTypeChanged(value);
}, },
items: items:
widget._items.map<DropdownMenuItem<String>>((item) { _items.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: item, value: item,
child: Text(item), child: Text(item),
@ -72,7 +73,6 @@ class _AddMountWidgetState extends State<AddMountWidget> {
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
if (_mountType?.toLowerCase() != 'encrypt')
Text( Text(
'Configuration Name', 'Configuration Name',
textAlign: TextAlign.left, textAlign: TextAlign.left,
@ -81,12 +81,53 @@ class _AddMountWidgetState extends State<AddMountWidget> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
if (_mountType?.toLowerCase() != 'encrypt')
TextField( TextField(
autofocus: true, autofocus: true,
decoration: InputDecoration(), decoration: InputDecoration(),
onChanged: widget.onNameChanged, onChanged: widget.onNameChanged,
), ),
if (mountTypeLower == 'encrypt')
Text(
'Path',
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
if (mountTypeLower == 'encrypt')
TextField(
decoration: InputDecoration(),
onChanged: widget.onPathChanged,
),
if (mountTypeLower == 'sia')
Text(
'ApiAuth',
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
if (mountTypeLower == 'sia')
TextField(
decoration: InputDecoration(),
onChanged: widget.onApiAuthChanged,
),
if (mountTypeLower == 'sia' || mountTypeLower == 's3')
Text(
'Bucket',
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
if (mountTypeLower == 'sia' || mountTypeLower == 's3')
TextField(
decoration: InputDecoration(),
onChanged: widget.onBucketChanged,
),
], ],
); );
} }

View File

@ -4,14 +4,9 @@ import 'package:repertory/models/mount.dart';
import 'package:repertory/models/mount_list.dart'; import 'package:repertory/models/mount_list.dart';
import 'package:repertory/widgets/mount_widget.dart'; import 'package:repertory/widgets/mount_widget.dart';
class MountListWidget extends StatefulWidget { class MountListWidget extends StatelessWidget {
const MountListWidget({super.key}); const MountListWidget({super.key});
@override
State<MountListWidget> createState() => _MountListWidgetState();
}
class _MountListWidgetState extends State<MountListWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<MountList>( return Consumer<MountList>(

View File

@ -565,4 +565,13 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
_settings = jsonDecode(jsonEncode(widget.mount.mountConfig.settings)); _settings = jsonDecode(jsonEncode(widget.mount.mountConfig.settings));
super.initState(); super.initState();
} }
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
} }

View File

@ -109,4 +109,13 @@ class _MountWidgetState extends State<MountWidget> {
Provider.of<Mount>(context, listen: false).refresh(); Provider.of<Mount>(context, listen: false).refresh();
}); });
} }
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
} }