[client] Show last used info

This commit is contained in:
Abhishek Shroff
2025-08-05 01:00:16 +05:30
parent 21084b03ea
commit c8ea9aa698
4 changed files with 67 additions and 28 deletions

View File

@@ -6,6 +6,7 @@ class ApiKey {
final DateTime? expires;
final String description;
final List<String> scopes;
final LastUsedInfo lastUsed;
ApiKey({
required this.id,
@@ -13,9 +14,18 @@ class ApiKey {
required this.expires,
required this.description,
required this.scopes,
required this.lastUsed,
});
}
class LastUsedInfo {
final int timestamp;
final String ip;
final String device;
LastUsedInfo(this.timestamp, this.ip, this.device);
}
class ListApiKeysResponse extends PhylumApiSuccessResponse {
final List<ApiKey> keys;
@@ -30,11 +40,16 @@ class ListApiKeysResponse extends PhylumApiSuccessResponse {
expires: k['expires'] == 0 ? null : DateTime.fromMillisecondsSinceEpoch(k['expires'] * 1000),
description: k['description'],
scopes: (k['scopes'] as List).cast<String>(),
lastUsed: _parseLastUsed((k['last_used'] as Map).cast<String, dynamic>()),
);
}).toList(growable: false);
return ListApiKeysResponse._(keys: keys);
}
static LastUsedInfo _parseLastUsed(Map<String, dynamic> data) {
return LastUsedInfo(data['timestamp'], data['ip'], data['device']);
}
@override
Future<void> process(PhylumAccount account) => SynchronousFuture(null);
}

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:moment_dart/moment_dart.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/requests/api_key_generate_request.dart';
import 'package:phylum/libphylum/requests/api_keys_list_request.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/ui/profile/generate_api_key_dialog.dart';
import 'package:phylum/util/dialogs.dart';
import 'package:phylum/util/time.dart';
import 'package:provider/provider.dart';
class ApiKeysView extends StatefulWidget {
@@ -35,35 +35,33 @@ class _ApiKeysViewState extends State<ApiKeysView> {
);
}
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton.icon(
onPressed: () => createKey(context),
label: Text('Generate New API Key'),
icon: Icon(Icons.add),
),
for (final k in keys)
Card(
margin: EdgeInsets.all(2.0),
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
k.description,
style: const TextStyle(fontSize: 18),
),
Text('Created ${k.created.formatLong()}', style: const TextStyle(fontSize: 15)),
Text('Expires ${k.expires?.formatLong() ?? 'Never'}', style: const TextStyle(fontSize: 15)),
Text('Scopes: ${k.scopes.join(', ')}', style: const TextStyle(fontSize: 15)),
],
),
return ListTileTheme(
data: ListTileThemeData(
tileColor: Theme.of(context).colorScheme.surfaceBright,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: ElevatedButton.icon(
onPressed: () => createKey(context),
label: Text('Generate New API Key'),
icon: Icon(Icons.add),
),
),
],
...ListTile.divideTiles(context: context, tiles: [
for (final k in keys)
ListTile(
title: Text(k.description),
subtitle: k.lastUsed.used
? Text('${k.lastUsed.device} \u2022 ${k.lastUsed.fromNow()}')
: const Text('Never Used'),
),
]),
],
),
);
}
@@ -75,6 +73,12 @@ class _ApiKeysViewState extends State<ApiKeysView> {
);
if (response is ListApiKeysResponse) {
response.keys.sort((a, b) {
if (a.lastUsed.timestamp == b.lastUsed.timestamp) {
b.created.compareTo(a.created);
}
return b.lastUsed.timestamp - a.lastUsed.timestamp;
});
setState(() => keys = response.keys);
} else {
setState(() => error = response.description);
@@ -147,3 +151,14 @@ class _ApiKeysViewState extends State<ApiKeysView> {
}
}
}
extension on LastUsedInfo {
String fromNow() {
if (timestamp == 0) {
return "Never";
}
return Moment.fromMillisecondsSinceEpoch(timestamp * 1000).fromNow();
}
bool get used => timestamp != 0;
}

View File

@@ -698,6 +698,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
moment_dart:
dependency: "direct main"
description:
name: moment_dart
sha256: "74e4241d412b87aac99fc2eaaf948e682c9c876b31850b82e35bdf2606fdf178"
url: "https://pub.dev"
source: hosted
version: "5.2.3"
nested:
dependency: transitive
description:

View File

@@ -28,6 +28,7 @@ dependencies:
http_parser:
intl:
logger:
moment_dart:
mime:
offtheline:
git: