✨ Audit logs
This commit is contained in:
parent
2aa699547c
commit
6c32d76f78
@ -481,5 +481,6 @@
|
|||||||
"authPreferences": "Auth preferences",
|
"authPreferences": "Auth preferences",
|
||||||
"authPreferencesDesc": "Set the security behavior of your account",
|
"authPreferencesDesc": "Set the security behavior of your account",
|
||||||
"authMaximumAuthSteps": "Maximum authentication steps",
|
"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": "安全偏好设置",
|
"authPreferences": "安全偏好设置",
|
||||||
"authPreferencesDesc": "调整账号的安全行为模式",
|
"authPreferencesDesc": "调整账号的安全行为模式",
|
||||||
"authMaximumAuthSteps": "最大认证步数",
|
"authMaximumAuthSteps": "最大认证步数",
|
||||||
"authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2"
|
"authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2",
|
||||||
|
"auditLog": "活动日志"
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,9 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- flutter_secure_storage (6.0.0):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_udid (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- SAMKeychain
|
||||||
- flutter_webrtc (0.11.3):
|
- flutter_webrtc (0.11.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WebRTC-SDK (= 125.6422.04)
|
- WebRTC-SDK (= 125.6422.04)
|
||||||
@ -259,6 +262,7 @@ PODS:
|
|||||||
- PromisesObjC (= 2.4.0)
|
- PromisesObjC (= 2.4.0)
|
||||||
- protocol_handler_ios (0.0.1):
|
- protocol_handler_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- SAMKeychain (1.5.3)
|
||||||
- screen_brightness_ios (0.1.0):
|
- screen_brightness_ios (0.1.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SDWebImage (5.19.7):
|
- SDWebImage (5.19.7):
|
||||||
@ -316,6 +320,7 @@ DEPENDENCIES:
|
|||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/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`)
|
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
||||||
- gal (from `.symlinks/plugins/gal/darwin`)
|
- gal (from `.symlinks/plugins/gal/darwin`)
|
||||||
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
||||||
@ -364,6 +369,7 @@ SPEC REPOS:
|
|||||||
- nanopb
|
- nanopb
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- PromisesSwift
|
- PromisesSwift
|
||||||
|
- SAMKeychain
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- sqlite3
|
- sqlite3
|
||||||
- SwiftyGif
|
- SwiftyGif
|
||||||
@ -401,6 +407,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
flutter_udid:
|
||||||
|
:path: ".symlinks/plugins/flutter_udid/ios"
|
||||||
flutter_webrtc:
|
flutter_webrtc:
|
||||||
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
||||||
gal:
|
gal:
|
||||||
@ -480,6 +488,7 @@ SPEC CHECKSUMS:
|
|||||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||||
|
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
||||||
flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f
|
flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f
|
||||||
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
|
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
|
||||||
GoogleAppMeasurement: 76d4f8b36b03bd8381fa9a7fe2cc7f99c0a2e93a
|
GoogleAppMeasurement: 76d4f8b36b03bd8381fa9a7fe2cc7f99c0a2e93a
|
||||||
@ -501,6 +510,7 @@ SPEC CHECKSUMS:
|
|||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||||
protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990
|
protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990
|
||||||
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
||||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
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/models/realm.dart';
|
||||||
import 'package:solian/screens/about.dart';
|
import 'package:solian/screens/about.dart';
|
||||||
import 'package:solian/screens/account.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/friend.dart';
|
||||||
import 'package:solian/screens/account/preferences/notifications.dart';
|
import 'package:solian/screens/account/preferences/notifications.dart';
|
||||||
import 'package:solian/screens/account/preferences/security.dart';
|
import 'package:solian/screens/account/preferences/security.dart';
|
||||||
@ -275,6 +276,14 @@ abstract class AppRouter {
|
|||||||
child: const AuthPreferencesScreen(),
|
child: const AuthPreferencesScreen(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/audit',
|
||||||
|
name: 'auditLog',
|
||||||
|
builder: (context, state) => TitleShell(
|
||||||
|
state: state,
|
||||||
|
child: const AuditLogScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account/view/:name',
|
path: '/account/view/:name',
|
||||||
name: 'accountProfilePage',
|
name: 'accountProfilePage',
|
||||||
|
@ -129,6 +129,15 @@ class _AccountScreenState extends State<AccountScreen> {
|
|||||||
AppRouter.instance.pushNamed('settings');
|
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)
|
if (auth.isAuthorized.value)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.lock),
|
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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.7.0"
|
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:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -84,6 +84,7 @@ dependencies:
|
|||||||
in_app_review: ^2.0.9
|
in_app_review: ^2.0.9
|
||||||
syntax_highlight: ^0.4.0
|
syntax_highlight: ^0.4.0
|
||||||
flutter_udid: ^3.0.0
|
flutter_udid: ^3.0.0
|
||||||
|
timeline_tile: ^2.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user