Finish KeyToken operations
This commit is contained in:
216
flutter/lib/pages/keytoken_list/keytoken_create_modal.dart
Normal file
216
flutter/lib/pages/keytoken_list/keytoken_create_modal.dart
Normal file
@@ -0,0 +1,216 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||
import 'package:simplecloudnotifier/components/error_display/error_display.dart';
|
||||
import 'package:simplecloudnotifier/models/channel.dart';
|
||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||
|
||||
class KeyTokenCreateDialog extends StatefulWidget {
|
||||
final void Function(KeyToken, String) onCreated;
|
||||
|
||||
const KeyTokenCreateDialog({
|
||||
required this.onCreated,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_KeyTokenCreateDialogState createState() => _KeyTokenCreateDialogState();
|
||||
}
|
||||
|
||||
class _KeyTokenCreateDialogState extends State<KeyTokenCreateDialog> {
|
||||
TextEditingController _ctrlName = TextEditingController();
|
||||
Set<String> selectedPermissions = {'CS'};
|
||||
|
||||
ImmediateFuture<List<Channel>> _futureOwnedChannels = ImmediateFuture.ofPending();
|
||||
|
||||
bool allChannels = true;
|
||||
Set<String> selectedChannels = new Set<String>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
setState(() {
|
||||
_futureOwnedChannels = ImmediateFuture.ofFuture(APIClient.getChannelList(userAcc, ChannelSelector.owned).then((p) => p.map((c) => c.channel).toList()));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ctrlName.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Create new key'),
|
||||
content: Container(
|
||||
width: 0,
|
||||
height: 400,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildNameCtrl(context),
|
||||
SizedBox(height: 32),
|
||||
_buildPermissionCtrl(context),
|
||||
SizedBox(height: 32),
|
||||
_buildChannelCtrl(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(textStyle: Theme.of(context).textTheme.labelLarge),
|
||||
child: const Text('Create'),
|
||||
onPressed: _create,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNameCtrl(BuildContext context) {
|
||||
return TextField(
|
||||
controller: _ctrlName,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Key name',
|
||||
hintText: 'Enter a name for the new key',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPermissionCtrl(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('Permissions:', style: Theme.of(context).textTheme.labelLarge, textAlign: TextAlign.start),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
itemBuilder: (builder, index) {
|
||||
final txt = (['Admin', 'Read messages', 'Send messages', 'Read userdata'])[index];
|
||||
final prm = (['A', 'CR', 'CS', 'UR'])[index];
|
||||
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
visualDensity: VisualDensity(horizontal: 0, vertical: -4),
|
||||
title: Text(txt),
|
||||
leading: Icon(
|
||||
selectedPermissions.contains(prm) ? Icons.check_box : Icons.check_box_outline_blank,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selectedPermissions.contains(prm)) {
|
||||
selectedPermissions.remove(prm);
|
||||
} else {
|
||||
selectedPermissions.add(prm);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: 4,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChannelCtrl(BuildContext context) {
|
||||
return FutureBuilder<List<Channel>>(
|
||||
future: _futureOwnedChannels.future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return ErrorDisplay(errorMessage: '${snapshot.error}');
|
||||
}
|
||||
|
||||
final ownChannels = snapshot.data!;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('Channels:', style: Theme.of(context).textTheme.labelLarge, textAlign: TextAlign.start),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
visualDensity: VisualDensity(horizontal: 0, vertical: -4),
|
||||
title: Text('All Channels'),
|
||||
leading: Icon(
|
||||
allChannels ? Icons.check_box : Icons.check_box_outline_blank,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
allChannels = !allChannels;
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
if (!allChannels)
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
itemBuilder: (builder, index) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
visualDensity: VisualDensity(horizontal: 0, vertical: -4),
|
||||
title: Text(ownChannels[index].displayName),
|
||||
leading: Icon(
|
||||
selectedChannels.contains(ownChannels[index].channelID) ? Icons.check_box : Icons.check_box_outline_blank,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selectedChannels.contains(ownChannels[index].channelID)) {
|
||||
selectedChannels.remove(ownChannels[index].channelID);
|
||||
} else {
|
||||
selectedChannels.add(ownChannels[index].channelID);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: ownChannels.length,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _create() async {
|
||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
if (!userAcc.isAuth()) return;
|
||||
|
||||
if (_ctrlName.text.isEmpty) {
|
||||
Toaster.error('Missing data', 'Please enter a name for the key');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final perm = selectedPermissions.join(';');
|
||||
final channels = allChannels ? <String>[] : selectedChannels.toList();
|
||||
|
||||
var kt = await APIClient.createKeyToken(userAcc, _ctrlName.text, perm, allChannels, channels: channels);
|
||||
Toaster.success('Success', 'Key created successfully');
|
||||
Navigator.of(context).pop();
|
||||
widget.onCreated(kt.keyToken, kt.token);
|
||||
} catch (exc, trace) {
|
||||
ApplicationLog.error('Failed to create keytoken: ' + exc.toString(), trace: trace);
|
||||
Toaster.error("Error", 'Failed to create key: ${exc.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
97
flutter/lib/pages/keytoken_list/keytoken_created_modal.dart
Normal file
97
flutter/lib/pages/keytoken_list/keytoken_created_modal.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:simplecloudnotifier/components/badge_display/badge_display.dart';
|
||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||
|
||||
class KeyTokenCreatedModal extends StatelessWidget {
|
||||
final KeyToken keytoken;
|
||||
final String tokenValue;
|
||||
|
||||
const KeyTokenCreatedModal({
|
||||
Key? key,
|
||||
required this.keytoken,
|
||||
required this.tokenValue,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('A new key was created'),
|
||||
content: Container(
|
||||
width: 0,
|
||||
height: 350,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidIdCardClip,
|
||||
title: 'KeyTokenID',
|
||||
values: [keytoken.keytokenID],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidInputText,
|
||||
title: 'Name',
|
||||
values: [keytoken.name],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidShieldKeyhole,
|
||||
title: 'Permissions',
|
||||
values: _formatPermissions(keytoken.permissions),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const BadgeDisplay(
|
||||
text: "Please copy and save the token now, it cannot be retrieved later.",
|
||||
icon: null,
|
||||
mode: BadgeMode.warn,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidKey,
|
||||
title: 'Token',
|
||||
values: [tokenValue.substring(0, 12) + '...'],
|
||||
iconActions: [(FontAwesomeIcons.copy, null, _copy)],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('Close'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _formatPermissions(String v) {
|
||||
var splt = v.split(';');
|
||||
|
||||
if (splt.length == 0) return ["None"];
|
||||
|
||||
List<String> result = [];
|
||||
|
||||
if (splt.contains("A")) result.add("Admin");
|
||||
if (splt.contains("UR")) result.add("Read Account");
|
||||
if (splt.contains("CR")) result.add("Read Messages");
|
||||
if (splt.contains("CS")) result.add("Send Messages");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void _copy() {
|
||||
Clipboard.setData(new ClipboardData(text: tokenValue));
|
||||
Toaster.info("Clipboard", 'Copied text to Clipboard');
|
||||
print('================= [CLIPBOARD] =================\n${tokenValue}\n================= [/CLIPBOARD] =================');
|
||||
}
|
||||
}
|
@@ -1,9 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||
import 'package:simplecloudnotifier/pages/keytoken_list/keytoken_create_modal.dart';
|
||||
import 'package:simplecloudnotifier/pages/keytoken_list/keytoken_created_modal.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
import 'package:simplecloudnotifier/pages/keytoken_list/keytoken_list_item.dart';
|
||||
@@ -81,6 +84,27 @@ class _KeyTokenListPageState extends State<KeyTokenListPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: 'fab_keytokenlist_plus',
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => KeyTokenCreateDialog(onCreated: _created),
|
||||
);
|
||||
},
|
||||
child: const Icon(FontAwesomeIcons.plus),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _created(KeyToken token, String tokValue) {
|
||||
setState(() {
|
||||
_pagingController.itemList?.insert(0, token);
|
||||
});
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => KeyTokenCreatedModal(keytoken: token, tokenValue: tokValue),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user