Migrate Harbor state and traffic history to SQLite
This commit is contained in:
@@ -153,7 +153,7 @@ test('Mac and Gateway traffic drawers use one feature boundary and the cached re
|
||||
|
||||
test('traffic polling runs every second only while the drawer is open and unpaused', () => {
|
||||
assert.match(feature, /const POLL_MS = 1_000/);
|
||||
assert.match(feature, /if \(!enabled \|\| !isOpen \|\| paused\) return undefined/);
|
||||
assert.match(feature, /if \(!enabled \|\| !isOpen \|\| paused \|\| view !== 'live'\) return undefined/);
|
||||
assert.match(feature, /assertLiveTrafficSnapshot\(await loadLiveTraffic\(\)\)/);
|
||||
assert.match(feature, /setSnapshot\(next\)[\s\S]*setRequestState\('ready'\)/);
|
||||
assert.match(feature, /catch \{[\s\S]*setRequestState\('error'\)/);
|
||||
@@ -175,7 +175,7 @@ test('traffic drawer exposes the requested truthful states and accessible contro
|
||||
'Только трафик через Harbor Connect. Приложения macOS недоступны внутри Docker.',
|
||||
'Только соединения, прошедшие через sing-box Gateway. Трафик, обходящий sing-box напрямую, здесь не виден.',
|
||||
]) assert.match(feature, new RegExp(copy.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
assert.match(feature, /\{feature\.isGateway \? 'GATEWAY' : 'MAC'\} · \{snapshot\?\.summary\.active \|\| 0\} АКТИВНЫХ/);
|
||||
assert.match(feature, /feature\.view === 'live' \? `\$\{snapshot\?\.summary\.active \|\| 0\} АКТИВНЫХ` : 'ИСТОРИЯ'/);
|
||||
assert.match(feature, /feature\.isGateway[\s\S]*Инспектор трафика выключен в настройках Harbor Gateway\.[\s\S]*Инспектор трафика выключен в настройках Harbor Connect\./);
|
||||
assert.match(feature, /feature\.isGateway[\s\S]*Только соединения, прошедшие через sing-box Gateway\. Трафик, обходящий sing-box напрямую, здесь не виден\.[\s\S]*Только трафик через Harbor Connect\. Приложения macOS недоступны внутри Docker\./);
|
||||
assert.match(feature, /aria-label="Найти устройство"/);
|
||||
@@ -187,7 +187,8 @@ test('traffic drawer exposes the requested truthful states and accessible contro
|
||||
assert.match(feature, /const \[expandedId, setExpandedId\] = useState\(''\)/);
|
||||
assert.match(feature, /aria-expanded=\{expanded\}[\s\S]*aria-controls=\{detailsId\}/);
|
||||
assert.match(feature, /Источник[\s\S]*Назначение[\s\S]*Правило[\s\S]*Цепочка/);
|
||||
assert.doesNotMatch(feature, /closeConnection|reroute|history|sessionStorage/);
|
||||
assert.doesNotMatch(feature, /closeConnection|reroute|sessionStorage/);
|
||||
assert.match(feature, /TrafficHistoryPanel/);
|
||||
});
|
||||
|
||||
test('traffic retention and grouping use canonical server settings and the frozen server observation clock', () => {
|
||||
|
||||
@@ -40,26 +40,26 @@ const expectedImports = [
|
||||
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
||||
const acceptedLedger = {
|
||||
counts: {
|
||||
cascadeEdges: 1188,
|
||||
cascadeEdges: 1190,
|
||||
customProperties: 115,
|
||||
declarations: 4862,
|
||||
declarations: 4872,
|
||||
important: 0,
|
||||
keyframes: 55,
|
||||
media: 23,
|
||||
rules: 1329,
|
||||
variableReferences: 1270,
|
||||
rules: 1332,
|
||||
variableReferences: 1274,
|
||||
},
|
||||
hashes: {
|
||||
cascadeEdges: '3d02c10ab8fd95f7eed306048e9b59ec391a0febaa9e3e235badcff3b042eef7',
|
||||
cascadeEdges: '07be381535d1cd8f78990a5b2eae27903471ec2c6cbe1589e36e8f1797f1e610',
|
||||
customProperties: '81d1e70737ae5a8e4b20d4c1be1b24685975a404863444da4a8821be1f0d5097',
|
||||
declarations: '0f31e2cdeeab02b9e0a845a6b50ff7e3f035a441217a418e7b5465b5a296ae16',
|
||||
declarations: 'bd186c9317ee7ff540b4eeddfc33d2c4ab64a1b2a9362f95480d180d4f644605',
|
||||
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
|
||||
duplicateSelectors: '8982145dba05b33bf1c93b2d54cfbe76305b276ff3c657aa020144016ada5848',
|
||||
keyframes: 'b9b0bf3fad92e69b93ec0c24f01da15e78bbe89a095597d64ce4f7ef5e272fdd',
|
||||
ruleDeclarationSequences: '33a0dff2428a8e712776d40d796ea05d169544eb1af02ad350759061fb2a3c3c',
|
||||
selectors: '5734c69a504c59025e2e6b1637cfb6d1905ca1ae358783d0916a694a973eea22',
|
||||
variableReferences: '49ad724b2812128f5344fdd55b177aa96ea9b30eb4d878dc86f2e4f3fa898182',
|
||||
witnesses: '4e357ee4f7310d60399baa3099bd8f4ed320debc1e3c9a09f55b892a1178049c',
|
||||
ruleDeclarationSequences: 'b066b7e20fd1195c67c23c38cbaf7b58edcd6356e62bd173371234206c74817e',
|
||||
selectors: '2e38ff14b0a7d581b090b520c92b6bff60c84082db4dc20a49c454ef468fbeb5',
|
||||
variableReferences: '5fd2c93d2467be16976c102b595a5fb15c5692e2d98645022d7a9acf2f42d829',
|
||||
witnesses: 'e9ae2a8416aa44a75a01452bc17884dcb133cccb939880cdecc761ea1043721c',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -212,7 +212,7 @@ test('client typography uses the shared semantic scale outside the token owner',
|
||||
|
||||
test('accepted stylesheet has pinned declaration, selector, keyframe, variable, and cascade ledgers', () => {
|
||||
const witnesses = readStyleWitnesses(root);
|
||||
assert.equal(witnesses.length, 1375);
|
||||
assert.equal(witnesses.length, 1486);
|
||||
assert.equal(witnesses.filter((witness) => witness.unknown || witness.ancestorUnknown).length, 0);
|
||||
const ledger = createStyleLedger(readStyleSource(root), { witnesses });
|
||||
assert.deepEqual(ledger.counts, acceptedLedger.counts);
|
||||
@@ -315,6 +315,12 @@ test('JSX witnesses keep real multi-class collisions and exclude impossible elem
|
||||
assert.equal(conservativeSibling.counts.cascadeEdges, 1);
|
||||
});
|
||||
|
||||
test('JSX witness recursion requires an explicit finite bound for the four-level history tree', () => {
|
||||
const sources = [{ file: '/fixture/App.tsx', source: 'export function App() { return <Branch />; } function Branch() { return <div className="branch"><Branch /></div>; }' }];
|
||||
assert.throws(() => createStyleWitnesses(sources), /Recursive JSX witness/);
|
||||
assert.equal(createStyleWitnesses(sources, { recursionLimits: { Branch: 4 } }).length, 4);
|
||||
});
|
||||
|
||||
test('selector proof uses the observed level-four grammar and exact specificity', () => {
|
||||
const fixtures = [
|
||||
['#root', [{ a: 1, b: 0, c: 0 }]],
|
||||
@@ -405,8 +411,8 @@ test('main owns one public stylesheet and the regrouped production CSS is determ
|
||||
assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
|
||||
|
||||
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
|
||||
assert.deepEqual(assets, ['index-fllV-PfI.css']);
|
||||
assert.deepEqual(assets, ['index-D6ACNk74.css']);
|
||||
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
|
||||
assert.equal(built.byteLength, 184958);
|
||||
assert.equal(sha256(built), '3a9ed6fdf6c5d81234de9aa0abd995bc9db1cfe298194162654edcbfe3ae7c62');
|
||||
assert.equal(built.byteLength, 185399);
|
||||
assert.equal(sha256(built), '3327b4873e7dba63fd44c21d34c4fe19f78167ef6c5badcfd8dfe08eb2705c5d');
|
||||
});
|
||||
|
||||
@@ -349,7 +349,7 @@ function bindCallParameters(callable, argumentsList, callerEnvironment, callerFi
|
||||
return environment;
|
||||
}
|
||||
|
||||
export function createStyleWitnesses(sources, { entry = 'App' } = {}) {
|
||||
export function createStyleWitnesses(sources, { entry = 'App', recursionLimits = {} } = {}) {
|
||||
const files = sources.map(({ file, source }) => ({ file, source, parsed: parseWitnessFile(file, source) }));
|
||||
const { definitions, definitionNodes } = componentDefinitions(files);
|
||||
const entryDefinition = definitions.get(entry);
|
||||
@@ -361,7 +361,11 @@ export function createStyleWitnesses(sources, { entry = 'App' } = {}) {
|
||||
const dynamicByFile = dynamicClassBindings(files);
|
||||
const witnesses = [];
|
||||
const expand = (definition, ancestors, ancestorUnknown, environment, stack) => {
|
||||
if (stack.includes(definition)) throw new TypeError(`Recursive JSX witness component: ${definition.node.id?.name || definition.file}`);
|
||||
if (stack.includes(definition)) {
|
||||
const limit = recursionLimits[definition.node.id?.name];
|
||||
if (!Number.isInteger(limit) || limit < 1) throw new TypeError(`Recursive JSX witness component: ${definition.node.id?.name || definition.file}`);
|
||||
if (stack.filter((item) => item === definition).length >= limit) return;
|
||||
}
|
||||
const nextStack = [...stack, definition];
|
||||
const entryEnvironment = withLocalJsxBindings(definition.node.body, environment, definitionNodes, definition.file);
|
||||
const visit = (
|
||||
@@ -474,7 +478,8 @@ export function readStyleWitnesses(root) {
|
||||
file,
|
||||
source: fs.readFileSync(file, 'utf8'),
|
||||
}));
|
||||
return createStyleWitnesses(files);
|
||||
// History has exactly service/domain/hostname/IP levels, not arbitrary JSX recursion.
|
||||
return createStyleWitnesses(files, { recursionLimits: { HistoryRows: 4, HistoryBranch: 3 } });
|
||||
}
|
||||
|
||||
const OBSERVED_PROPERTIES = new Set(`
|
||||
|
||||
Reference in New Issue
Block a user