feat: setup logs retention by max file size
This commit is contained in:
1 parent
70c8492f4f
commit
f914eae3c5
44 files changed
+2316
-161
No files matched your search
@@ -3,8 +3,11 @@ import { initializeLocale, setLocale } from '@/i18n'
|
||||
import type { WorkspaceSettings } from '@/composables/useSettings'
|
||||
import { recentWhenLabel } from '../activityTime'
|
||||
import {
|
||||
formatLogSize,
|
||||
helperCopy,
|
||||
isShorter,
|
||||
isSmaller,
|
||||
sizeHelperCopy,
|
||||
lastCleanupLine,
|
||||
nextCleanupLine,
|
||||
shorteningDescription,
|
||||
@@ -20,6 +23,8 @@ function settings(overrides: Partial<WorkspaceSettings> = {}): WorkspaceSettings
|
||||
return {
|
||||
version: 'v2.2.0-beta',
|
||||
activity_retention_days: 90,
|
||||
activity_retention_max_gb: 0,
|
||||
activity_log_size_bytes: 0,
|
||||
activity_last_cleanup_at: at(6, 3, 0),
|
||||
activity_last_cleanup_removed: 142,
|
||||
activity_next_cleanup_at: at(7, 3, 0),
|
||||
@@ -86,3 +91,43 @@ describe('retention lines', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('retention size limit', () => {
|
||||
it('ranks 1 < 5 < 10 < 25 < 50 < No size limit', () => {
|
||||
expect(isSmaller(1, 5)).toBe(true)
|
||||
expect(isSmaller(5, 0)).toBe(true)
|
||||
expect(isSmaller(0, 50)).toBe(false)
|
||||
expect(isSmaller(5, 5)).toBe(false)
|
||||
expect(isSmaller(10, 50)).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[0, 'Less than 0.1 GB'],
|
||||
[53687091, 'Less than 0.1 GB'],
|
||||
[107374183, '0.1 GB'],
|
||||
[1073741824, '1.0 GB'],
|
||||
[3435973837, '3.2 GB'],
|
||||
[5368709120, '5.0 GB'],
|
||||
])('formats %s bytes as %s on a 1024^3 base', (bytes, shown) => {
|
||||
expect(formatLogSize(bytes)).toBe(shown)
|
||||
})
|
||||
|
||||
it('words the helpers for each combination', () => {
|
||||
expect(helperCopy(90, 5)).toBe('Events older than this are removed.')
|
||||
expect(helperCopy(0, 5)).toBe('No age limit. Only the size limit applies.')
|
||||
expect(helperCopy(0, 0)).toBe('Nothing is removed automatically. The log grows over time.')
|
||||
expect(sizeHelperCopy(0, 99)).toBe('Optional. Logs can grow without a size cap.')
|
||||
expect(sizeHelperCopy(5, 3435973837)).toBe(
|
||||
'When logs pass 5 GB, the oldest events are removed. Currently 3.2 GB.',
|
||||
)
|
||||
})
|
||||
|
||||
it('words the helpers in French with Go and a decimal comma', () => {
|
||||
setLocale('fr')
|
||||
|
||||
expect(sizeHelperCopy(5, 3435973837)).toBe(
|
||||
'Au-delà de 5 Go, les événements les plus anciens sont supprimés. Actuellement 3,2 Go.',
|
||||
)
|
||||
expect(formatLogSize(0)).toBe('Moins de 0,1 Go')
|
||||
})
|
||||
})
|
||||
@@ -101,6 +101,26 @@ describe('Sentences: English and French come from the same code and params', ()
|
||||
'Changed the activity retention from 90 days to Forever',
|
||||
"Durée de conservation de l'activité modifiée : 90 jours → Toujours",
|
||||
],
|
||||
[
|
||||
'system.activity_retention_changed size only',
|
||||
{
|
||||
action: 'system.activity_retention_changed',
|
||||
target: null,
|
||||
params: { max_gb_from: 0, max_gb_to: 5 },
|
||||
},
|
||||
'Changed the activity size limit from No size limit to 5 GB',
|
||||
"Limite de taille de l'activité modifiée : Aucune limite de taille → 5 Go",
|
||||
],
|
||||
[
|
||||
'system.activity_retention_changed age and size',
|
||||
{
|
||||
action: 'system.activity_retention_changed',
|
||||
target: null,
|
||||
params: { from: 90, to: 30, max_gb_from: 10, max_gb_to: 0 },
|
||||
},
|
||||
'Changed the activity retention from 90 days to 30 days and the size limit from 10 GB to No size limit',
|
||||
"Durée de conservation de l'activité modifiée : 90 jours → 30 jours, et limite de taille : 10 Go → Aucune limite de taille",
|
||||
],
|
||||
[
|
||||
'system.restart_requested update',
|
||||
{
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { t } from '@/i18n'
|
||||
import { formatNumber, t } from '@/i18n'
|
||||
import type { WorkspaceSettings } from '@/composables/useSettings'
|
||||
import { localDayTime } from '@/lib/activityTime'
|
||||
|
||||
export const FOREVER_DAYS = 0
|
||||
export const NO_SIZE_LIMIT = 0
|
||||
export const SAVED_VISIBLE_MS = 2000
|
||||
|
||||
const BYTES_PER_GB = 1024 ** 3
|
||||
const MIN_SHOWN_GB = 0.1
|
||||
|
||||
const RANKED_PERIODS = [30, 90, 365, FOREVER_DAYS]
|
||||
const RANKED_SIZES = [1, 5, 10, 25, 50, NO_SIZE_LIMIT]
|
||||
|
||||
export function retentionOptions() {
|
||||
return RANKED_PERIODS.map((value) => ({ value, label: periodLabel(value) }))
|
||||
@@ -32,12 +37,52 @@ export function isShorter(candidate: number, saved: number) {
|
||||
return periodRank(candidate) < periodRank(saved)
|
||||
}
|
||||
|
||||
export function helperCopy(days: number) {
|
||||
return days === FOREVER_DAYS
|
||||
? t('Nothing is removed automatically. The log grows over time.')
|
||||
export function sizeOptions() {
|
||||
return RANKED_SIZES.map((value) => ({ value, label: sizeLabel(value) }))
|
||||
}
|
||||
|
||||
export function sizeLabel(gb: number) {
|
||||
return gb === NO_SIZE_LIMIT ? t('No size limit') : t('{0} GB', [gb])
|
||||
}
|
||||
|
||||
export function sizeRank(gb: number) {
|
||||
return RANKED_SIZES.indexOf(gb)
|
||||
}
|
||||
|
||||
export function isSmaller(candidate: number, saved: number) {
|
||||
return sizeRank(candidate) < sizeRank(saved)
|
||||
}
|
||||
|
||||
export function formatLogSize(bytes: number) {
|
||||
const gb = bytes / BYTES_PER_GB
|
||||
if (gb < MIN_SHOWN_GB) {
|
||||
return t('Less than 0.1 GB')
|
||||
}
|
||||
const figure = formatNumber(gb, { minimumFractionDigits: 1, maximumFractionDigits: 1 })
|
||||
return t('{0} GB', [figure])
|
||||
}
|
||||
|
||||
export function helperCopy(days: number, gb = NO_SIZE_LIMIT) {
|
||||
const sized = gb !== NO_SIZE_LIMIT
|
||||
if (days === FOREVER_DAYS) {
|
||||
return sized
|
||||
? t('No age limit. Only the size limit applies.')
|
||||
: t('Nothing is removed automatically. The log grows over time.')
|
||||
}
|
||||
return sized
|
||||
? t('Events older than this are removed.')
|
||||
: t('Older events are removed automatically.')
|
||||
}
|
||||
|
||||
export function sizeHelperCopy(gb: number, bytes: number) {
|
||||
return gb === NO_SIZE_LIMIT
|
||||
? t('Optional. Logs can grow without a size cap.')
|
||||
: t('When logs pass {0} GB, the oldest events are removed. Currently {1}.', [
|
||||
gb,
|
||||
formatLogSize(bytes),
|
||||
])
|
||||
}
|
||||
|
||||
export function lastCleanupLine(settings: WorkspaceSettings) {
|
||||
if (!settings.activity_last_cleanup_at) {
|
||||
return t('Never')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { formatNumber, t } from '@/i18n'
|
||||
import type { MessageKey } from '@/i18n/messages'
|
||||
import type { ActivityItem } from '@/lib/activity'
|
||||
import { sizeLabel } from '@/lib/activityRetention'
|
||||
|
||||
export interface SentenceSegment {
|
||||
text: string
|
||||
@@ -139,9 +140,36 @@ function retentionDays(value: unknown) {
|
||||
return days === 0 ? t('Forever') : t('{0} days', [formatNumber(days)])
|
||||
}
|
||||
|
||||
function changedAge(params: Record<string, unknown>) {
|
||||
return 'from' in params || 'to' in params
|
||||
}
|
||||
|
||||
function changedSize(params: Record<string, unknown>) {
|
||||
return 'max_gb_from' in params || 'max_gb_to' in params
|
||||
}
|
||||
|
||||
function retentionTemplate(params: Record<string, unknown>): MessageKey {
|
||||
if (changedSize(params) && changedAge(params)) {
|
||||
return 'Changed the activity retention from {0} to {1} and the size limit from {2} to {3}'
|
||||
}
|
||||
return changedSize(params)
|
||||
? 'Changed the activity size limit from {0} to {1}'
|
||||
: 'Changed the activity retention from {0} to {1}'
|
||||
}
|
||||
|
||||
function retentionValues(params: Record<string, unknown>) {
|
||||
const sizes = [sizeLabel(count(params.max_gb_from)), sizeLabel(count(params.max_gb_to))]
|
||||
if (!changedAge(params)) return sizes
|
||||
const ages = [retentionDays(params.from), retentionDays(params.to)]
|
||||
return changedSize(params) ? [...ages, ...sizes] : ages
|
||||
}
|
||||
|
||||
function resolveTemplate(item: ActivityItem): MessageKey | null {
|
||||
const template = CATALOGUE[item.action]
|
||||
if (!template) return null
|
||||
if (item.action === 'system.activity_retention_changed') {
|
||||
return retentionTemplate(item.params)
|
||||
}
|
||||
if (item.action === 'system.restart_requested' && item.outcome === 'success') {
|
||||
return item.params.kind === 'update'
|
||||
? 'Restarted to apply the update {0}'
|
||||
@@ -156,7 +184,7 @@ function slotValues(item: ActivityItem, template: MessageKey) {
|
||||
return [purgeSummary(item.params)]
|
||||
}
|
||||
if (item.action === 'system.activity_retention_changed') {
|
||||
return [retentionDays(item.params.from), retentionDays(item.params.to)]
|
||||
return retentionValues(item.params)
|
||||
}
|
||||
if (item.action === 'system.activity_cleanup') {
|
||||
return [formatNumber(count(item.params.removed))]
|
||||
|
||||
Reference in new issue
Block a user