Improve activity journal readability
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
export const HARBOR_VERSIONS = Object.freeze({
|
export const HARBOR_VERSIONS = Object.freeze({
|
||||||
macClient: '0.30.4',
|
macClient: '0.30.5',
|
||||||
gatewayClient: '0.31.4',
|
gatewayClient: '0.31.5',
|
||||||
gatewayBackend: '0.31.0',
|
gatewayBackend: '0.31.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { assertActivityJournalPage, type ActivityJournalEvent } from '../../../shared/activityJournal.js';
|
import { assertActivityJournalPage, type ActivityJournalEvent } from '../../../shared/activityJournal.js';
|
||||||
|
import { compactActivityJournalEvents, refreshStreakCopy, type ActivityJournalDisplayItem } from './activityJournalModel.js';
|
||||||
import { Drawer } from '../../ui/Drawer.js';
|
import { Drawer } from '../../ui/Drawer.js';
|
||||||
import { RailAction } from '../../ui/RailAction.js';
|
import { RailAction } from '../../ui/RailAction.js';
|
||||||
|
|
||||||
@@ -122,11 +123,11 @@ export function ActivityJournalPanel({ feature, loadPage }: {
|
|||||||
if (feature.isOpen) void load(null, status !== 'idle');
|
if (feature.isOpen) void load(null, status !== 'idle');
|
||||||
}, [feature.isOpen]);
|
}, [feature.isOpen]);
|
||||||
|
|
||||||
const groups = events.reduce<Array<{ label: string; events: ActivityJournalEvent[] }>>((result, event) => {
|
const groups = compactActivityJournalEvents(events).reduce<Array<{ label: string; items: ActivityJournalDisplayItem[] }>>((result, item) => {
|
||||||
const label = dayLabel(event.occurredAt);
|
const label = dayLabel(item.event.occurredAt);
|
||||||
const group = result.at(-1);
|
const group = result.at(-1);
|
||||||
if (group?.label === label) group.events.push(event);
|
if (group?.label === label) group.items.push(item);
|
||||||
else result.push({ label, events: [event] });
|
else result.push({ label, items: [item] });
|
||||||
return result;
|
return result;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -152,9 +153,18 @@ export function ActivityJournalPanel({ feature, loadPage }: {
|
|||||||
: !events.length && status === 'ready' ? <p className="client-journal-empty">За последние 30 дней важных событий пока нет</p>
|
: !events.length && status === 'ready' ? <p className="client-journal-empty">За последние 30 дней важных событий пока нет</p>
|
||||||
: <div className="client-journal-groups">{groups.map((group) => <section key={group.label}>
|
: <div className="client-journal-groups">{groups.map((group) => <section key={group.label}>
|
||||||
<h3>{group.label}</h3>
|
<h3>{group.label}</h3>
|
||||||
<ol>{group.events.map((event) => {
|
<ol>{group.items.map((item) => {
|
||||||
const [title, details] = eventCopy(event);
|
const { event } = item;
|
||||||
return <li key={event.id} className={event.severity === 'error' ? 'is-error' : event.severity === 'warning' ? 'is-warning' : ''}>
|
const [title, details] = refreshStreakCopy(item) || eventCopy(event);
|
||||||
|
return <li key={event.id} className={event.severity === 'error'
|
||||||
|
? 'client-journal-event is-error'
|
||||||
|
: event.severity === 'warning'
|
||||||
|
? 'client-journal-event is-warning'
|
||||||
|
: event.type === 'failover.switched' && event.data.toRole === 'primary'
|
||||||
|
? 'client-journal-event is-switch-primary'
|
||||||
|
: event.type === 'failover.switched' && event.data.toRole === 'reserve'
|
||||||
|
? 'client-journal-event is-switch-reserve'
|
||||||
|
: 'client-journal-event'}>
|
||||||
<div className="client-journal-time"><time dateTime={event.occurredAt}>{new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit' }).format(new Date(event.occurredAt))}</time><span>{event.source}</span></div>
|
<div className="client-journal-time"><time dateTime={event.occurredAt}>{new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit' }).format(new Date(event.occurredAt))}</time><span>{event.source}</span></div>
|
||||||
<div><strong>{title}</strong>{details && <span>{details}</span>}{event.severity !== 'info' && <em>{event.severity === 'error' ? 'Ошибка' : 'Внимание'}</em>}</div>
|
<div><strong>{title}</strong>{details && <span>{details}</span>}{event.severity !== 'info' && <em>{event.severity === 'error' ? 'Ошибка' : 'Внимание'}</em>}</div>
|
||||||
</li>;
|
</li>;
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import type { ActivityJournalEvent } from '../../../shared/activityJournal.js';
|
||||||
|
|
||||||
|
export interface ActivityJournalRefreshStreak {
|
||||||
|
count: number;
|
||||||
|
firstOccurredAt: string;
|
||||||
|
profileLabels: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityJournalDisplayItem {
|
||||||
|
event: ActivityJournalEvent;
|
||||||
|
refreshStreak: ActivityJournalRefreshStreak | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function localDay(value: string) {
|
||||||
|
const date = new Date(value);
|
||||||
|
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRoutineRefresh(event: ActivityJournalEvent) {
|
||||||
|
return event.type === 'subscription.refreshed'
|
||||||
|
&& event.severity === 'info'
|
||||||
|
&& Number(event.data.added || 0) === 0
|
||||||
|
&& Number(event.data.removed || 0) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compactActivityJournalEvents(events: ActivityJournalEvent[]) {
|
||||||
|
const items: ActivityJournalDisplayItem[] = [];
|
||||||
|
for (let index = 0; index < events.length;) {
|
||||||
|
const event = events[index];
|
||||||
|
if (!isRoutineRefresh(event)) {
|
||||||
|
items.push({ event, refreshStreak: null });
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const day = localDay(event.occurredAt);
|
||||||
|
let end = index + 1;
|
||||||
|
while (end < events.length && isRoutineRefresh(events[end]) && localDay(events[end].occurredAt) === day) {
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
const streakEvents = events.slice(index, end);
|
||||||
|
if (streakEvents.length === 1) {
|
||||||
|
items.push({ event, refreshStreak: null });
|
||||||
|
} else {
|
||||||
|
const profileLabels = [...new Set(streakEvents.map(({ data }) => String(data.profileLabel || 'Подписка')))];
|
||||||
|
items.push({
|
||||||
|
event,
|
||||||
|
refreshStreak: {
|
||||||
|
count: streakEvents.length,
|
||||||
|
firstOccurredAt: streakEvents.at(-1)?.occurredAt || event.occurredAt,
|
||||||
|
profileLabels,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
index = end;
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function plural(value: number, one: string, few: string, many: string) {
|
||||||
|
const tens = value % 100;
|
||||||
|
const units = value % 10;
|
||||||
|
if (tens < 11 || tens > 14) {
|
||||||
|
if (units === 1) return one;
|
||||||
|
if (units >= 2 && units <= 4) return few;
|
||||||
|
}
|
||||||
|
return many;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatObservedDuration(firstOccurredAt: string, lastOccurredAt: string) {
|
||||||
|
const minutes = Math.max(0, Math.round((Date.parse(lastOccurredAt) - Date.parse(firstOccurredAt)) / 60_000));
|
||||||
|
if (minutes < 1) return 'меньше минуты';
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const remainingMinutes = minutes % 60;
|
||||||
|
return [hours ? `${hours} ч` : '', remainingMinutes ? `${remainingMinutes} мин` : ''].filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function refreshStreakCopy(item: ActivityJournalDisplayItem): [string, string] | null {
|
||||||
|
const streak = item.refreshStreak;
|
||||||
|
if (!streak) return null;
|
||||||
|
const profile = streak.profileLabels.length === 1
|
||||||
|
? streak.profileLabels[0]
|
||||||
|
: `подписок: ${streak.profileLabels.length}`;
|
||||||
|
const updates = `${streak.count} ${plural(streak.count, 'обновление', 'обновления', 'обновлений')}`;
|
||||||
|
return [
|
||||||
|
'Подписки обновлялись без ошибок',
|
||||||
|
`${formatObservedDuration(streak.firstOccurredAt, item.event.occurredAt)} всё хорошо · ${updates} · ${profile}`,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -10,13 +10,23 @@
|
|||||||
.client-journal-groups section { display: grid; gap: 7px; }
|
.client-journal-groups section { display: grid; gap: 7px; }
|
||||||
.client-journal-groups h3 { margin: 0; }
|
.client-journal-groups h3 { margin: 0; }
|
||||||
.client-journal-groups ol { display: grid; margin: 0; padding: 0; }
|
.client-journal-groups ol { display: grid; margin: 0; padding: 0; }
|
||||||
.client-journal-groups li { min-height: 58px; display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 14px; align-items: start; padding: 10px 0; border-top: 1px solid color-mix(in oklch, var(--client-border) 48%, transparent); }
|
.client-journal-groups li { position: relative; min-height: 58px; display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 14px; align-items: start; padding: 10px 0; border-top: 1px solid color-mix(in oklch, var(--client-border) 48%, transparent); }
|
||||||
.client-journal-time, .client-journal-groups li > div:last-child { min-width: 0; display: grid; gap: 3px; }
|
.client-journal-time, .client-journal-groups li > div:last-child { min-width: 0; display: grid; gap: 3px; }
|
||||||
.client-journal-time time { color: var(--client-text); font: var(--type-data); font-variant-numeric: var(--numeric-tabular); letter-spacing: var(--type-data-tracking); text-transform: var(--type-data-transform); }
|
.client-journal-time time { color: var(--client-text); font: var(--type-data); font-variant-numeric: var(--numeric-tabular); letter-spacing: var(--type-data-tracking); text-transform: var(--type-data-transform); }
|
||||||
.client-journal-time span, .client-journal-groups li span { color: var(--client-muted); font: var(--type-label); letter-spacing: var(--type-label-tracking); text-transform: var(--type-label-transform); overflow-wrap: anywhere; }
|
.client-journal-time span, .client-journal-groups li span { color: var(--client-muted); font: var(--type-label); letter-spacing: var(--type-label-tracking); text-transform: var(--type-label-transform); overflow-wrap: anywhere; }
|
||||||
.client-journal-time span { text-transform: var(--type-label-transform); }
|
.client-journal-time span { text-transform: var(--type-label-transform); }
|
||||||
.client-journal-groups li strong { font: var(--type-body); letter-spacing: var(--type-body-tracking); text-transform: var(--type-body-transform); }
|
.client-journal-groups li strong { font: var(--type-body); letter-spacing: var(--type-body-tracking); text-transform: var(--type-body-transform); }
|
||||||
.client-journal-groups li em { width: fit-content; color: oklch(0.68 0.15 28); font: var(--type-label); font-style: normal; letter-spacing: var(--type-label-tracking); text-transform: var(--type-label-transform); }
|
.client-journal-groups li em { width: fit-content; color: oklch(0.68 0.15 28); font: var(--type-label); font-style: normal; letter-spacing: var(--type-label-tracking); text-transform: var(--type-label-transform); }
|
||||||
|
.client-journal-event.is-switch-primary::before,
|
||||||
|
.client-journal-event.is-switch-reserve::before,
|
||||||
|
.client-journal-event.is-warning::before,
|
||||||
|
.client-journal-event.is-error::before { content: ''; position: absolute; inset: 9px auto 9px -8px; width: 3px; border-radius: 999px; }
|
||||||
|
.client-journal-event.is-switch-primary::before { background: var(--harbor-connect); }
|
||||||
|
.client-journal-event.is-switch-reserve::before { background: var(--harbor-gateway); }
|
||||||
|
.client-journal-event.is-warning::before { background: oklch(0.68 0.14 72); }
|
||||||
|
.client-journal-event.is-error::before { background: oklch(0.68 0.15 28); }
|
||||||
|
.client-journal-event.is-warning em { color: oklch(0.68 0.14 72); }
|
||||||
|
.client-journal-event.is-error em { color: oklch(0.68 0.15 28); }
|
||||||
.client-journal-error, .client-journal-empty { min-height: 80px; margin: 0 8px; color: var(--client-muted); font: var(--type-body); letter-spacing: var(--type-body-tracking); text-transform: var(--type-body-transform); }
|
.client-journal-error, .client-journal-empty { min-height: 80px; margin: 0 8px; color: var(--client-muted); font: var(--type-body); letter-spacing: var(--type-body-tracking); text-transform: var(--type-body-transform); }
|
||||||
.client-journal-error button, .client-journal-footer button { border: 0; background: transparent; color: var(--client-accent); font: var(--type-control); letter-spacing: var(--type-control-tracking); text-transform: var(--type-control-transform); cursor: pointer; }
|
.client-journal-error button, .client-journal-footer button { border: 0; background: transparent; color: var(--client-accent); font: var(--type-control); letter-spacing: var(--type-control-tracking); text-transform: var(--type-control-transform); cursor: pointer; }
|
||||||
.client-journal-skeleton { display: grid; gap: 14px; margin: 0 8px; }
|
.client-journal-skeleton { display: grid; gap: 14px; margin: 0 8px; }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const root = path.resolve(import.meta.dirname, '../..');
|
|||||||
const page = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8');
|
const page = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8');
|
||||||
const feature = fs.readFileSync(path.join(root, 'src/web/features/activity-journal/ActivityJournalFeature.tsx'), 'utf8');
|
const feature = fs.readFileSync(path.join(root, 'src/web/features/activity-journal/ActivityJournalFeature.tsx'), 'utf8');
|
||||||
const styles = fs.readFileSync(path.join(root, 'src/web/styles/features/activity-journal.css'), 'utf8');
|
const styles = fs.readFileSync(path.join(root, 'src/web/styles/features/activity-journal.css'), 'utf8');
|
||||||
|
const model = fs.readFileSync(path.join(root, 'src/web/features/activity-journal/activityJournalModel.ts'), 'utf8');
|
||||||
|
|
||||||
test('journal is the last Gateway-only drawer and refreshes whenever it opens', () => {
|
test('journal is the last Gateway-only drawer and refreshes whenever it opens', () => {
|
||||||
assert.match(page, /<RoutingToggle[\s\S]*\{isGateway && <ActivityJournalToggle/);
|
assert.match(page, /<RoutingToggle[\s\S]*\{isGateway && <ActivityJournalToggle/);
|
||||||
@@ -24,4 +25,10 @@ test('journal presents 30-day grouped history with refresh, pagination and stabl
|
|||||||
assert.match(feature, /Журнал временно недоступен[\s\S]*Повторить/);
|
assert.match(feature, /Журнал временно недоступен[\s\S]*Повторить/);
|
||||||
assert.match(styles, /\.client-journal-skeleton/);
|
assert.match(styles, /\.client-journal-skeleton/);
|
||||||
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)/);
|
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)/);
|
||||||
|
assert.match(feature, /compactActivityJournalEvents\(events\)/);
|
||||||
|
assert.match(model, /Подписки обновлялись без ошибок/);
|
||||||
|
assert.match(styles, /\.client-journal-event\.is-switch-primary::before \{ background: var\(--harbor-connect\); \}/);
|
||||||
|
assert.match(styles, /\.client-journal-event\.is-switch-reserve::before \{ background: var\(--harbor-gateway\); \}/);
|
||||||
|
assert.match(styles, /\.client-journal-event\.is-warning::before \{ background: oklch\(0\.68 0\.14 72\); \}/);
|
||||||
|
assert.match(styles, /\.client-journal-event\.is-error::before \{ background: oklch\(0\.68 0\.15 28\); \}/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import {
|
||||||
|
compactActivityJournalEvents,
|
||||||
|
refreshStreakCopy,
|
||||||
|
} from '../../.test-dist/src/web/features/activity-journal/activityJournalModel.js';
|
||||||
|
|
||||||
|
function event(id, occurredAt, type = 'subscription.refreshed', data = {}) {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
occurredAt,
|
||||||
|
type,
|
||||||
|
severity: 'info',
|
||||||
|
source: type.startsWith('subscription.') ? 'subscription' : 'connection',
|
||||||
|
dedupeKey: null,
|
||||||
|
data: type === 'subscription.refreshed'
|
||||||
|
? { profileLabel: 'Дом', serverCount: 8, added: 0, removed: 0, ...data }
|
||||||
|
: data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('adjacent unchanged refreshes become one observed success streak', () => {
|
||||||
|
const items = compactActivityJournalEvents([
|
||||||
|
event('newest', '2026-08-25T15:00:00.000Z', 'subscription.refreshed', { profileLabel: 'Дом' }),
|
||||||
|
event('middle', '2026-08-25T13:30:00.000Z', 'subscription.refreshed', { profileLabel: 'Работа' }),
|
||||||
|
event('oldest', '2026-08-25T12:00:00.000Z', 'subscription.refreshed', { profileLabel: 'Дом' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(items.length, 1);
|
||||||
|
assert.deepEqual(items[0].refreshStreak, {
|
||||||
|
count: 3,
|
||||||
|
firstOccurredAt: '2026-08-25T12:00:00.000Z',
|
||||||
|
profileLabels: ['Дом', 'Работа'],
|
||||||
|
});
|
||||||
|
assert.deepEqual(refreshStreakCopy(items[0]), [
|
||||||
|
'Подписки обновлялись без ошибок',
|
||||||
|
'3 ч всё хорошо · 3 обновления · подписок: 2',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('another event and a changed server list break refresh streaks', () => {
|
||||||
|
const items = compactActivityJournalEvents([
|
||||||
|
event('routine-new', '2026-08-25T15:00:00.000Z'),
|
||||||
|
event('started', '2026-08-25T14:50:00.000Z', 'connection.started', { profileLabel: 'Дом', serverLabel: 'Амстердам' }),
|
||||||
|
event('changed', '2026-08-25T14:45:00.000Z', 'subscription.refreshed', { added: 1 }),
|
||||||
|
event('routine-old', '2026-08-25T14:30:00.000Z'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(items.length, 4);
|
||||||
|
assert.ok(items.every(({ refreshStreak }) => refreshStreak === null));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a local calendar-day boundary breaks an otherwise adjacent streak', () => {
|
||||||
|
const newer = new Date(2026, 7, 25, 0, 5).toISOString();
|
||||||
|
const older = new Date(2026, 7, 24, 23, 55).toISOString();
|
||||||
|
const items = compactActivityJournalEvents([
|
||||||
|
event('new-day', newer),
|
||||||
|
event('old-day', older),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(items.length, 2);
|
||||||
|
assert.ok(items.every(({ refreshStreak }) => refreshStreak === null));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('one-profile streak names the profile and keeps the latest event identity', () => {
|
||||||
|
const items = compactActivityJournalEvents([
|
||||||
|
event('newest', '2026-08-25T15:15:00.000Z'),
|
||||||
|
event('oldest', '2026-08-25T15:00:00.000Z'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(items[0].event.id, 'newest');
|
||||||
|
assert.deepEqual(refreshStreakCopy(items[0]), [
|
||||||
|
'Подписки обновлялись без ошибок',
|
||||||
|
'15 мин всё хорошо · 2 обновления · Дом',
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -39,26 +39,26 @@ const expectedImports = [
|
|||||||
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
||||||
const acceptedLedger = {
|
const acceptedLedger = {
|
||||||
counts: {
|
counts: {
|
||||||
cascadeEdges: 1037,
|
cascadeEdges: 1098,
|
||||||
customProperties: 106,
|
customProperties: 106,
|
||||||
declarations: 4110,
|
declarations: 4122,
|
||||||
important: 0,
|
important: 0,
|
||||||
keyframes: 50,
|
keyframes: 50,
|
||||||
media: 17,
|
media: 17,
|
||||||
rules: 1106,
|
rules: 1113,
|
||||||
variableReferences: 1049,
|
variableReferences: 1051,
|
||||||
},
|
},
|
||||||
hashes: {
|
hashes: {
|
||||||
cascadeEdges: 'da134201db19eb45f82a1169f571fbea293ff8a76c6bcb24d3b10b060bba2fbd',
|
cascadeEdges: '9814b59202515f6993f2836d529b7bb9e25e632b0803c832a363ac264e5b60a3',
|
||||||
customProperties: 'fd6f16069f9fe3526f09d4d574431ddae67150d8e7a8a40e04c9fd2d179ec7ac',
|
customProperties: 'fd6f16069f9fe3526f09d4d574431ddae67150d8e7a8a40e04c9fd2d179ec7ac',
|
||||||
declarations: '89d33ebe8f3b462b0dcb3ae45bf2710d9a8633033047d025aa317b9e35d4d2b1',
|
declarations: 'dcbf716667469136ad6c25412fb24741c3b66749ceefb799d5785a6668dbf9de',
|
||||||
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
|
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
|
||||||
duplicateSelectors: '8982145dba05b33bf1c93b2d54cfbe76305b276ff3c657aa020144016ada5848',
|
duplicateSelectors: '8982145dba05b33bf1c93b2d54cfbe76305b276ff3c657aa020144016ada5848',
|
||||||
keyframes: 'af4b9ae18d070fd2462a9b050293f3821bf5017c6e7cc53bbe18dc826f0fe3b9',
|
keyframes: 'af4b9ae18d070fd2462a9b050293f3821bf5017c6e7cc53bbe18dc826f0fe3b9',
|
||||||
ruleDeclarationSequences: '17a00d972ccc4ecb5450cfc5abd385602b6fd3911085e63c375d69df70bbda7f',
|
ruleDeclarationSequences: '55167d84597ea8e8a4c4898f8fc8431593e920dc2e089728cc18c52f31d8c2cb',
|
||||||
selectors: '28220ffd959c5f11f9956ebe90eb201c7fe3d4733b0ad6f756ecdc03f8512d13',
|
selectors: '2ab1759842aadbf1e733e9fc97946f48578903bb47d192b0b373329262982299',
|
||||||
variableReferences: '09c3fc03be5d841b5276279ae40557f75ca79ef2c8b21f7578e67c8801f2a87c',
|
variableReferences: '2065fa19b325d995769988999b4e5dc51b54e8b011af39397f980584c3fffbe4',
|
||||||
witnesses: 'ff6fe62b26aaae6c9171e176b9fd6176e13df145ce30998d83d52d696bb0ad8c',
|
witnesses: '2ade0023244f8fc6a2fed0b2e1c3a99c4055c32abd6d8bfcd0d5f117a48f05bb',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -408,8 +408,8 @@ test('main owns one public stylesheet and the regrouped production CSS is determ
|
|||||||
assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
|
assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
|
||||||
|
|
||||||
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
|
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
|
||||||
assert.deepEqual(assets, ['index-CpsQVQ8c.css']);
|
assert.deepEqual(assets, ['index-BH_msVtT.css']);
|
||||||
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
|
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
|
||||||
assert.equal(built.byteLength, 156271);
|
assert.equal(built.byteLength, 156918);
|
||||||
assert.equal(sha256(built), '244fa71e9354cb1f81233cc630b8d5014c4f8ad910ad96dae4eee637d5c312e6');
|
assert.equal(sha256(built), 'a1eafbf8c9c89a57478ba5c78d8b2a2fa1d44bcc8dc7bb747cba2889096509c5');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"src/web/features/subscription/requestError.ts",
|
"src/web/features/subscription/requestError.ts",
|
||||||
"src/web/features/servers/serverPickerModel.ts",
|
"src/web/features/servers/serverPickerModel.ts",
|
||||||
"src/web/features/instructions/instructionBlocks.ts",
|
"src/web/features/instructions/instructionBlocks.ts",
|
||||||
|
"src/web/features/activity-journal/activityJournalModel.ts",
|
||||||
"src/web/features/instructions/prometheus.ts",
|
"src/web/features/instructions/prometheus.ts",
|
||||||
"src/web/features/routing/ruleReorderModel.ts",
|
"src/web/features/routing/ruleReorderModel.ts",
|
||||||
"monitoring/grafana/harbor-gateway.json"
|
"monitoring/grafana/harbor-gateway.json"
|
||||||
|
|||||||
Reference in New Issue
Block a user