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",
"shareImage": "Share as image",
"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": "活动日志",
"shareImage": "分享图片",
"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:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:marquee/marquee.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/audit_log.dart';
@ -29,7 +30,7 @@ class _AuditLogScreenState extends State<AuditLogScreen> {
final AuthProvider auth = Get.find();
final client = await auth.configureClient('id');
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) {
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
void initState() {
super.initState();
@ -53,42 +70,85 @@ class _AuditLogScreenState extends State<AuditLogScreen> {
@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),
return Column(
children: [
CheckboxListTile(
value: _showIp,
title: Text('showIp'.tr),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
secondary: const Icon(Icons.alternate_email),
tileColor:
Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.5),
onChanged: (val) {
setState(() => _showIp = val ?? false);
},
),
Expanded(
child: RefreshIndicator(
onRefresh: () {
_events.clear();
return _getEvents();
},
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(
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);
},
),
),
).paddingSymmetric(horizontal: 18);
},
),
],
);
}
}

View File

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

View File

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

View File

@ -414,6 +414,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -1277,6 +1285,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.0"
marquee:
dependency: "direct main"
description:
name: marquee
sha256: a87e7e80c5d21434f90ad92add9f820cf68be374b226404fe881d2bba7be0862
url: "https://pub.dev"
source: hosted
version: "2.3.0"
matcher:
dependency: transitive
description:

View File

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