Better audit logs

This commit is contained in:
LittleSheep 2024-10-14 22:05:49 +08:00
parent 77288713e1
commit 48e3b510cf
7 changed files with 126 additions and 46 deletions

View File

@ -485,5 +485,6 @@
"auditLog": "Audit log", "auditLog": "Audit log",
"shareImage": "Share as image", "shareImage": "Share as image",
"shareImageFooter": "Only on the Solar Network", "shareImageFooter": "Only on the Solar Network",
"fileSavedAt": "File saved at @path" "fileSavedAt": "File saved at @path",
"showIp": "Show IP Address"
} }

View File

@ -481,5 +481,6 @@
"auditLog": "活动日志", "auditLog": "活动日志",
"shareImage": "分享图片", "shareImage": "分享图片",
"shareImageFooter": "上 Solar Network 看更多有趣帖子", "shareImageFooter": "上 Solar Network 看更多有趣帖子",
"fileSavedAt": "文件保存于 @path" "fileSavedAt": "文件保存于 @path",
"showIp": "显示 IP 地址"
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:marquee/marquee.dart';
import 'package:solian/exceptions/request.dart'; import 'package:solian/exceptions/request.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/audit_log.dart'; import 'package:solian/models/audit_log.dart';
@ -29,7 +30,7 @@ class _AuditLogScreenState extends State<AuditLogScreen> {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = await auth.configureClient('id'); final client = await auth.configureClient('id');
final resp = final resp =
await client.get('/users/me/events?take=10&offset=${_events.length}'); await client.get('/users/me/events?take=15&offset=${_events.length}');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
context.showErrorDialog(RequestException(resp)); context.showErrorDialog(RequestException(resp));
} }
@ -45,6 +46,22 @@ class _AuditLogScreenState extends State<AuditLogScreen> {
}); });
} }
bool _showIp = false;
String _censorIpAddress(String ip) {
List<String> parts = ip.split('.');
if (parts.length == 4) {
String censoredPart1 = '*' * parts[1].length;
String censoredPart2 = '*' * parts[2].length;
String censoredPart3 = '*' * parts[3].length;
return '${parts[0]}.$censoredPart1.$censoredPart2.$censoredPart3';
} else {
return '***.***.***.***';
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -53,42 +70,85 @@ class _AuditLogScreenState extends State<AuditLogScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InfiniteList( return Column(
itemCount: _events.length, children: [
isLoading: _isBusy, CheckboxListTile(
onFetchData: () { value: _showIp,
_getEvents(); title: Text('showIp'.tr),
}, contentPadding: const EdgeInsets.symmetric(horizontal: 24),
itemBuilder: (context, idx) { secondary: const Icon(Icons.alternate_email),
final element = _events[idx]; tileColor:
return TimelineTile( Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.5),
isFirst: idx == 0, onChanged: (val) {
isLast: _events.length - 1 == idx, setState(() => _showIp = val ?? false);
alignment: TimelineAlign.start, },
endChild: Container( ),
child: Card( Expanded(
child: Column( child: RefreshIndicator(
crossAxisAlignment: CrossAxisAlignment.start, onRefresh: () {
children: [ _events.clear();
Text( return _getEvents();
element.type, },
style: GoogleFonts.robotoMono(fontSize: 15), child: InfiniteList(
padding: const EdgeInsets.symmetric(vertical: 12),
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,
indicatorStyle: IndicatorStyle(width: 15),
endChild: Container(
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
element.type,
style: GoogleFonts.robotoMono(fontSize: 15),
),
Text(
_showIp
? element.ipAddress
: _censorIpAddress(element.ipAddress),
style: GoogleFonts.sourceCodePro(
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 20,
width: double.maxFinite,
child: Marquee(
text: element.userAgent,
velocity: 25,
startAfter: Duration(milliseconds: 500),
pauseAfterRound: Duration(milliseconds: 3000),
),
),
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),
), ),
Row( ).paddingSymmetric(horizontal: 18);
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); ),
}, ],
); );
} }
} }

View File

@ -62,10 +62,9 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
return CustomScrollView( return CustomScrollView(
slivers: [ slivers: [
if (_isBusy) SliverToBoxAdapter(
SliverToBoxAdapter( child: LoadingIndicator(isActive: _isBusy),
child: LoadingIndicator(), ),
),
SliverToBoxAdapter( SliverToBoxAdapter(
child: PostItem( child: PostItem(
item: _item!, item: _item!,

View File

@ -38,11 +38,13 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
Future<void> _loadLastMessages() async { Future<void> _loadLastMessages() async {
final messages = await _eventController.src.getLastInAllChannels(); final messages = await _eventController.src.getLastInAllChannels();
setState(() { if (mounted) {
_lastMessages = messages setState(() {
.map((k, v) => MapEntry(k, v.firstOrNull)) _lastMessages = messages
.cast<int, LocalMessageEventTableData>(); .map((k, v) => MapEntry(k, v.firstOrNull))
}); .cast<int, LocalMessageEventTableData>();
});
}
} }
@override @override

View File

@ -414,6 +414,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" version: "2.0.5"
fading_edge_scrollview:
dependency: transitive
description:
name: fading_edge_scrollview
sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -1277,6 +1285,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.0"
marquee:
dependency: "direct main"
description:
name: marquee
sha256: a87e7e80c5d21434f90ad92add9f820cf68be374b226404fe881d2bba7be0862
url: "https://pub.dev"
source: hosted
version: "2.3.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:

View File

@ -89,6 +89,7 @@ dependencies:
qr_flutter: ^4.1.0 qr_flutter: ^4.1.0
flutter_resizable_container: ^3.0.0 flutter_resizable_container: ^3.0.0
file_saver: ^0.2.14 file_saver: ^0.2.14
marquee: ^2.3.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: