- Pipeline cancel/approve/reject corrigido com busca bidirecional - Secrets injetados no executor via cleanEnv - Versionamento automático ao atualizar agentes - writeJsonAsync com log de erro - Removido asyncHandler.js (código morto) - Restaurado permissionMode padrão bypassPermissions - Ícones dos cards alinhados à direita com wrapper - Botão Editar convertido para ícone nos cards - Webhook test agora dispara execução real do agente/pipeline - Corrigido App.navigateTo no teste de webhook
243 lines
5.3 KiB
JavaScript
243 lines
5.3 KiB
JavaScript
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from 'fs';
|
|
import { writeFile, rename } from 'fs/promises';
|
|
import { dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const DATA_DIR = `${__dirname}/../../data`;
|
|
|
|
const DEFAULT_SETTINGS = {
|
|
defaultModel: 'claude-sonnet-4-6',
|
|
defaultWorkdir: '',
|
|
maxConcurrent: 5,
|
|
};
|
|
|
|
const DEBOUNCE_MS = 300;
|
|
const allStores = [];
|
|
|
|
function ensureDir() {
|
|
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
function readJson(path, fallback) {
|
|
try {
|
|
if (!existsSync(path)) return fallback;
|
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function writeJson(path, data) {
|
|
ensureDir();
|
|
const tmpPath = path + '.tmp';
|
|
writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf8');
|
|
renameSync(tmpPath, path);
|
|
}
|
|
|
|
async function writeJsonAsync(path, data) {
|
|
ensureDir();
|
|
const tmpPath = path + '.tmp';
|
|
await writeFile(tmpPath, JSON.stringify(data, null, 2), 'utf8');
|
|
await rename(tmpPath, path);
|
|
}
|
|
|
|
function clone(v) {
|
|
return structuredClone(v);
|
|
}
|
|
|
|
function createStore(filePath) {
|
|
let mem = null;
|
|
let dirty = false;
|
|
let timer = null;
|
|
let maxSize = Infinity;
|
|
|
|
function boot() {
|
|
if (mem !== null) return;
|
|
ensureDir();
|
|
mem = readJson(filePath, []);
|
|
}
|
|
|
|
function touch() {
|
|
dirty = true;
|
|
if (timer) return;
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
if (dirty) {
|
|
writeJsonAsync(filePath, mem).catch((e) => console.error(`[db] Erro ao salvar ${filePath}:`, e.message));
|
|
dirty = false;
|
|
}
|
|
}, DEBOUNCE_MS);
|
|
}
|
|
|
|
const store = {
|
|
getAll() {
|
|
boot();
|
|
return clone(mem);
|
|
},
|
|
|
|
getById(id) {
|
|
boot();
|
|
const item = mem.find((i) => i.id === id);
|
|
return item ? clone(item) : null;
|
|
},
|
|
|
|
findById(id) {
|
|
return store.getById(id);
|
|
},
|
|
|
|
count() {
|
|
boot();
|
|
return mem.length;
|
|
},
|
|
|
|
filter(predicate) {
|
|
boot();
|
|
return mem.filter(predicate).map((item) => clone(item));
|
|
},
|
|
|
|
create(data) {
|
|
boot();
|
|
const item = {
|
|
id: uuidv4(),
|
|
...data,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
mem.push(item);
|
|
if (maxSize !== Infinity && mem.length > maxSize) {
|
|
mem.splice(0, mem.length - maxSize);
|
|
}
|
|
touch();
|
|
return clone(item);
|
|
},
|
|
|
|
update(id, data) {
|
|
boot();
|
|
const i = mem.findIndex((x) => x.id === id);
|
|
if (i === -1) return null;
|
|
mem[i] = { ...mem[i], ...data, id, updated_at: new Date().toISOString() };
|
|
touch();
|
|
return clone(mem[i]);
|
|
},
|
|
|
|
delete(id) {
|
|
boot();
|
|
const i = mem.findIndex((x) => x.id === id);
|
|
if (i === -1) return false;
|
|
mem.splice(i, 1);
|
|
touch();
|
|
return true;
|
|
},
|
|
|
|
save(items) {
|
|
if (!Array.isArray(items)) return;
|
|
mem = items;
|
|
touch();
|
|
},
|
|
|
|
flush() {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
if (mem !== null && dirty) {
|
|
writeJson(filePath, mem);
|
|
dirty = false;
|
|
}
|
|
},
|
|
|
|
setMaxSize(n) {
|
|
maxSize = n;
|
|
},
|
|
};
|
|
|
|
allStores.push(store);
|
|
return store;
|
|
}
|
|
|
|
function createSettingsStore(filePath) {
|
|
let mem = null;
|
|
let dirty = false;
|
|
let timer = null;
|
|
|
|
function boot() {
|
|
if (mem !== null) return;
|
|
ensureDir();
|
|
mem = { ...DEFAULT_SETTINGS, ...readJson(filePath, DEFAULT_SETTINGS) };
|
|
}
|
|
|
|
function touch() {
|
|
dirty = true;
|
|
if (timer) return;
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
if (dirty) {
|
|
writeJson(filePath, mem);
|
|
dirty = false;
|
|
}
|
|
}, DEBOUNCE_MS);
|
|
}
|
|
|
|
const store = {
|
|
get() {
|
|
boot();
|
|
return clone(mem);
|
|
},
|
|
|
|
save(data) {
|
|
boot();
|
|
mem = { ...mem, ...data };
|
|
touch();
|
|
return clone(mem);
|
|
},
|
|
|
|
flush() {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
if (mem !== null && dirty) {
|
|
writeJson(filePath, mem);
|
|
dirty = false;
|
|
}
|
|
},
|
|
};
|
|
|
|
allStores.push(store);
|
|
return store;
|
|
}
|
|
|
|
const locks = new Map();
|
|
|
|
export async function withLock(key, fn) {
|
|
while (locks.has(key)) await locks.get(key);
|
|
let resolve;
|
|
const promise = new Promise((r) => { resolve = r; });
|
|
locks.set(key, promise);
|
|
try {
|
|
return await fn();
|
|
} finally {
|
|
locks.delete(key);
|
|
resolve();
|
|
}
|
|
}
|
|
|
|
export function flushAllStores() {
|
|
for (const s of allStores) s.flush();
|
|
}
|
|
|
|
export const agentsStore = createStore(`${DATA_DIR}/agents.json`);
|
|
export const tasksStore = createStore(`${DATA_DIR}/tasks.json`);
|
|
export const pipelinesStore = createStore(`${DATA_DIR}/pipelines.json`);
|
|
export const schedulesStore = createStore(`${DATA_DIR}/schedules.json`);
|
|
export const executionsStore = createStore(`${DATA_DIR}/executions.json`);
|
|
executionsStore.setMaxSize(5000);
|
|
export const webhooksStore = createStore(`${DATA_DIR}/webhooks.json`);
|
|
export const settingsStore = createSettingsStore(`${DATA_DIR}/settings.json`);
|
|
export const secretsStore = createStore(`${DATA_DIR}/secrets.json`);
|
|
export const notificationsStore = createStore(`${DATA_DIR}/notifications.json`);
|
|
notificationsStore.setMaxSize(200);
|
|
export const agentVersionsStore = createStore(`${DATA_DIR}/agent_versions.json`);
|