✨ Audit logs
This commit is contained in:
		| @@ -481,5 +481,6 @@ | ||||
|   "authPreferences": "Auth preferences", | ||||
|   "authPreferencesDesc": "Set the security behavior of your account", | ||||
|   "authMaximumAuthSteps": "Maximum authentication steps", | ||||
|   "authMaximumAuthStepsDesc": "The maximum number of authentication steps when logging in, higher value is more secure, lower value is more convenient; default is 2" | ||||
|   "authMaximumAuthStepsDesc": "The maximum number of authentication steps when logging in, higher value is more secure, lower value is more convenient; default is 2", | ||||
|   "auditLog": "Audit log" | ||||
| } | ||||
|   | ||||
| @@ -477,5 +477,6 @@ | ||||
|   "authPreferences": "安全偏好设置", | ||||
|   "authPreferencesDesc": "调整账号的安全行为模式", | ||||
|   "authMaximumAuthSteps": "最大认证步数", | ||||
|   "authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2" | ||||
|   "authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2", | ||||
|   "auditLog": "活动日志" | ||||
| } | ||||
|   | ||||
| @@ -166,6 +166,9 @@ PODS: | ||||
|     - Flutter | ||||
|   - flutter_secure_storage (6.0.0): | ||||
|     - Flutter | ||||
|   - flutter_udid (0.0.1): | ||||
|     - Flutter | ||||
|     - SAMKeychain | ||||
|   - flutter_webrtc (0.11.3): | ||||
|     - Flutter | ||||
|     - WebRTC-SDK (= 125.6422.04) | ||||
| @@ -259,6 +262,7 @@ PODS: | ||||
|     - PromisesObjC (= 2.4.0) | ||||
|   - protocol_handler_ios (0.0.1): | ||||
|     - Flutter | ||||
|   - SAMKeychain (1.5.3) | ||||
|   - screen_brightness_ios (0.1.0): | ||||
|     - Flutter | ||||
|   - SDWebImage (5.19.7): | ||||
| @@ -316,6 +320,7 @@ DEPENDENCIES: | ||||
|   - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) | ||||
|   - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) | ||||
|   - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) | ||||
|   - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) | ||||
|   - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) | ||||
|   - gal (from `.symlinks/plugins/gal/darwin`) | ||||
|   - image_cropper (from `.symlinks/plugins/image_cropper/ios`) | ||||
| @@ -364,6 +369,7 @@ SPEC REPOS: | ||||
|     - nanopb | ||||
|     - PromisesObjC | ||||
|     - PromisesSwift | ||||
|     - SAMKeychain | ||||
|     - SDWebImage | ||||
|     - sqlite3 | ||||
|     - SwiftyGif | ||||
| @@ -401,6 +407,8 @@ EXTERNAL SOURCES: | ||||
|     :path: ".symlinks/plugins/flutter_native_splash/ios" | ||||
|   flutter_secure_storage: | ||||
|     :path: ".symlinks/plugins/flutter_secure_storage/ios" | ||||
|   flutter_udid: | ||||
|     :path: ".symlinks/plugins/flutter_udid/ios" | ||||
|   flutter_webrtc: | ||||
|     :path: ".symlinks/plugins/flutter_webrtc/ios" | ||||
|   gal: | ||||
| @@ -480,6 +488,7 @@ SPEC CHECKSUMS: | ||||
|   flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 | ||||
|   flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 | ||||
|   flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 | ||||
|   flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04 | ||||
|   flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f | ||||
|   gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1 | ||||
|   GoogleAppMeasurement: 76d4f8b36b03bd8381fa9a7fe2cc7f99c0a2e93a | ||||
| @@ -501,6 +510,7 @@ SPEC CHECKSUMS: | ||||
|   PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 | ||||
|   PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 | ||||
|   protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990 | ||||
|   SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c | ||||
|   screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 | ||||
|   SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 | ||||
|   share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad | ||||
|   | ||||
							
								
								
									
										38
									
								
								lib/models/audit_log.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/models/audit_log.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import 'package:json_annotation/json_annotation.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
|  | ||||
| part 'audit_log.g.dart'; | ||||
|  | ||||
| @JsonSerializable() | ||||
| class AuditEvent { | ||||
|   int id; | ||||
|   DateTime createdAt; | ||||
|   DateTime updatedAt; | ||||
|   DateTime? deletedAt; | ||||
|   String type; | ||||
|   String target; | ||||
|   String location; | ||||
|   String ipAddress; | ||||
|   String userAgent; | ||||
|   Account account; | ||||
|   int accountId; | ||||
|  | ||||
|   AuditEvent({ | ||||
|     required this.id, | ||||
|     required this.createdAt, | ||||
|     required this.updatedAt, | ||||
|     required this.deletedAt, | ||||
|     required this.type, | ||||
|     required this.target, | ||||
|     required this.location, | ||||
|     required this.ipAddress, | ||||
|     required this.userAgent, | ||||
|     required this.account, | ||||
|     required this.accountId, | ||||
|   }); | ||||
|  | ||||
|   static AuditEvent fromJson(Map<String, dynamic> json) => | ||||
|       _$AuditEventFromJson(json); | ||||
|  | ||||
|   Map<String, dynamic> toJson() => _$AuditEventToJson(this); | ||||
| } | ||||
							
								
								
									
										38
									
								
								lib/models/audit_log.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/models/audit_log.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
|  | ||||
| part of 'audit_log.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // JsonSerializableGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| AuditEvent _$AuditEventFromJson(Map<String, dynamic> json) => AuditEvent( | ||||
|       id: (json['id'] as num).toInt(), | ||||
|       createdAt: DateTime.parse(json['created_at'] as String), | ||||
|       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||
|       deletedAt: json['deleted_at'] == null | ||||
|           ? null | ||||
|           : DateTime.parse(json['deleted_at'] as String), | ||||
|       type: json['type'] as String, | ||||
|       target: json['target'] as String, | ||||
|       location: json['location'] as String, | ||||
|       ipAddress: json['ip_address'] as String, | ||||
|       userAgent: json['user_agent'] as String, | ||||
|       account: Account.fromJson(json['account'] as Map<String, dynamic>), | ||||
|       accountId: (json['account_id'] as num).toInt(), | ||||
|     ); | ||||
|  | ||||
| Map<String, dynamic> _$AuditEventToJson(AuditEvent instance) => | ||||
|     <String, dynamic>{ | ||||
|       'id': instance.id, | ||||
|       'created_at': instance.createdAt.toIso8601String(), | ||||
|       'updated_at': instance.updatedAt.toIso8601String(), | ||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||
|       'type': instance.type, | ||||
|       'target': instance.target, | ||||
|       'location': instance.location, | ||||
|       'ip_address': instance.ipAddress, | ||||
|       'user_agent': instance.userAgent, | ||||
|       'account': instance.account.toJson(), | ||||
|       'account_id': instance.accountId, | ||||
|     }; | ||||
| @@ -6,6 +6,7 @@ import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/models/realm.dart'; | ||||
| import 'package:solian/screens/about.dart'; | ||||
| import 'package:solian/screens/account.dart'; | ||||
| import 'package:solian/screens/account/audit_log.dart'; | ||||
| import 'package:solian/screens/account/friend.dart'; | ||||
| import 'package:solian/screens/account/preferences/notifications.dart'; | ||||
| import 'package:solian/screens/account/preferences/security.dart'; | ||||
| @@ -275,6 +276,14 @@ abstract class AppRouter { | ||||
|           child: const AuthPreferencesScreen(), | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/audit', | ||||
|         name: 'auditLog', | ||||
|         builder: (context, state) => TitleShell( | ||||
|           state: state, | ||||
|           child: const AuditLogScreen(), | ||||
|         ), | ||||
|       ), | ||||
|       GoRoute( | ||||
|         path: '/account/view/:name', | ||||
|         name: 'accountProfilePage', | ||||
|   | ||||
| @@ -129,6 +129,15 @@ class _AccountScreenState extends State<AccountScreen> { | ||||
|                   AppRouter.instance.pushNamed('settings'); | ||||
|                 }, | ||||
|               ), | ||||
|               if (auth.isAuthorized.value) | ||||
|                 ListTile( | ||||
|                   leading: const Icon(Icons.event_repeat), | ||||
|                   contentPadding: const EdgeInsets.symmetric(horizontal: 34), | ||||
|                   title: Text('auditLog'.tr), | ||||
|                   onTap: () { | ||||
|                     AppRouter.instance.pushNamed('auditLog'); | ||||
|                   }, | ||||
|                 ), | ||||
|               if (auth.isAuthorized.value) | ||||
|                 ListTile( | ||||
|                   leading: const Icon(Icons.lock), | ||||
|   | ||||
							
								
								
									
										96
									
								
								lib/screens/account/audit_log.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								lib/screens/account/audit_log.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:solian/exceptions/request.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/audit_log.dart'; | ||||
| import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/widgets/relative_date.dart'; | ||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||
| import 'package:timeline_tile/timeline_tile.dart'; | ||||
|  | ||||
| class AuditLogScreen extends StatefulWidget { | ||||
|   const AuditLogScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<AuditLogScreen> createState() => _AuditLogScreenState(); | ||||
| } | ||||
|  | ||||
| class _AuditLogScreenState extends State<AuditLogScreen> { | ||||
|   bool _isBusy = true; | ||||
|  | ||||
|   int _totalEvent = 0; | ||||
|   List<AuditEvent> _events = List.empty(growable: true); | ||||
|  | ||||
|   Future<void> _getEvents() async { | ||||
|     if (!_isBusy) setState(() => _isBusy = true); | ||||
|  | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     final client = await auth.configureClient('id'); | ||||
|     final resp = | ||||
|         await client.get('/users/me/events?take=10&offset=${_events.length}'); | ||||
|     if (resp.statusCode != 200) { | ||||
|       context.showErrorDialog(RequestException(resp)); | ||||
|     } | ||||
|  | ||||
|     final result = PaginationResult.fromJson(resp.body); | ||||
|  | ||||
|     setState(() { | ||||
|       _totalEvent = result.count; | ||||
|       _events.addAll( | ||||
|         result.data?.map((x) => AuditEvent.fromJson(x)).toList() ?? | ||||
|             List.empty(), | ||||
|       ); | ||||
|       _isBusy = false; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _getEvents(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return InfiniteList( | ||||
|       itemCount: _events.length, | ||||
|       isLoading: _isBusy, | ||||
|       onFetchData: () { | ||||
|         _getEvents(); | ||||
|       }, | ||||
|       itemBuilder: (context, idx) { | ||||
|         final element = _events[idx]; | ||||
|         return TimelineTile( | ||||
|           isFirst: idx == 0, | ||||
|           isLast: _events.length - 1 == idx, | ||||
|           alignment: TimelineAlign.start, | ||||
|           endChild: Container( | ||||
|             child: Card( | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   Text( | ||||
|                     element.type, | ||||
|                     style: GoogleFonts.robotoMono(fontSize: 15), | ||||
|                   ), | ||||
|                   Row( | ||||
|                     children: [ | ||||
|                       RelativeDate(element.createdAt), | ||||
|                       const Gap(6), | ||||
|                       Text('·'), | ||||
|                       const Gap(6), | ||||
|                       RelativeDate(element.createdAt, isFull: true), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ], | ||||
|               ).paddingSymmetric(horizontal: 12, vertical: 8), | ||||
|             ).paddingOnly(left: 16), | ||||
|           ), | ||||
|         ).paddingSymmetric(horizontal: 18); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -2034,6 +2034,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.7.0" | ||||
|   timeline_tile: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: timeline_tile | ||||
|       sha256: "85ec2023c67137397c2812e3e848b2fb20b410b67cd9aff304bb5480c376fc0c" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|   timezone: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -84,6 +84,7 @@ dependencies: | ||||
|   in_app_review: ^2.0.9 | ||||
|   syntax_highlight: ^0.4.0 | ||||
|   flutter_udid: ^3.0.0 | ||||
|   timeline_tile: ^2.0.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user