✨ Rotate bot key
This commit is contained in:
@@ -904,5 +904,8 @@
|
|||||||
"revoke": "Revoke",
|
"revoke": "Revoke",
|
||||||
"keyName": "Key Name",
|
"keyName": "Key Name",
|
||||||
"newKeyGenerated": "New Key Generated",
|
"newKeyGenerated": "New Key Generated",
|
||||||
"copyKeyHint": "Please copy this key and store it somewhere safe. You will not be able to see it again."
|
"copyKeyHint": "Please copy this key and store it somewhere safe. You will not be able to see it again.",
|
||||||
|
"rotateKey": "Rotate Key",
|
||||||
|
"rotateBotKey": "Rotate Bot Key",
|
||||||
|
"rotateBotKeyHint": "Are you sure you want to rotate this key? The old key will become invalid immediately. This action cannot be undone."
|
||||||
}
|
}
|
@@ -6,6 +6,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/bot_key.dart';
|
import 'package:island/models/bot_key.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/services/time.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/response.dart';
|
import 'package:island/widgets/response.dart';
|
||||||
@@ -95,7 +96,6 @@ class BotKeysScreen extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => SheetScaffold(
|
(context) => SheetScaffold(
|
||||||
heightFactor: 0.65,
|
|
||||||
titleText: 'newBotKey'.tr(),
|
titleText: 'newBotKey'.tr(),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
@@ -136,6 +136,28 @@ class BotKeysScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rotateKey(String keyId) {
|
||||||
|
showConfirmAlert('rotateBotKeyHint'.tr(), 'rotateBotKey'.tr()).then((
|
||||||
|
confirm,
|
||||||
|
) async {
|
||||||
|
if (confirm) {
|
||||||
|
try {
|
||||||
|
if (context.mounted) showLoadingModal(context);
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final resp = await client.post(
|
||||||
|
'/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys/$keyId/rotate',
|
||||||
|
);
|
||||||
|
final rotatedApiKey = SnAccountApiKey.fromJson(resp.data);
|
||||||
|
showNewKeySheet(rotatedApiKey);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err.toString());
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void revokeKey(String keyId) {
|
void revokeKey(String keyId) {
|
||||||
showConfirmAlert('revokeBotKeyHint'.tr(), 'revokeBotKey'.tr()).then((
|
showConfirmAlert('revokeBotKeyHint'.tr(), 'revokeBotKey'.tr()).then((
|
||||||
confirm,
|
confirm,
|
||||||
@@ -158,80 +180,99 @@ class BotKeysScreen extends HookConsumerWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return keys.when(
|
||||||
children: [
|
data: (data) {
|
||||||
ListTile(
|
return Column(
|
||||||
leading: const Icon(Symbols.add),
|
children: [
|
||||||
title: Text('newBotKey'.tr()),
|
ListTile(
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
leading: const Icon(Symbols.add),
|
||||||
onTap: createKey,
|
title: Text('newBotKey'.tr()),
|
||||||
),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
const Divider(height: 1),
|
onTap: createKey,
|
||||||
Expanded(
|
),
|
||||||
child: keys.when(
|
const Divider(height: 1),
|
||||||
data: (data) {
|
Expanded(
|
||||||
if (data.isEmpty) {
|
child:
|
||||||
return Center(child: Text('noBotKeys'.tr()));
|
data.isEmpty
|
||||||
}
|
? Center(child: Text('noBotKeys'.tr()))
|
||||||
return RefreshIndicator(
|
: RefreshIndicator(
|
||||||
onRefresh:
|
onRefresh:
|
||||||
() => ref.refresh(
|
() => ref.refresh(
|
||||||
botKeysProvider(publisherName, projectId, botId).future,
|
botKeysProvider(
|
||||||
),
|
publisherName,
|
||||||
child: ListView.builder(
|
projectId,
|
||||||
padding: EdgeInsets.zero,
|
botId,
|
||||||
itemCount: data.length,
|
).future,
|
||||||
itemBuilder: (context, index) {
|
),
|
||||||
final apiKey = data[index];
|
child: ListView.builder(
|
||||||
return ListTile(
|
padding: EdgeInsets.zero,
|
||||||
title: Text(apiKey.label),
|
itemCount: data.length,
|
||||||
subtitle: Text(
|
itemBuilder: (context, index) {
|
||||||
'Created: ${DateFormat.yMMMd().format(apiKey.createdAt)}',
|
final apiKey = data[index];
|
||||||
),
|
return ListTile(
|
||||||
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
title: Text(apiKey.label),
|
||||||
trailing: PopupMenuButton(
|
subtitle: Text(apiKey.createdAt.formatSystem()),
|
||||||
itemBuilder:
|
contentPadding: EdgeInsets.only(
|
||||||
(context) => [
|
left: 16,
|
||||||
PopupMenuItem(
|
right: 12,
|
||||||
value: 'revoke',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Symbols.delete,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
Text(
|
|
||||||
'revoke'.tr(),
|
|
||||||
style: TextStyle(color: Colors.red),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
trailing: PopupMenuButton(
|
||||||
onSelected: (value) {
|
itemBuilder:
|
||||||
if (value == 'revoke') {
|
(context) => [
|
||||||
revokeKey(apiKey.id);
|
PopupMenuItem(
|
||||||
}
|
value: 'rotate',
|
||||||
},
|
child: Row(
|
||||||
),
|
children: [
|
||||||
);
|
const Icon(Symbols.refresh),
|
||||||
},
|
const Gap(12),
|
||||||
),
|
Text('rotateKey'.tr()),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
),
|
||||||
error:
|
PopupMenuItem(
|
||||||
(err, stack) => ResponseErrorWidget(
|
value: 'revoke',
|
||||||
error: err,
|
child: Row(
|
||||||
onRetry:
|
children: [
|
||||||
() => ref.invalidate(
|
const Icon(
|
||||||
botKeysProvider(publisherName, projectId, botId),
|
Symbols.delete,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
Text(
|
||||||
|
'revoke'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'rotate') {
|
||||||
|
rotateKey(apiKey.id);
|
||||||
|
} else if (value == 'revoke') {
|
||||||
|
revokeKey(apiKey.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(err, stack) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry:
|
||||||
|
() => ref.invalidate(
|
||||||
|
botKeysProvider(publisherName, projectId, botId),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user