chore(test): проверить ограниченное восстановление из хранилища через интерфейс
This commit is contained in:
1 parent
0216415953
commit
4b028be241
10 files changed
+311
-9
No files matched your search
@@ -0,0 +1,66 @@
|
|||||||
|
const fs = require('fs'), assert = require('node:assert/strict'), { createHash } = require('crypto');
|
||||||
|
const { chromium, expect } = require('@playwright/test');
|
||||||
|
(async () => {
|
||||||
|
const phase = process.argv[2], portal = JSON.parse(fs.readFileSync('tmp/portal195-private.json'));
|
||||||
|
const state = JSON.parse(fs.readFileSync('tmp/state239-public.json'));
|
||||||
|
const browser = await chromium.launch({ channel: 'msedge', headless: true });
|
||||||
|
try {
|
||||||
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }), errors = [], receipts = [];
|
||||||
|
page.on('pageerror', e => errors.push(e.message));
|
||||||
|
page.on('response', r => { if (new URL(r.url()).pathname.startsWith('/api/backups') && r.request().method() !== 'GET') receipts.push({ method: r.request().method(), path: new URL(r.url()).pathname, status: r.status() }); });
|
||||||
|
await page.goto('http://localhost:3000/backups');
|
||||||
|
await page.getByLabel('Email', { exact: true }).fill(portal.email);
|
||||||
|
await page.getByLabel('Пароль', { exact: true }).fill(portal.password);
|
||||||
|
await page.getByRole('button', { name: 'Войти', exact: true }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Резервные копии', exact: true })).toBeVisible({ timeout: 30000 });
|
||||||
|
if (phase === 'configure') {
|
||||||
|
const s = JSON.parse(fs.readFileSync('tmp/s3-ui239-private.json'));
|
||||||
|
const paused = await page.evaluate(async () => {
|
||||||
|
const r = await fetch('/api/backups/schedule', { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + localStorage.getItem('mmapp_token') }, body: JSON.stringify({ enabled: false }) });
|
||||||
|
return { status: r.status, body: await r.json() };
|
||||||
|
});
|
||||||
|
assert.equal(paused.status, 200); assert.equal(paused.body.enabled, false);
|
||||||
|
await page.getByRole('tab', { name: 'Настройки', exact: true }).click();
|
||||||
|
await page.getByRole('button', { name: 'S3', exact: true }).click();
|
||||||
|
for (const [label, value] of Object.entries({ Endpoint: s.endpoint, Region: s.region, Bucket: s.bucket, Prefix: state.prefix, 'Access key': s.accessKey, 'Secret key': s.secretKey })) await page.getByLabel(label, { exact: true }).fill(value);
|
||||||
|
const path = page.getByRole('switch', { name: 'Path-style', exact: true });
|
||||||
|
if (await path.getAttribute('aria-checked') === 'true') await path.click();
|
||||||
|
const saved = page.waitForResponse(r => new URL(r.url()).pathname === '/api/backups/storage' && r.request().method() === 'PUT');
|
||||||
|
await page.getByRole('button', { name: 'Сохранить хранилище', exact: true }).click();
|
||||||
|
assert.equal((await saved).status(), 200);
|
||||||
|
await expect(page.getByLabel('Secret key', { exact: true })).toHaveValue('');
|
||||||
|
const tested = page.waitForResponse(r => new URL(r.url()).pathname === '/api/backups/storage/test' && r.request().method() === 'POST');
|
||||||
|
await page.getByRole('button', { name: 'Проверить', exact: true }).click();
|
||||||
|
assert.equal((await tested).status(), 200);
|
||||||
|
} else {
|
||||||
|
await page.getByRole('button', { name: 'Обновить', exact: true }).click();
|
||||||
|
await expect(page.getByText(state.filename, { exact: true })).toBeVisible();
|
||||||
|
if (phase === 'restore') {
|
||||||
|
const download = page.waitForEvent('download');
|
||||||
|
await page.getByRole('button', { name: 'Скачать ' + state.filename, exact: true }).click();
|
||||||
|
const d = await download; assert.equal(d.suggestedFilename(), state.filename);
|
||||||
|
const bytes = fs.readFileSync(await d.path());
|
||||||
|
const expected = Buffer.from(`# Own limited UI restore 239\n/interface bridge add name=${state.bridge} comment=mm-ui239\n`);
|
||||||
|
assert.deepEqual(bytes, expected);
|
||||||
|
await page.getByRole('button', { name: 'Восстановить ' + state.filename, exact: true }).click();
|
||||||
|
const dialog = page.getByRole('alertdialog');
|
||||||
|
await expect(dialog).toContainText(state.filename);
|
||||||
|
const restored = page.waitForResponse(r => new URL(r.url()).pathname === '/api/backups/' + state.id + '/restore' && r.request().method() === 'POST');
|
||||||
|
await dialog.getByRole('button', { name: 'Восстановить', exact: true }).click();
|
||||||
|
const r = await restored; assert.equal(r.status(), 200);
|
||||||
|
const result = await r.json(); assert.equal(result.backupId, state.id); assert.equal(result.serverId, state.serverId);
|
||||||
|
await expect(dialog).toBeHidden();
|
||||||
|
fs.writeFileSync('docs/ux-ui/acceptance/s3-ui239-browser.json', JSON.stringify({ date: '2026-10-14', block: '12.5.239', realPortalLogin: true, noMocks: true, passed: true, downloadBytes: bytes.length, downloadSha256: createHash('sha256').update(bytes).digest('hex'), restoreStatus: 200, restoreReceiptMatched: true, receipts, pageErrors: errors, fullConfigurationApplied: false }, null, 2) + '\n');
|
||||||
|
} else if (phase === 'delete') {
|
||||||
|
await page.getByRole('button', { name: 'Удалить ' + state.filename, exact: true }).click();
|
||||||
|
const deleted = page.waitForResponse(r => new URL(r.url()).pathname === '/api/backups/' + state.id && r.request().method() === 'DELETE');
|
||||||
|
await page.getByRole('alertdialog').getByRole('button', { name: 'Удалить', exact: true }).click();
|
||||||
|
assert.equal((await deleted).status(), 204); await expect(page.getByRole('alertdialog')).toBeHidden();
|
||||||
|
await expect(page.getByText(state.filename, { exact: true })).toHaveCount(0);
|
||||||
|
const p = 'docs/ux-ui/acceptance/s3-ui239-browser.json', report = JSON.parse(fs.readFileSync(p));
|
||||||
|
Object.assign(report, { deleteStatus: 204, ownRowRemovedThroughUi: true, deleteReceipts: receipts }); fs.writeFileSync(p, JSON.stringify(report, null, 2) + '\n');
|
||||||
|
} else throw Error('Unsupported phase');
|
||||||
|
}
|
||||||
|
assert.deepEqual(errors, []); console.log('S3 UI239 ' + phase + ' passed');
|
||||||
|
} finally { await browser.close(); }
|
||||||
|
})().catch(e => { console.error(e.message); process.exitCode = 1; });
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { randomUUID, createHash } from 'node:crypto';
|
||||||
|
import { createRequire } from 'node:module';
|
||||||
|
const require = createRequire('/app/backend/package.json');
|
||||||
|
const { ListObjectVersionsCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
|
||||||
|
const { pool } = await import('/app/backend/dist/db/index.js');
|
||||||
|
const { MikrotikClient } = await import('/app/backend/dist/services/mikrotik.js');
|
||||||
|
const { createS3ClientFromConfig, s3PutObject, s3GetObject } = await import('/app/backend/dist/services/s3-backup-client.js');
|
||||||
|
const phase = process.argv[2], statePath = '/checks/state239-private.json', publicPath = '/checks/s3-ui239-native.json';
|
||||||
|
const config = { endpoint: process.env.MMAPP_TEST_S3_ENDPOINT, region: process.env.MMAPP_TEST_S3_REGION, bucket: process.env.MMAPP_TEST_S3_BUCKET, accessKeyId: process.env.MMAPP_TEST_S3_ACCESS_KEY, secretAccessKey: process.env.MMAPP_TEST_S3_SECRET_KEY, forcePathStyle: false };
|
||||||
|
assert.equal(config.endpoint, 'https://s3.ru-1.storage.selcloud.ru');
|
||||||
|
assert.equal(config.bucket, 'mikrotik-test-s3'); assert(config.secretAccessKey);
|
||||||
|
const s3 = createS3ClientFromConfig(config);
|
||||||
|
const server = (await pool.query("SELECT * FROM servers WHERE host='194.226.121.150' AND enabled=true")).rows;
|
||||||
|
assert.equal(server.length, 1);
|
||||||
|
const client = new MikrotikClient({ host: server[0].host, port: server[0].port, useSsl: server[0].use_ssl, verifySsl: false, username: server[0].username, password: server[0].password });
|
||||||
|
const rows = async table => (await pool.query(`SELECT to_jsonb(${table}) AS row FROM ${table} ORDER BY id`)).rows.map(r => r.row);
|
||||||
|
const read = endpoint => client.get(endpoint, 10000);
|
||||||
|
const save = state => fs.writeFileSync(statePath, JSON.stringify(state), { mode: 0o600 });
|
||||||
|
async function versions(state) {
|
||||||
|
const result = []; let keyMarker, versionMarker;
|
||||||
|
for (let n = 0; n < 10; n++) {
|
||||||
|
const r = await s3.send(new ListObjectVersionsCommand({ Bucket: config.bucket, Prefix: state.prefix + '/', KeyMarker: keyMarker, VersionIdMarker: versionMarker }));
|
||||||
|
result.push(...(r.Versions ?? []).map(v => ({ ...v, kind: 'version' })), ...(r.DeleteMarkers ?? []).map(v => ({ ...v, kind: 'delete-marker' })));
|
||||||
|
if (!r.IsTruncated) return result;
|
||||||
|
assert(r.NextKeyMarker || r.NextVersionIdMarker); keyMarker = r.NextKeyMarker; versionMarker = r.NextVersionIdMarker;
|
||||||
|
} throw Error('Own version pagination limit');
|
||||||
|
}
|
||||||
|
async function restoreTable(table, original) {
|
||||||
|
assert.equal(original.length, 1); assert.equal(original[0].id, 1);
|
||||||
|
const columns = Object.keys(original[0]).filter(k => k !== 'id');
|
||||||
|
assert(columns.every(k => /^[a-z][a-z0-9_]*$/.test(k)));
|
||||||
|
await pool.query(`UPDATE ${table} SET ${columns.map((k, n) => `"${k}"=$${n + 1}`).join(',')} WHERE id=1`, columns.map(k => {
|
||||||
|
const value = original[0][k]; return value && typeof value === 'object' ? JSON.stringify(value) : value;
|
||||||
|
}));
|
||||||
|
assert.deepEqual(await rows(table), original);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (phase === 'init') {
|
||||||
|
assert(!fs.existsSync(statePath));
|
||||||
|
const uuid = randomUUID(), bridge = 'mm-ui239-' + uuid.slice(0, 12), filename = 'mm-ui239-' + uuid + '.rsc';
|
||||||
|
const state = { id: 'mm-ui239-' + uuid, prefix: 'mmapp-acceptance/' + uuid, bridge, filename, key: 'mmapp-acceptance/' + uuid + '/' + filename, serverId: Number(server[0].id), serverName: server[0].name, storage: await rows('backup_storage_settings'), schedule: await rows('backup_schedule_settings'), catalog: await rows('backup_entries'), bridges: await read('/interface/bridge'), files: await read('/file') };
|
||||||
|
assert.equal(state.storage[0].provider, 'local'); assert(state.catalog.every(r => r.storage === 'local' && !r.s3_key));
|
||||||
|
assert(!state.bridges.some(b => b.name === bridge)); assert(!state.files.some(f => f.name === filename));
|
||||||
|
assert.equal((await versions(state)).length, 0); save(state);
|
||||||
|
fs.writeFileSync('/checks/state239-public.json', JSON.stringify({ id: state.id, prefix: state.prefix, filename, bridge, serverId: state.serverId }));
|
||||||
|
console.log('Own scope initialized; original settings/catalog saved privately');
|
||||||
|
} else {
|
||||||
|
const state = JSON.parse(fs.readFileSync(statePath));
|
||||||
|
assert(/^mm-ui239-[a-f0-9-]+\.rsc$/.test(state.filename)); assert(state.key === state.prefix + '/' + state.filename);
|
||||||
|
if (phase === 'upload') {
|
||||||
|
const storage = (await rows('backup_storage_settings'))[0];
|
||||||
|
assert.equal(storage.provider, 's3'); assert.equal(storage.s3_prefix, state.prefix); assert.equal(storage.s3_bucket, config.bucket);
|
||||||
|
assert.equal((await rows('backup_schedule_settings'))[0].enabled, false);
|
||||||
|
const script = Buffer.from(`# Own limited UI restore 239\n/interface bridge add name=${state.bridge} comment=mm-ui239\n`);
|
||||||
|
assert.equal((await versions(state)).length, 0);
|
||||||
|
await s3PutObject(s3, config.bucket, state.key, script);
|
||||||
|
assert.deepEqual(await s3GetObject(s3, config.bucket, state.key), script);
|
||||||
|
await pool.query('INSERT INTO backup_entries(id,server_id,server_name,filename,size_bytes,kind,notes,storage,s3_key,created_at) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,now())', [state.id, state.serverId, state.serverName, state.filename, script.length, 'manual', 'Own limited UI restore 239: bridge only', 's3', state.key]);
|
||||||
|
state.bytes = script.length; state.sha256 = createHash('sha256').update(script).digest('hex'); save(state);
|
||||||
|
console.log(JSON.stringify({ ownS3ScriptUploaded: true, bytes: state.bytes, catalogRowCreated: true }));
|
||||||
|
} else if (phase === 'verify') {
|
||||||
|
const bridges = await read('/interface/bridge'), own = bridges.filter(b => b.name === state.bridge);
|
||||||
|
assert.equal(own.length, 1); assert.equal(own[0].comment, 'mm-ui239');
|
||||||
|
assert.equal((await read('/interface/bridge/port')).filter(p => p.bridge === state.bridge).length, 0);
|
||||||
|
assert.equal((await read('/ip/address')).filter(p => p.interface === state.bridge).length, 0);
|
||||||
|
assert.deepEqual(bridges.filter(b => b.name !== state.bridge), state.bridges);
|
||||||
|
state.nativeVerified = true; save(state);
|
||||||
|
console.log('Native bridge confirmed; no ports/addresses, original bridges unchanged');
|
||||||
|
} else if (phase === 'cleanup') {
|
||||||
|
const own = (await read('/interface/bridge')).filter(b => b.name === state.bridge);
|
||||||
|
assert(own.length <= 1);
|
||||||
|
if (own.length) { assert.equal(own[0].comment, 'mm-ui239'); await client.delete('/interface/bridge/' + encodeURIComponent(own[0]['.id'])); }
|
||||||
|
const ownFiles = (await read('/file')).filter(f => f.name === state.filename); assert(ownFiles.length <= 1);
|
||||||
|
for (const file of ownFiles) await client.removeFile(file['.id']);
|
||||||
|
await pool.query('DELETE FROM backup_entries WHERE id=$1 AND filename=$2 AND s3_key=$3', [state.id, state.filename, state.key]);
|
||||||
|
const existing = await versions(state); assert(existing.every(v => v.Key === state.key && v.VersionId));
|
||||||
|
for (const v of existing) await s3.send(new DeleteObjectCommand({ Bucket: config.bucket, Key: v.Key, VersionId: v.VersionId }));
|
||||||
|
assert.equal((await versions(state)).length, 0);
|
||||||
|
assert.deepEqual(await rows('backup_entries'), state.catalog);
|
||||||
|
assert.deepEqual(await read('/interface/bridge'), state.bridges);
|
||||||
|
assert(!(await read('/file')).some(f => f.name === state.filename));
|
||||||
|
await restoreTable('backup_storage_settings', state.storage); await restoreTable('backup_schedule_settings', state.schedule);
|
||||||
|
const report = { date: '2026-10-14', block: '12.5.239', host: server[0].host, prefix: state.prefix, limitedScriptBytes: state.bytes ?? null, sha256: state.sha256 ?? null, nativeBridgeVerified: Boolean(state.nativeVerified), bridgeAndUploadedFileRemoved: true, originalBridgesUnchanged: true, originalCatalogAllFieldsMatched: true, originalCatalogRows: state.catalog.length, originalSettingsAllFieldsMatched: true, s3VersionsAndMarkersRemoved: existing.length, s3VersionsAndMarkersRemaining: 0, passwordChanges: 0, fullConfigurationApplied: false };
|
||||||
|
fs.writeFileSync(publicPath, JSON.stringify(report, null, 2) + '\n'); fs.unlinkSync(statePath); console.log(JSON.stringify(report));
|
||||||
|
} else throw Error('Unsupported phase');
|
||||||
|
}
|
||||||
|
} finally { s3.destroy(); await pool.end(); }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# UX/UI: протокол внешней приёмки 12.5
|
# UX/UI: протокол внешней приёмки 12.5
|
||||||
|
|
||||||
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
|
Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт.
|
||||||
|
|
||||||
IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md).
|
IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md).
|
||||||
|
|
||||||
@@ -18,6 +18,22 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro
|
|||||||
|
|
||||||
BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json).
|
BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json).
|
||||||
|
|
||||||
|
## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14)
|
||||||
|
|
||||||
|
Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR.
|
||||||
|
|
||||||
|
Собственный объект записан и прочитан SDK, затем только его строка добавлена в каталог. Через обычный UI скачан файл с совпавшими байтами/SHA256; restore вернул HTTP200 с нужными backupId/serverId. Direct RouterOS на194.226.121.150 подтвердил ровно один bridge, comment, отсутствие bridge ports/IP addresses и неизменность исходных bridges. Через UI удалена только собственная строка/объект:HTTP204, строка исчезла; pageerrors0. Восстановление на втором CHR и полный config restore этим блоком не проверялись.
|
||||||
|
|
||||||
|
Cleanup удалил свой bridge и загруженный .rsc, все версии/delete markers только точного ключа UUID prefix, сверил исходные10 SQL-строк и оба settings rows целиком. Первая попытка остановилась уже после удаления объектов: проверочный SQL-column guard не допускал цифру3 в s3_*; исправлен allowlist имён, повторная очистка прошла. Final-attempt removed0 означает, что версии удалены первой попыткой; конечный listing0 подтверждён. Общее число удалённых версий первой попытки не записано и не выдумывается. Bucket settings, чужие объекты и пароли не менялись. Приватный SQL-снимок и временные ключевые файлы workstation/Debian удалены; production-код и образы ux-backups237 не менялись.
|
||||||
|
|
||||||
|
Acceptance helpers s3-ui-limited-restore.mjs/.cjs проходят node --check. [UI-протокол](ux-ui/acceptance/s3-ui239-browser.json) и [native/cleanup](ux-ui/acceptance/s3-ui239-native.json) фиксируют точные границы. Общая регрессия не повторялась: новых runtime изменений нет.
|
||||||
|
|
||||||
|
| Референс | Схема принятого сценария |
|
||||||
|
| --- | --- |
|
||||||
|
| [ReUI Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Собственный объект S3 → строка истории → download → подтверждение restore → native bridge → delete → cleanup и исходные настройки |
|
||||||
|
|
||||||
|
Следующий240 — retention в изолированном scope без удаления существующего каталога. Полная конфигурация, массовые экспорты на реальном CHR, миграция buckets, multi-instance, другие ОС/WAN/jobs остаются отдельной приёмкой.
|
||||||
|
|
||||||
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
||||||
|
|
||||||
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
||||||
|
|||||||
+17
-1
@@ -1,9 +1,25 @@
|
|||||||
# План реализации UX/UI по пятичасовым окнам
|
# План реализации UX/UI по пятичасовым окнам
|
||||||
|
|
||||||
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
|
Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт.
|
||||||
|
|
||||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-14. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||||
|
|
||||||
|
## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14)
|
||||||
|
|
||||||
|
Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR.
|
||||||
|
|
||||||
|
Собственный объект записан и прочитан SDK, затем только его строка добавлена в каталог. Через обычный UI скачан файл с совпавшими байтами/SHA256; restore вернул HTTP200 с нужными backupId/serverId. Direct RouterOS на194.226.121.150 подтвердил ровно один bridge, comment, отсутствие bridge ports/IP addresses и неизменность исходных bridges. Через UI удалена только собственная строка/объект:HTTP204, строка исчезла; pageerrors0. Восстановление на втором CHR и полный config restore этим блоком не проверялись.
|
||||||
|
|
||||||
|
Cleanup удалил свой bridge и загруженный .rsc, все версии/delete markers только точного ключа UUID prefix, сверил исходные10 SQL-строк и оба settings rows целиком. Первая попытка остановилась уже после удаления объектов: проверочный SQL-column guard не допускал цифру3 в s3_*; исправлен allowlist имён, повторная очистка прошла. Final-attempt removed0 означает, что версии удалены первой попыткой; конечный listing0 подтверждён. Общее число удалённых версий первой попытки не записано и не выдумывается. Bucket settings, чужие объекты и пароли не менялись. Приватный SQL-снимок и временные ключевые файлы workstation/Debian удалены; production-код и образы ux-backups237 не менялись.
|
||||||
|
|
||||||
|
Acceptance helpers s3-ui-limited-restore.mjs/.cjs проходят node --check. [UI-протокол](ux-ui/acceptance/s3-ui239-browser.json) и [native/cleanup](ux-ui/acceptance/s3-ui239-native.json) фиксируют точные границы. Общая регрессия не повторялась: новых runtime изменений нет.
|
||||||
|
|
||||||
|
| Референс | Схема принятого сценария |
|
||||||
|
| --- | --- |
|
||||||
|
| [ReUI Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Собственный объект S3 → строка истории → download → подтверждение restore → native bridge → delete → cleanup и исходные настройки |
|
||||||
|
|
||||||
|
Следующий240 — retention в изолированном scope без удаления существующего каталога. Полная конфигурация, массовые экспорты на реальном CHR, миграция buckets, multi-instance, другие ОС/WAN/jobs остаются отдельной приёмкой.
|
||||||
|
|
||||||
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
||||||
|
|
||||||
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
||||||
|
|||||||
+17
-1
@@ -1,6 +1,6 @@
|
|||||||
# UX/UI: журнал реализации
|
# UX/UI: журнал реализации
|
||||||
|
|
||||||
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
|
Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт.
|
||||||
|
|
||||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
||||||
|
|
||||||
@@ -38,6 +38,22 @@
|
|||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка12.3.83 в границах журнала. Внешняя12.5: блоки1–236 завершены в записанных scopes, этап открыт. Runtime backend ux-backup234/frontend ux-routing218; backend25/UI52 и дополнительные SQL/UDP222 прошли. |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка12.3.83 в границах журнала. Внешняя12.5: блоки1–236 завершены в записанных scopes, этап открыт. Runtime backend ux-backup234/frontend ux-routing218; backend25/UI52 и дополнительные SQL/UDP222 прошли. |
|
||||||
|
|
||||||
|
## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14)
|
||||||
|
|
||||||
|
Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR.
|
||||||
|
|
||||||
|
Собственный объект записан и прочитан SDK, затем только его строка добавлена в каталог. Через обычный UI скачан файл с совпавшими байтами/SHA256; restore вернул HTTP200 с нужными backupId/serverId. Direct RouterOS на194.226.121.150 подтвердил ровно один bridge, comment, отсутствие bridge ports/IP addresses и неизменность исходных bridges. Через UI удалена только собственная строка/объект:HTTP204, строка исчезла; pageerrors0. Восстановление на втором CHR и полный config restore этим блоком не проверялись.
|
||||||
|
|
||||||
|
Cleanup удалил свой bridge и загруженный .rsc, все версии/delete markers только точного ключа UUID prefix, сверил исходные10 SQL-строк и оба settings rows целиком. Первая попытка остановилась уже после удаления объектов: проверочный SQL-column guard не допускал цифру3 в s3_*; исправлен allowlist имён, повторная очистка прошла. Final-attempt removed0 означает, что версии удалены первой попыткой; конечный listing0 подтверждён. Общее число удалённых версий первой попытки не записано и не выдумывается. Bucket settings, чужие объекты и пароли не менялись. Приватный SQL-снимок и временные ключевые файлы workstation/Debian удалены; production-код и образы ux-backups237 не менялись.
|
||||||
|
|
||||||
|
Acceptance helpers s3-ui-limited-restore.mjs/.cjs проходят node --check. [UI-протокол](ux-ui/acceptance/s3-ui239-browser.json) и [native/cleanup](ux-ui/acceptance/s3-ui239-native.json) фиксируют точные границы. Общая регрессия не повторялась: новых runtime изменений нет.
|
||||||
|
|
||||||
|
| Референс | Схема принятого сценария |
|
||||||
|
| --- | --- |
|
||||||
|
| [ReUI Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Собственный объект S3 → строка истории → download → подтверждение restore → native bridge → delete → cleanup и исходные настройки |
|
||||||
|
|
||||||
|
Следующий240 — retention в изолированном scope без удаления существующего каталога. Полная конфигурация, массовые экспорты на реальном CHR, миграция buckets, multi-instance, другие ОС/WAN/jobs остаются отдельной приёмкой.
|
||||||
|
|
||||||
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
||||||
|
|
||||||
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
# Оставшаяся внешняя приёмка — 2026-10-14
|
# Оставшаяся внешняя приёмка — 2026-10-14
|
||||||
|
|
||||||
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
|
Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт.
|
||||||
|
|
||||||
Это актуальная матрица после 12.5.238. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
Это актуальная матрица после 12.5.239. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||||
|
|
||||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-backups237`, frontend `ux-backups237`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-backups237`, frontend `ux-backups237`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||||
|
|
||||||
|
## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14)
|
||||||
|
|
||||||
|
Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR.
|
||||||
|
|
||||||
|
Собственный объект записан и прочитан SDK, затем только его строка добавлена в каталог. Через обычный UI скачан файл с совпавшими байтами/SHA256; restore вернул HTTP200 с нужными backupId/serverId. Direct RouterOS на194.226.121.150 подтвердил ровно один bridge, comment, отсутствие bridge ports/IP addresses и неизменность исходных bridges. Через UI удалена только собственная строка/объект:HTTP204, строка исчезла; pageerrors0. Восстановление на втором CHR и полный config restore этим блоком не проверялись.
|
||||||
|
|
||||||
|
Cleanup удалил свой bridge и загруженный .rsc, все версии/delete markers только точного ключа UUID prefix, сверил исходные10 SQL-строк и оба settings rows целиком. Первая попытка остановилась уже после удаления объектов: проверочный SQL-column guard не допускал цифру3 в s3_*; исправлен allowlist имён, повторная очистка прошла. Final-attempt removed0 означает, что версии удалены первой попыткой; конечный listing0 подтверждён. Общее число удалённых версий первой попытки не записано и не выдумывается. Bucket settings, чужие объекты и пароли не менялись. Приватный SQL-снимок и временные ключевые файлы workstation/Debian удалены; production-код и образы ux-backups237 не менялись.
|
||||||
|
|
||||||
|
Acceptance helpers s3-ui-limited-restore.mjs/.cjs проходят node --check. [UI-протокол](ux-ui/acceptance/s3-ui239-browser.json) и [native/cleanup](ux-ui/acceptance/s3-ui239-native.json) фиксируют точные границы. Общая регрессия не повторялась: новых runtime изменений нет.
|
||||||
|
|
||||||
|
| Референс | Схема принятого сценария |
|
||||||
|
| --- | --- |
|
||||||
|
| [ReUI Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Собственный объект S3 → строка истории → download → подтверждение restore → native bridge → delete → cleanup и исходные настройки |
|
||||||
|
|
||||||
|
Следующий240 — retention в изолированном scope без удаления существующего каталога. Полная конфигурация, массовые экспорты на реальном CHR, миграция buckets, multi-instance, другие ОС/WAN/jobs остаются отдельной приёмкой.
|
||||||
|
|
||||||
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
||||||
|
|
||||||
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
||||||
@@ -43,7 +59,7 @@ Production backend/frontend ux-backups237 собраны и развёрнуты
|
|||||||
| REST и достоверность данных | Ошибки OSPF/BFD/BGP/GRE/WAN215–221 явные; latest API smoke222 прошёл | Correlated236:384 samples/40 diagnostics без отказов; current policies/address/load исправны.828/304 retained REST sessions и vendor inactive-session fix — гипотеза; 238:763.844s/624samples/24diagnostics все200, API records изменились без отказа; причинность401/500 и version control остаются открытыми |
|
| REST и достоверность данных | Ошибки OSPF/BFD/BGP/GRE/WAN215–221 явные; latest API smoke222 прошёл | Correlated236:384 samples/40 diagnostics без отказов; current policies/address/load исправны.828/304 retained REST sessions и vendor inactive-session fix — гипотеза; 238:763.844s/624samples/24diagnostics все200, API records изменились без отказа; причинность401/500 и version control остаются открытыми |
|
||||||
| Клиентские ОС и внешняя сеть | CHR↔CHR/Linux, private IPv4/ULA и synthetic IPv6/IPFIX; Android full-tunnel/reconnect227 подтверждены пользователем | Windows/macOS/iOS VPN, расширенная Android приёмка, независимые WAN/multi-WAN и public IPv6; public IPv6 на текущем стенде отсутствует |
|
| Клиентские ОС и внешняя сеть | CHR↔CHR/Linux, private IPv4/ULA и synthetic IPv6/IPFIX; Android full-tunnel/reconnect227 подтверждены пользователем | Windows/macOS/iOS VPN, расширенная Android приёмка, независимые WAN/multi-WAN и public IPv6; public IPv6 на текущем стенде отсутствует |
|
||||||
| Масштаб и длительная устойчивость | Контролируемые lock/dedupe/loss/recovery и короткие native проверки | Несколько backend/worker/writers, длительный rekey/REST/traffic, physical disconnect и maximum throughput |
|
| Масштаб и длительная устойчивость | Контролируемые lock/dedupe/loss/recovery и короткие native проверки | Несколько backend/worker/writers, длительный rekey/REST/traffic, physical disconnect и maximum throughput |
|
||||||
| Внешние хранилища и jobs | Локальные API/errors/RBAC/owned SQL; реальные Portal1.3.1/Telegram195–201 и Selectel S3/client/API/ownSQL228–229 | Large native exports проверены HTTP fixture; Missing local files UX237 принят; S3 restore через UI, миграция между buckets/multi-instance и retention; provider/container/Gitea/MaxMind jobs с соответствующими endpoints/аккаунтами; private/paid credentials не выдумывать |
|
| Внешние хранилища и jobs | Локальные API/errors/RBAC/owned SQL; реальные Portal1.3.1/Telegram195–201 и Selectel S3/client/API/ownSQL228–229 | Large native exports проверены HTTP fixture; Missing local files UX237 принят; Bridge-only S3 restore через UI239 принят; миграция между buckets/multi-instance и retention; provider/container/Gitea/MaxMind jobs с соответствующими endpoints/аккаунтами; private/paid credentials не выдумывать |
|
||||||
| Расширенные сетевые конфигурации | GRE/WG/VXLAN/IPsec/OSPF/BFD/BGP сценарии и точные границы записаны | from-pool/EUI-64/multiple-address import/export, outer IPv6, произвольная PKI/OID/policy/path-length и длительные переходы; BFD-only gate гарантией не стал |
|
| Расширенные сетевые конфигурации | GRE/WG/VXLAN/IPsec/OSPF/BFD/BGP сценарии и точные границы записаны | from-pool/EUI-64/multiple-address import/export, outer IPv6, произвольная PKI/OID/policy/path-length и длительные переходы; BFD-only gate гарантией не стал |
|
||||||
| Общий UX, доступность и производительность | Полная UI192 исторически; latest backend25/UI52, scoped contrast/live218 и keyboard206 | Свежая UI226 принята в записанных границах; профильный performance и ручная accessibility/screen-reader приёмка; scoped axe не полный WCAG |
|
| Общий UX, доступность и производительность | Полная UI192 исторически; latest backend25/UI52, scoped contrast/live218 и keyboard206 | Свежая UI226 принята в записанных границах; профильный performance и ручная accessibility/screen-reader приёмка; scoped axe не полный WCAG |
|
||||||
| Границы выпуска | План/доказательства/датированные блоки сохранены; коммиты локальные | Определить требуемые конфигурации и принять результат в этих границах; невыполненные варианты остаются явными ограничениями |
|
| Границы выпуска | План/доказательства/датированные блоки сохранены; коммиты локальные | Определить требуемые конфигурации и принять результат в этих границах; невыполненные варианты остаются явными ограничениями |
|
||||||
@@ -70,7 +86,7 @@ Portal offline JWT действует доexp после logout/role change; э
|
|||||||
|
|
||||||
## Следующая последовательность — 2026-10-14
|
## Следующая последовательность — 2026-10-14
|
||||||
|
|
||||||
1. **239 — S3 restore через UI:** только собственный bridge без портов/адресов; отдельный prefix/catalog row, сверка результата и очистки. Затем — retention только собственных объектов.
|
1. **240 — retention:** изолированные own SQL/S3 scope, сохранение latest copies по каждому серверу, partial cleanup и точная очистка версий. Исходный каталог приложения не использовать для pruning.
|
||||||
2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания.
|
2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания.
|
||||||
3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций.
|
3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций.
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -1,6 +1,6 @@
|
|||||||
# Этап 12: итоговая приёмка UX/UI
|
# Этап 12: итоговая приёмка UX/UI
|
||||||
|
|
||||||
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
|
Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт.
|
||||||
|
|
||||||
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
||||||
|
|
||||||
@@ -4152,6 +4152,22 @@ Real Portal/browser API, provider local verified: создан один job ро
|
|||||||
Runtime backend ux-backup234/frontend ux-routing218 healthy; новое application image для volume-only fix не требовалось. Пароли CHR не менялись, S3 mutations0 в235, configs body/JWT не опубликованы, private local manifest удалён. Исходные10 missing local files не восстановлены и records не удалены. Force-recreate — не host reboot/power loss/fsync/DR приёмка. Далее236 — UX недоступных local files, затем version retention границы и оставшиеся7 группы матрицы; multi-instance guard не принимается. Этап12.5 открыт.
|
Runtime backend ux-backup234/frontend ux-routing218 healthy; новое application image для volume-only fix не требовалось. Пароли CHR не менялись, S3 mutations0 в235, configs body/JWT не опубликованы, private local manifest удалён. Исходные10 missing local files не восстановлены и records не удалены. Force-recreate — не host reboot/power loss/fsync/DR приёмка. Далее236 — UX недоступных local files, затем version retention границы и оставшиеся7 группы матрицы; multi-instance guard не принимается. Этап12.5 открыт.
|
||||||
|
|
||||||
|
|
||||||
|
## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14)
|
||||||
|
|
||||||
|
Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR.
|
||||||
|
|
||||||
|
Собственный объект записан и прочитан SDK, затем только его строка добавлена в каталог. Через обычный UI скачан файл с совпавшими байтами/SHA256; restore вернул HTTP200 с нужными backupId/serverId. Direct RouterOS на194.226.121.150 подтвердил ровно один bridge, comment, отсутствие bridge ports/IP addresses и неизменность исходных bridges. Через UI удалена только собственная строка/объект:HTTP204, строка исчезла; pageerrors0. Восстановление на втором CHR и полный config restore этим блоком не проверялись.
|
||||||
|
|
||||||
|
Cleanup удалил свой bridge и загруженный .rsc, все версии/delete markers только точного ключа UUID prefix, сверил исходные10 SQL-строк и оба settings rows целиком. Первая попытка остановилась уже после удаления объектов: проверочный SQL-column guard не допускал цифру3 в s3_*; исправлен allowlist имён, повторная очистка прошла. Final-attempt removed0 означает, что версии удалены первой попыткой; конечный listing0 подтверждён. Общее число удалённых версий первой попытки не записано и не выдумывается. Bucket settings, чужие объекты и пароли не менялись. Приватный SQL-снимок и временные ключевые файлы workstation/Debian удалены; production-код и образы ux-backups237 не менялись.
|
||||||
|
|
||||||
|
Acceptance helpers s3-ui-limited-restore.mjs/.cjs проходят node --check. [UI-протокол](ux-ui/acceptance/s3-ui239-browser.json) и [native/cleanup](ux-ui/acceptance/s3-ui239-native.json) фиксируют точные границы. Общая регрессия не повторялась: новых runtime изменений нет.
|
||||||
|
|
||||||
|
| Референс | Схема принятого сценария |
|
||||||
|
| --- | --- |
|
||||||
|
| [ReUI Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Собственный объект S3 → строка истории → download → подтверждение restore → native bridge → delete → cleanup и исходные настройки |
|
||||||
|
|
||||||
|
Следующий240 — retention в изолированном scope без удаления существующего каталога. Полная конфигурация, массовые экспорты на реальном CHR, миграция buckets, multi-instance, другие ОС/WAN/jobs остаются отдельной приёмкой.
|
||||||
|
|
||||||
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
||||||
|
|
||||||
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Тестовый сервер UX/UI — 2026-10-10
|
# Тестовый сервер UX/UI — 2026-10-10
|
||||||
|
|
||||||
Актуальное продолжение 2026-10-14:238 — REST наблюдался763.844s:624 paired samples +24 diagnostics, все200. На обоих CHR изменились API session records; retained REST rows844→846/320→322. Это не доказывает причину прежних401/500 или точный механизм cached-login renewal. Runtime backend/frontend ux-backups237; missing-file UX237 принят. Следующий239 — S3 restore через live UI в пределах собственного bridge и retention. Этап12.5 открыт.
|
Актуальное продолжение 2026-10-14:239 — Selectel S3 restore через реальный Portal/UI принят для собственного95-byte скрипта bridge-only:download SHA256 совпал,restore200/receipt и native bridge без ports/IP подтверждены,delete204. Bridge/file/catalog/S3versions очищены; исходные10 строк, storage/schedule сверены целиком и восстановлены; приватные ключи/SQL-снимок удалены. Runtime backend/frontend ux-backups237. Следующий240 — retention в изолированном собственном S3 scope. Этап12.5 открыт.
|
||||||
|
|
||||||
| Параметр | Проверенное состояние |
|
| Параметр | Проверенное состояние |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -34,6 +34,22 @@ OSPF84: real GRE Full adjacency/private routes/Ping3/3 и4 live PC/320px URL/bro
|
|||||||
|
|
||||||
BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json).
|
BFD85: Up/Full/own route/Ping,4 live UI и loss/withdrawal/recovery приняты; exact own cleanup8 коллекций×2 passed. Withdrawal5,245ms не подтверждает sub-second BFD timing; этот вопрос открыт. Runtime routing83 без новой сборки. [Отчёт](ux-ui/acceptance/ospf-bfd-loss-2026-10-09.json).
|
||||||
|
|
||||||
|
## 12.5.239 — восстановление из Selectel S3 через реальный интерфейс (2026-10-14)
|
||||||
|
|
||||||
|
Пользователь явно разрешил передачу тестовых S3 access/secret keys на Debian после отказа automatic approval review. Ключи находились только в приватных ignored/0600 файлах и временных настройках приложения. Перед изменением сохранены raw SQL storage/schedule и10 исходных backup rows приватно. Расписание временно остановлено, virtual-hosted S3 сохранён и проверен через live UI с реальным Portal1.3.1/JWT. Использован новый UUID prefix и ровно один собственный95-byte .rsc: добавление bridge mm-ui239-* с comment=mm-ui239 без портов/адресов. Это специально ограниченный скрипт, не native full export и не полное восстановление CHR.
|
||||||
|
|
||||||
|
Собственный объект записан и прочитан SDK, затем только его строка добавлена в каталог. Через обычный UI скачан файл с совпавшими байтами/SHA256; restore вернул HTTP200 с нужными backupId/serverId. Direct RouterOS на194.226.121.150 подтвердил ровно один bridge, comment, отсутствие bridge ports/IP addresses и неизменность исходных bridges. Через UI удалена только собственная строка/объект:HTTP204, строка исчезла; pageerrors0. Восстановление на втором CHR и полный config restore этим блоком не проверялись.
|
||||||
|
|
||||||
|
Cleanup удалил свой bridge и загруженный .rsc, все версии/delete markers только точного ключа UUID prefix, сверил исходные10 SQL-строк и оба settings rows целиком. Первая попытка остановилась уже после удаления объектов: проверочный SQL-column guard не допускал цифру3 в s3_*; исправлен allowlist имён, повторная очистка прошла. Final-attempt removed0 означает, что версии удалены первой попыткой; конечный listing0 подтверждён. Общее число удалённых версий первой попытки не записано и не выдумывается. Bucket settings, чужие объекты и пароли не менялись. Приватный SQL-снимок и временные ключевые файлы workstation/Debian удалены; production-код и образы ux-backups237 не менялись.
|
||||||
|
|
||||||
|
Acceptance helpers s3-ui-limited-restore.mjs/.cjs проходят node --check. [UI-протокол](ux-ui/acceptance/s3-ui239-browser.json) и [native/cleanup](ux-ui/acceptance/s3-ui239-native.json) фиксируют точные границы. Общая регрессия не повторялась: новых runtime изменений нет.
|
||||||
|
|
||||||
|
| Референс | Схема принятого сценария |
|
||||||
|
| --- | --- |
|
||||||
|
| [ReUI Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Собственный объект S3 → строка истории → download → подтверждение restore → native bridge → delete → cleanup и исходные настройки |
|
||||||
|
|
||||||
|
Следующий240 — retention в изолированном scope без удаления существующего каталога. Полная конфигурация, массовые экспорты на реальном CHR, миграция buckets, multi-instance, другие ОС/WAN/jobs остаются отдельной приёмкой.
|
||||||
|
|
||||||
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
## 12.5.238 — REST за пределами десятиминутного окна (2026-10-14)
|
||||||
|
|
||||||
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
Read-only workstation-наблюдение длилось763.844s (07:01:21.663Z–07:14:05.507Z):26 циклов с целевым периодом30s, шесть endpoints × два CHR × compiled MikrotikClient/fetch Connection:close. 624 samples и24 diagnostics, все HTTP200; повторные запросы при отказе не выполнялись, потому что отказов не было. Учётные данные передавались из приватного process env, временный env-файл удалён; Basic/JWT/пароли в публичный отчёт не включены. RouterOS configurationWrites0/passwordChanges0.
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-10-14",
|
||||||
|
"block": "12.5.239",
|
||||||
|
"realPortalLogin": true,
|
||||||
|
"noMocks": true,
|
||||||
|
"passed": true,
|
||||||
|
"downloadBytes": 95,
|
||||||
|
"downloadSha256": "61a84020603fd0aca3818a2cfad630d7b80b1a2506c5211ab4d04874e3459697",
|
||||||
|
"restoreStatus": 200,
|
||||||
|
"restoreReceiptMatched": true,
|
||||||
|
"receipts": [
|
||||||
|
{
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/api/backups/mm-ui239-6bebaf17-569c-4fe3-9860-dc518ad03364/restore",
|
||||||
|
"status": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pageErrors": [],
|
||||||
|
"fullConfigurationApplied": false,
|
||||||
|
"deleteStatus": 204,
|
||||||
|
"ownRowRemovedThroughUi": true,
|
||||||
|
"deleteReceipts": [
|
||||||
|
{
|
||||||
|
"method": "DELETE",
|
||||||
|
"path": "/api/backups/mm-ui239-6bebaf17-569c-4fe3-9860-dc518ad03364",
|
||||||
|
"status": 204
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-10-14",
|
||||||
|
"block": "12.5.239",
|
||||||
|
"host": "194.226.121.150",
|
||||||
|
"prefix": "mmapp-acceptance/6bebaf17-569c-4fe3-9860-dc518ad03364",
|
||||||
|
"limitedScriptBytes": 95,
|
||||||
|
"sha256": "61a84020603fd0aca3818a2cfad630d7b80b1a2506c5211ab4d04874e3459697",
|
||||||
|
"nativeBridgeVerified": true,
|
||||||
|
"bridgeAndUploadedFileRemoved": true,
|
||||||
|
"originalBridgesUnchanged": true,
|
||||||
|
"originalCatalogAllFieldsMatched": true,
|
||||||
|
"originalCatalogRows": 10,
|
||||||
|
"originalSettingsAllFieldsMatched": true,
|
||||||
|
"s3VersionsAndMarkersRemaining": 0,
|
||||||
|
"passwordChanges": 0,
|
||||||
|
"fullConfigurationApplied": false,
|
||||||
|
"s3VersionsAndMarkersRemovedInFinalAttempt": 0,
|
||||||
|
"cleanupAttempts": 2,
|
||||||
|
"firstCleanupFailure": "Acceptance SQL column guard rejected s3_* names after own object cleanup",
|
||||||
|
"privateKeyFileRemoved": true,
|
||||||
|
"privateSqlSnapshotRemoved": true
|
||||||
|
}
|
||||||
Reference in new issue
Block a user