@octo-cyber/log-analysis 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/controllers/log-analysis.controller.d.ts +21 -0
  2. package/dist/controllers/log-analysis.controller.d.ts.map +1 -0
  3. package/dist/controllers/log-analysis.controller.js +75 -0
  4. package/dist/controllers/log-analysis.controller.js.map +1 -0
  5. package/dist/controllers/retention-rule.controller.d.ts +17 -0
  6. package/dist/controllers/retention-rule.controller.d.ts.map +1 -0
  7. package/dist/controllers/retention-rule.controller.js +97 -0
  8. package/dist/controllers/retention-rule.controller.js.map +1 -0
  9. package/dist/entities/index.d.ts +6 -0
  10. package/dist/entities/index.d.ts.map +1 -0
  11. package/dist/entities/index.js +13 -0
  12. package/dist/entities/index.js.map +1 -0
  13. package/dist/entities/operation-log.entity.d.ts +45 -0
  14. package/dist/entities/operation-log.entity.d.ts.map +1 -0
  15. package/dist/entities/operation-log.entity.js +125 -0
  16. package/dist/entities/operation-log.entity.js.map +1 -0
  17. package/dist/entities/retention-rule.entity.d.ts +17 -0
  18. package/dist/entities/retention-rule.entity.d.ts.map +1 -0
  19. package/dist/entities/retention-rule.entity.js +66 -0
  20. package/dist/entities/retention-rule.entity.js.map +1 -0
  21. package/dist/index.d.ts +18 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +39 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/log-analysis.module.d.ts +18 -0
  26. package/dist/log-analysis.module.d.ts.map +1 -0
  27. package/dist/log-analysis.module.js +41 -0
  28. package/dist/log-analysis.module.js.map +1 -0
  29. package/dist/schemas/log-analysis.schema.d.ts +116 -0
  30. package/dist/schemas/log-analysis.schema.d.ts.map +1 -0
  31. package/dist/schemas/log-analysis.schema.js +42 -0
  32. package/dist/schemas/log-analysis.schema.js.map +1 -0
  33. package/dist/services/log-query.service.d.ts +37 -0
  34. package/dist/services/log-query.service.d.ts.map +1 -0
  35. package/dist/services/log-query.service.js +103 -0
  36. package/dist/services/log-query.service.js.map +1 -0
  37. package/dist/services/log-writer.service.d.ts +20 -0
  38. package/dist/services/log-writer.service.d.ts.map +1 -0
  39. package/dist/services/log-writer.service.js +66 -0
  40. package/dist/services/log-writer.service.js.map +1 -0
  41. package/dist/services/retention-rule.service.d.ts +19 -0
  42. package/dist/services/retention-rule.service.d.ts.map +1 -0
  43. package/dist/services/retention-rule.service.js +110 -0
  44. package/dist/services/retention-rule.service.js.map +1 -0
  45. package/package.json +88 -0
  46. package/web/components/LogsTable.tsx +69 -0
  47. package/web/components/RetentionRulesTab.tsx +187 -0
  48. package/web/components/StatsOverview.tsx +91 -0
  49. package/web/index.ts +27 -0
  50. package/web/manifest.ts +17 -0
  51. package/web/messages/en-US.json +70 -0
  52. package/web/messages/zh-CN.json +70 -0
  53. package/web/pages/LogAnalysisPage.tsx +202 -0
  54. package/web/services/log-analysis-service.ts +68 -0
  55. package/web/types/log-analysis.ts +83 -0
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@octo-cyber/log-analysis",
3
+ "version": "0.5.0",
4
+ "description": "Octo log-analysis module — operation log recording, querying, and statistical analysis",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./web": {
14
+ "types": "./web/index.ts",
15
+ "import": "./web/index.ts",
16
+ "default": "./web/index.ts"
17
+ },
18
+ "./web/pages/*": {
19
+ "types": "./web/pages/*.tsx",
20
+ "import": "./web/pages/*.tsx",
21
+ "default": "./web/pages/*.tsx"
22
+ }
23
+ },
24
+ "dependencies": {
25
+ "@octo-cyber/core": "^0.5.4",
26
+ "@octo-cyber/auth": "^0.5.6"
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^19.0.0",
30
+ "react-dom": "^19.0.0",
31
+ "next": "^16.0.0",
32
+ "next-intl": "^4.0.0",
33
+ "lucide-react": ">=0.400.0",
34
+ "@tanstack/react-table": "^8.0.0",
35
+ "zod": "^3.0.0",
36
+ "sonner": "^2.0.0",
37
+ "@octo-cyber/ui": "^0.5.3"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@octo-cyber/ui": {
41
+ "optional": true
42
+ },
43
+ "react": {
44
+ "optional": true
45
+ },
46
+ "react-dom": {
47
+ "optional": true
48
+ },
49
+ "next": {
50
+ "optional": true
51
+ },
52
+ "next-intl": {
53
+ "optional": true
54
+ },
55
+ "lucide-react": {
56
+ "optional": true
57
+ },
58
+ "@tanstack/react-table": {
59
+ "optional": true
60
+ },
61
+ "zod": {
62
+ "optional": true
63
+ },
64
+ "sonner": {
65
+ "optional": true
66
+ }
67
+ },
68
+ "devDependencies": {
69
+ "@types/react": "^19",
70
+ "@types/react-dom": "^19",
71
+ "typescript": "^5.8.0",
72
+ "vitest": "^3.0.0"
73
+ },
74
+ "files": [
75
+ "dist",
76
+ "web"
77
+ ],
78
+ "publishConfig": {
79
+ "registry": "https://registry.npmjs.org/"
80
+ },
81
+ "license": "MIT",
82
+ "scripts": {
83
+ "build": "tsc -p tsconfig.build.json",
84
+ "dev": "tsc -p tsconfig.build.json --watch",
85
+ "test": "vitest run",
86
+ "typecheck": "tsc --noEmit"
87
+ }
88
+ }
@@ -0,0 +1,69 @@
1
+ 'use client'
2
+
3
+ import { useTranslations } from 'next-intl'
4
+ import { Badge } from '@octo-cyber/ui/components/ui/badge'
5
+ import {
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from '@octo-cyber/ui/components/ui/table'
13
+ import type { OperationLog, LogLevel } from '../types/log-analysis'
14
+
15
+ interface LogsTableProps {
16
+ logs: OperationLog[]
17
+ }
18
+
19
+ const levelVariant: Record<LogLevel, 'default' | 'secondary' | 'destructive'> = {
20
+ INFO: 'default',
21
+ WARN: 'secondary',
22
+ ERROR: 'destructive',
23
+ }
24
+
25
+ export function LogsTable({ logs }: LogsTableProps) {
26
+ const t = useTranslations('logAnalysis')
27
+
28
+ return (
29
+ <div className="rounded-md border dark:border-zinc-700">
30
+ <Table>
31
+ <TableHeader>
32
+ <TableRow>
33
+ <TableHead>{t('columns.createdAt')}</TableHead>
34
+ <TableHead>{t('columns.level')}</TableHead>
35
+ <TableHead>{t('columns.module')}</TableHead>
36
+ <TableHead>{t('columns.action')}</TableHead>
37
+ <TableHead>{t('columns.username')}</TableHead>
38
+ <TableHead>{t('columns.description')}</TableHead>
39
+ <TableHead>{t('columns.ip')}</TableHead>
40
+ </TableRow>
41
+ </TableHeader>
42
+ <TableBody>
43
+ {logs.length === 0 && (
44
+ <TableRow>
45
+ <TableCell colSpan={7} className="text-center text-muted-foreground py-8">
46
+ {t('empty')}
47
+ </TableCell>
48
+ </TableRow>
49
+ )}
50
+ {logs.map(log => (
51
+ <TableRow key={log.id}>
52
+ <TableCell className="text-xs text-muted-foreground whitespace-nowrap">
53
+ {new Date(log.createdAt).toLocaleString()}
54
+ </TableCell>
55
+ <TableCell>
56
+ <Badge variant={levelVariant[log.level]}>{log.level}</Badge>
57
+ </TableCell>
58
+ <TableCell className="font-mono text-xs">{log.module}</TableCell>
59
+ <TableCell className="text-xs">{log.action}</TableCell>
60
+ <TableCell className="text-sm">{log.username ?? '-'}</TableCell>
61
+ <TableCell className="max-w-xs truncate text-sm">{log.description}</TableCell>
62
+ <TableCell className="text-xs text-muted-foreground">{log.ip ?? '-'}</TableCell>
63
+ </TableRow>
64
+ ))}
65
+ </TableBody>
66
+ </Table>
67
+ </div>
68
+ )
69
+ }
@@ -0,0 +1,187 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useTranslations } from 'next-intl'
5
+ import { toast } from 'sonner'
6
+ import { Button } from '@octo-cyber/ui/components/ui/button'
7
+ import { Badge } from '@octo-cyber/ui/components/ui/badge'
8
+ import {
9
+ Table,
10
+ TableBody,
11
+ TableCell,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ } from '@octo-cyber/ui/components/ui/table'
16
+ import {
17
+ Dialog,
18
+ DialogContent,
19
+ DialogHeader,
20
+ DialogTitle,
21
+ DialogTrigger,
22
+ } from '@octo-cyber/ui/components/ui/dialog'
23
+ import { Input } from '@octo-cyber/ui/components/ui/input'
24
+ import { Label } from '@octo-cyber/ui/components/ui/label'
25
+ import { logAnalysisService } from '../services/log-analysis-service'
26
+ import type { RetentionRule, CreateRetentionRuleDto } from '../types/log-analysis'
27
+
28
+ interface RetentionRulesTabProps {
29
+ rules: RetentionRule[]
30
+ onRefresh: () => void
31
+ }
32
+
33
+ const emptyForm: CreateRetentionRuleDto = {
34
+ name: '',
35
+ module: null,
36
+ action: null,
37
+ retentionDays: 90,
38
+ enabled: true,
39
+ }
40
+
41
+ export function RetentionRulesTab({ rules, onRefresh }: RetentionRulesTabProps) {
42
+ const t = useTranslations('logAnalysis')
43
+ const [open, setOpen] = useState(false)
44
+ const [form, setForm] = useState<CreateRetentionRuleDto>(emptyForm)
45
+ const [saving, setSaving] = useState(false)
46
+ const [applying, setApplying] = useState(false)
47
+
48
+ const handleCreate = async () => {
49
+ if (!form.name.trim()) return
50
+ setSaving(true)
51
+ try {
52
+ await logAnalysisService.createRetentionRule(form)
53
+ toast.success(t('retention.created'))
54
+ setOpen(false)
55
+ setForm(emptyForm)
56
+ onRefresh()
57
+ } catch {
58
+ toast.error(t('retention.createError'))
59
+ } finally {
60
+ setSaving(false)
61
+ }
62
+ }
63
+
64
+ const handleDelete = async (id: number) => {
65
+ try {
66
+ await logAnalysisService.deleteRetentionRule(id)
67
+ toast.success(t('retention.deleted'))
68
+ onRefresh()
69
+ } catch {
70
+ toast.error(t('retention.deleteError'))
71
+ }
72
+ }
73
+
74
+ const handleApply = async () => {
75
+ setApplying(true)
76
+ try {
77
+ const result = await logAnalysisService.applyRetention()
78
+ toast.success(t('retention.applied', { count: result.deleted }))
79
+ onRefresh()
80
+ } catch {
81
+ toast.error(t('retention.applyError'))
82
+ } finally {
83
+ setApplying(false)
84
+ }
85
+ }
86
+
87
+ return (
88
+ <div className="space-y-4">
89
+ <div className="flex justify-end gap-2">
90
+ <Button variant="outline" onClick={handleApply} disabled={applying}>
91
+ {applying ? t('retention.applying') : t('retention.applyNow')}
92
+ </Button>
93
+ <Dialog open={open} onOpenChange={setOpen}>
94
+ <DialogTrigger asChild>
95
+ <Button>{t('retention.addRule')}</Button>
96
+ </DialogTrigger>
97
+ <DialogContent>
98
+ <DialogHeader>
99
+ <DialogTitle>{t('retention.addRule')}</DialogTitle>
100
+ </DialogHeader>
101
+ <div className="space-y-4 py-2">
102
+ <div className="space-y-1">
103
+ <Label>{t('retention.name')}</Label>
104
+ <Input
105
+ value={form.name}
106
+ onChange={e => setForm(f => ({ ...f, name: e.target.value }))}
107
+ placeholder={t('retention.namePlaceholder')}
108
+ />
109
+ </div>
110
+ <div className="space-y-1">
111
+ <Label>{t('retention.module')}</Label>
112
+ <Input
113
+ value={form.module ?? ''}
114
+ onChange={e => setForm(f => ({ ...f, module: e.target.value || null }))}
115
+ placeholder={t('retention.modulePlaceholder')}
116
+ />
117
+ </div>
118
+ <div className="space-y-1">
119
+ <Label>{t('retention.retentionDays')}</Label>
120
+ <Input
121
+ type="number"
122
+ min={0}
123
+ value={form.retentionDays}
124
+ onChange={e => setForm(f => ({ ...f, retentionDays: Number(e.target.value) }))}
125
+ />
126
+ </div>
127
+ <Button className="w-full" onClick={handleCreate} disabled={saving}>
128
+ {saving ? t('saving') : t('save')}
129
+ </Button>
130
+ </div>
131
+ </DialogContent>
132
+ </Dialog>
133
+ </div>
134
+
135
+ <div className="rounded-md border dark:border-zinc-700">
136
+ <Table>
137
+ <TableHeader>
138
+ <TableRow>
139
+ <TableHead>{t('retention.name')}</TableHead>
140
+ <TableHead>{t('retention.module')}</TableHead>
141
+ <TableHead>{t('retention.action')}</TableHead>
142
+ <TableHead>{t('retention.retentionDays')}</TableHead>
143
+ <TableHead>{t('retention.status')}</TableHead>
144
+ <TableHead className="w-24">{t('actions')}</TableHead>
145
+ </TableRow>
146
+ </TableHeader>
147
+ <TableBody>
148
+ {rules.length === 0 && (
149
+ <TableRow>
150
+ <TableCell colSpan={6} className="text-center text-muted-foreground py-8">
151
+ {t('empty')}
152
+ </TableCell>
153
+ </TableRow>
154
+ )}
155
+ {rules.map(rule => (
156
+ <TableRow key={rule.id}>
157
+ <TableCell className="font-medium">{rule.name}</TableCell>
158
+ <TableCell className="font-mono text-xs">{rule.module ?? t('retention.all')}</TableCell>
159
+ <TableCell className="text-sm">{rule.action ?? t('retention.all')}</TableCell>
160
+ <TableCell>
161
+ {rule.retentionDays === 0
162
+ ? t('retention.forever')
163
+ : t('retention.days', { count: rule.retentionDays })}
164
+ </TableCell>
165
+ <TableCell>
166
+ <Badge variant={rule.enabled ? 'default' : 'secondary'}>
167
+ {rule.enabled ? t('enabled') : t('disabled')}
168
+ </Badge>
169
+ </TableCell>
170
+ <TableCell>
171
+ <Button
172
+ variant="ghost"
173
+ size="sm"
174
+ className="text-destructive hover:text-destructive"
175
+ onClick={() => handleDelete(rule.id)}
176
+ >
177
+ {t('delete')}
178
+ </Button>
179
+ </TableCell>
180
+ </TableRow>
181
+ ))}
182
+ </TableBody>
183
+ </Table>
184
+ </div>
185
+ </div>
186
+ )
187
+ }
@@ -0,0 +1,91 @@
1
+ 'use client'
2
+
3
+ import { useTranslations } from 'next-intl'
4
+ import { Card, CardContent, CardHeader, CardTitle } from '@octo-cyber/ui/components/ui/card'
5
+ import { Badge } from '@octo-cyber/ui/components/ui/badge'
6
+ import type { LogStats } from '../types/log-analysis'
7
+
8
+ interface StatsOverviewProps {
9
+ stats: LogStats
10
+ }
11
+
12
+ export function StatsOverview({ stats }: StatsOverviewProps) {
13
+ const t = useTranslations('logAnalysis')
14
+
15
+ return (
16
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
17
+ {/* 总计 */}
18
+ <Card className="dark:bg-zinc-900 dark:border-zinc-700">
19
+ <CardHeader className="pb-2">
20
+ <CardTitle className="text-sm font-medium text-muted-foreground">
21
+ {t('stats.total')}
22
+ </CardTitle>
23
+ </CardHeader>
24
+ <CardContent>
25
+ <div className="text-2xl font-bold">{stats.total.toLocaleString()}</div>
26
+ </CardContent>
27
+ </Card>
28
+
29
+ {/* 按级别 */}
30
+ <Card className="dark:bg-zinc-900 dark:border-zinc-700">
31
+ <CardHeader className="pb-2">
32
+ <CardTitle className="text-sm font-medium text-muted-foreground">
33
+ {t('stats.byLevel')}
34
+ </CardTitle>
35
+ </CardHeader>
36
+ <CardContent className="flex flex-wrap gap-2">
37
+ {stats.byLevel.map(item => (
38
+ <div key={item.level} className="flex items-center gap-1">
39
+ <Badge
40
+ variant={
41
+ item.level === 'ERROR'
42
+ ? 'destructive'
43
+ : item.level === 'WARN'
44
+ ? 'secondary'
45
+ : 'default'
46
+ }
47
+ >
48
+ {item.level}
49
+ </Badge>
50
+ <span className="text-sm font-semibold">{item.count}</span>
51
+ </div>
52
+ ))}
53
+ </CardContent>
54
+ </Card>
55
+
56
+ {/* Top 5 模块 */}
57
+ <Card className="dark:bg-zinc-900 dark:border-zinc-700">
58
+ <CardHeader className="pb-2">
59
+ <CardTitle className="text-sm font-medium text-muted-foreground">
60
+ {t('stats.topModules')}
61
+ </CardTitle>
62
+ </CardHeader>
63
+ <CardContent className="space-y-1">
64
+ {stats.byModule.slice(0, 5).map(item => (
65
+ <div key={item.module} className="flex justify-between text-sm">
66
+ <span className="font-mono text-xs">{item.module}</span>
67
+ <span className="font-semibold">{item.count}</span>
68
+ </div>
69
+ ))}
70
+ </CardContent>
71
+ </Card>
72
+
73
+ {/* Top 5 动作 */}
74
+ <Card className="dark:bg-zinc-900 dark:border-zinc-700">
75
+ <CardHeader className="pb-2">
76
+ <CardTitle className="text-sm font-medium text-muted-foreground">
77
+ {t('stats.topActions')}
78
+ </CardTitle>
79
+ </CardHeader>
80
+ <CardContent className="space-y-1">
81
+ {stats.byAction.slice(0, 5).map(item => (
82
+ <div key={item.action} className="flex justify-between text-sm">
83
+ <span>{item.action}</span>
84
+ <span className="font-semibold">{item.count}</span>
85
+ </div>
86
+ ))}
87
+ </CardContent>
88
+ </Card>
89
+ </div>
90
+ )
91
+ }
package/web/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @octo-cyber/log-analysis — Frontend entry point.
3
+ */
4
+
5
+ // Manifest
6
+ export { logAnalysisFrontendManifest } from './manifest'
7
+
8
+ // i18n Messages
9
+ export { default as logAnalysisMessagesZhCN } from './messages/zh-CN.json'
10
+ export { default as logAnalysisMessagesEnUS } from './messages/en-US.json'
11
+
12
+ // Pages
13
+ export { default as LogAnalysisPage } from './pages/LogAnalysisPage'
14
+
15
+ // Services
16
+ export { logAnalysisService } from './services/log-analysis-service'
17
+
18
+ // Types
19
+ export type {
20
+ OperationLog,
21
+ RetentionRule,
22
+ LogStats,
23
+ LogLevel,
24
+ LogAction,
25
+ QueryLogsParams,
26
+ CreateRetentionRuleDto,
27
+ } from './types/log-analysis'
@@ -0,0 +1,17 @@
1
+ import type { IFrontendManifest } from '@octo-cyber/core'
2
+
3
+ export const logAnalysisFrontendManifest: IFrontendManifest = {
4
+ moduleId: 'log-analysis',
5
+ icon: 'ScrollText',
6
+ color: 'slate',
7
+ position: 'main',
8
+ titleKey: 'logAnalysis.title',
9
+ descriptionKey: 'logAnalysis.description',
10
+ pages: [
11
+ {
12
+ path: '/log-analysis',
13
+ titleKey: 'logAnalysis.pages.home',
14
+ component: '@octo-cyber/log-analysis/web/pages/LogAnalysisPage',
15
+ },
16
+ ],
17
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "logAnalysis": {
3
+ "title": "Operation Logs",
4
+ "description": "View system operation records, analyze user behavior, and manage log retention rules",
5
+ "tabs": {
6
+ "logs": "Logs",
7
+ "stats": "Statistics",
8
+ "retention": "Retention Rules"
9
+ },
10
+ "columns": {
11
+ "createdAt": "Time",
12
+ "level": "Level",
13
+ "module": "Module",
14
+ "action": "Action",
15
+ "username": "User",
16
+ "description": "Description",
17
+ "ip": "IP"
18
+ },
19
+ "filter": {
20
+ "keyword": "Search description...",
21
+ "module": "Module",
22
+ "level": "Level",
23
+ "action": "Action",
24
+ "all": "All"
25
+ },
26
+ "stats": {
27
+ "total": "Total Logs",
28
+ "byLevel": "By Level",
29
+ "topModules": "Top Modules",
30
+ "topActions": "Top Actions"
31
+ },
32
+ "retention": {
33
+ "addRule": "Add Rule",
34
+ "name": "Rule Name",
35
+ "namePlaceholder": "e.g. Clean login logs",
36
+ "module": "Match Module",
37
+ "modulePlaceholder": "Empty = all modules",
38
+ "action": "Match Action",
39
+ "retentionDays": "Retention Days",
40
+ "status": "Status",
41
+ "all": "All",
42
+ "forever": "Keep Forever",
43
+ "days": "{count} days",
44
+ "applyNow": "Apply Retention Now",
45
+ "applying": "Applying...",
46
+ "applied": "Cleaned {count} logs",
47
+ "created": "Rule created",
48
+ "deleted": "Rule deleted",
49
+ "createError": "Failed to create rule",
50
+ "deleteError": "Failed to delete rule",
51
+ "applyError": "Failed to apply retention"
52
+ },
53
+ "empty": "No data",
54
+ "loading": "Loading...",
55
+ "fetchError": "Failed to load data",
56
+ "search": "Search",
57
+ "save": "Save",
58
+ "saving": "Saving...",
59
+ "delete": "Delete",
60
+ "actions": "Actions",
61
+ "enabled": "Enabled",
62
+ "disabled": "Disabled",
63
+ "prev": "Previous",
64
+ "next": "Next",
65
+ "pageInfo": "Page {page} of {totalPages}",
66
+ "pages": {
67
+ "home": "Operation Logs"
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "logAnalysis": {
3
+ "title": "操作日志",
4
+ "description": "查看系统操作记录,分析用户行为,管理日志保留规则",
5
+ "tabs": {
6
+ "logs": "日志列表",
7
+ "stats": "统计分析",
8
+ "retention": "保留规则"
9
+ },
10
+ "columns": {
11
+ "createdAt": "时间",
12
+ "level": "级别",
13
+ "module": "模块",
14
+ "action": "操作",
15
+ "username": "用户",
16
+ "description": "描述",
17
+ "ip": "IP"
18
+ },
19
+ "filter": {
20
+ "keyword": "搜索描述...",
21
+ "module": "模块",
22
+ "level": "级别",
23
+ "action": "操作",
24
+ "all": "全部"
25
+ },
26
+ "stats": {
27
+ "total": "总条数",
28
+ "byLevel": "按级别",
29
+ "topModules": "Top 模块",
30
+ "topActions": "Top 操作"
31
+ },
32
+ "retention": {
33
+ "addRule": "添加规则",
34
+ "name": "规则名称",
35
+ "namePlaceholder": "如:清理登录日志",
36
+ "module": "匹配模块",
37
+ "modulePlaceholder": "空表示全部模块",
38
+ "action": "匹配操作",
39
+ "retentionDays": "保留天数",
40
+ "status": "状态",
41
+ "all": "全部",
42
+ "forever": "永久保留",
43
+ "days": "{count} 天",
44
+ "applyNow": "立即执行清理",
45
+ "applying": "执行中...",
46
+ "applied": "已清理 {count} 条日志",
47
+ "created": "规则已创建",
48
+ "deleted": "规则已删除",
49
+ "createError": "创建失败",
50
+ "deleteError": "删除失败",
51
+ "applyError": "清理执行失败"
52
+ },
53
+ "empty": "暂无数据",
54
+ "loading": "加载中...",
55
+ "fetchError": "数据加载失败",
56
+ "search": "搜索",
57
+ "save": "保存",
58
+ "saving": "保存中...",
59
+ "delete": "删除",
60
+ "actions": "操作",
61
+ "enabled": "启用",
62
+ "disabled": "禁用",
63
+ "prev": "上一页",
64
+ "next": "下一页",
65
+ "pageInfo": "第 {page} / {totalPages} 页",
66
+ "pages": {
67
+ "home": "操作日志"
68
+ }
69
+ }
70
+ }