From d84803efc940726be70d6fc4109ce4b67e8ad3f4 Mon Sep 17 00:00:00 2001 From: Tiago Santos Date: Fri, 28 Nov 2025 07:49:17 -0300 Subject: [PATCH] Initial commit --- .env.example | 6 + Dockerfile | 15 + api/Dockerfile | 14 + api/package.json | 17 + api/src/server.js | 412 ++++++++++++++ assets/Logo tema Claro.png | Bin 0 -> 39096 bytes assets/Logo tema Escuro.png | Bin 0 -> 40615 bytes assets/app.js | 1021 +++++++++++++++++++++++++++++++++++ assets/style.css | 159 ++++++ docker-compose.yml | 55 ++ index.html | 347 ++++++++++++ nginx.conf | 28 + 12 files changed, 2074 insertions(+) create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 api/Dockerfile create mode 100644 api/package.json create mode 100644 api/src/server.js create mode 100644 assets/Logo tema Claro.png create mode 100644 assets/Logo tema Escuro.png create mode 100644 assets/app.js create mode 100644 assets/style.css create mode 100644 docker-compose.yml create mode 100644 index.html create mode 100644 nginx.conf diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c60337d --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +POSTGRES_DB=telseg +POSTGRES_USER=telseg +POSTGRES_PASSWORD=troque_esta_senha +JWT_SECRET=troque_este_segredo +ADMIN_USER=admin +ADMIN_PASSWORD=admin123 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b9c4897 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM nginx:alpine + +# Copia configuração do Nginx (cache leve para assets, SPA fallback) +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copia arquivos estáticos do dashboard +COPY index.html /usr/share/nginx/html/index.html +COPY assets /usr/share/nginx/html/assets + +# Porta padrão já exposta na imagem base (80) + +# Dica: para rodar +# docker build -t dashboard_telseg . +# docker run --name dashboard_telseg -p 8080:80 -d dashboard_telseg + diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..341740e --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,14 @@ +FROM node:20-alpine +WORKDIR /app + +# Instala dependências +COPY package.json package-lock.json* ./ +RUN npm install --production || npm install --production --no-audit --no-fund + +# Copia código +COPY src ./src + +ENV PORT=3000 +EXPOSE 3000 +CMD ["npm", "start"] + diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..01140da --- /dev/null +++ b/api/package.json @@ -0,0 +1,17 @@ +{ + "name": "telseg-dashboard-api", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "src/server.js", + "scripts": { + "start": "node src/server.js" + }, + "dependencies": { + "cors": "^2.8.5", + "express": "^4.19.2", + "pg": "^8.11.3", + "bcryptjs": "^2.4.3", + "jsonwebtoken": "^9.0.2" + } +} diff --git a/api/src/server.js b/api/src/server.js new file mode 100644 index 0000000..013b5f3 --- /dev/null +++ b/api/src/server.js @@ -0,0 +1,412 @@ +import express from 'express'; +import cors from 'cors'; +import pkg from 'pg'; +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; + +const { Pool } = pkg; + +const PORT = process.env.PORT || 3000; +const DATABASE_URL = process.env.DATABASE_URL || `postgres://${process.env.POSTGRES_USER || 'telseg'}:${process.env.POSTGRES_PASSWORD || 'telseg'}@${process.env.DB_HOST || 'db'}:${process.env.DB_PORT || 5432}/${process.env.POSTGRES_DB || 'telseg'}`; +const JWT_SECRET = process.env.JWT_SECRET || 'devsecret'; +const ADMIN_USER = process.env.ADMIN_USER || 'admin'; +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123'; + +const pool = new Pool({ connectionString: DATABASE_URL }); + +async function initDb() { + const client = await pool.connect(); + try { + await client.query('BEGIN'); + await client.query(`CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + role TEXT NOT NULL CHECK (role IN ('master','user')), + created_at TIMESTAMPTZ DEFAULT now() + );`); + await client.query(`CREATE TABLE IF NOT EXISTS permissions ( + user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + units_read BOOLEAN DEFAULT false, + units_write BOOLEAN DEFAULT false, + creds_read BOOLEAN DEFAULT false, + creds_write BOOLEAN DEFAULT false, + notes_read BOOLEAN DEFAULT false, + notes_write BOOLEAN DEFAULT false + );`); + await client.query(`CREATE TABLE IF NOT EXISTS units ( + id TEXT PRIMARY KEY, + nome TEXT NOT NULL, + data_criacao DATE, + ocs TEXT[], + link TEXT, + ip TEXT, + login TEXT, + senha TEXT, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() + );`); + await client.query(`CREATE TABLE IF NOT EXISTS creds ( + id TEXT PRIMARY KEY, + nome TEXT NOT NULL, + usuario TEXT, + url TEXT, + notas TEXT, + password_enc JSONB NOT NULL, + updated_at BIGINT, + created_at TIMESTAMPTZ DEFAULT now() + );`); + await client.query(`CREATE TABLE IF NOT EXISTS vault ( + id INTEGER PRIMARY KEY, + salt TEXT NOT NULL, + verification JSONB NOT NULL, + iterations INTEGER NOT NULL, + v INTEGER DEFAULT 1 + );`); + await client.query(`CREATE TABLE IF NOT EXISTS notes ( + id TEXT PRIMARY KEY, + title TEXT, + body TEXT, + data JSONB, + updated_at BIGINT, + created_at TIMESTAMPTZ DEFAULT now() + );`); + await client.query(`ALTER TABLE notes ADD COLUMN IF NOT EXISTS data JSONB;`); + await client.query(`ALTER TABLE units ADD COLUMN IF NOT EXISTS login TEXT;`); + await client.query(`ALTER TABLE units ADD COLUMN IF NOT EXISTS senha TEXT;`); + await client.query('COMMIT'); + // Seed admin user if none exists + const { rows: cnt } = await pool.query('SELECT COUNT(*)::int AS c FROM users'); + if (cnt[0].c === 0) { + const id = genId(); + const hash = await bcrypt.hash(ADMIN_PASSWORD, 10); + await pool.query('INSERT INTO users (id, username, password_hash, role) VALUES ($1,$2,$3,$4)', [id, ADMIN_USER, hash, 'master']); + await pool.query('INSERT INTO permissions (user_id, units_read, units_write, creds_read, creds_write, notes_read, notes_write) VALUES ($1,true,true,true,true,true,true)', [id]); + console.log('Admin user created:', ADMIN_USER); + } + } catch (e) { + await client.query('ROLLBACK'); + console.error('DB init error:', e); + throw e; + } finally { + client.release(); + } +} + +function genId() { + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`; +} + +function normalizePerms(p = {}) { + return { + units_read: !!p.units_read, + units_write: !!p.units_write, + creds_read: !!p.creds_read, + creds_write: !!p.creds_write, + notes_read: !!p.notes_read, + notes_write: !!p.notes_write, + }; +} + +async function loadUserById(id) { + const { rows } = await pool.query('SELECT id, username, role FROM users WHERE id=$1', [id]); + if (!rows.length) return null; + const u = rows[0]; + const { rows: pr } = await pool.query('SELECT * FROM permissions WHERE user_id=$1', [id]); + return { id: u.id, username: u.username, role: u.role, permissions: normalizePerms(pr[0]) }; +} + +function signToken(user) { + return jwt.sign({ sub: user.id, role: user.role }, JWT_SECRET, { expiresIn: '7d' }); +} + +function mapUnit(row) { + return { + id: row.id, + nome: row.nome, + dataCriacao: row.data_criacao ? row.data_criacao.toISOString().slice(0,10) : null, + ocs: row.ocs || [], + link: row.link || '', + ip: row.ip || '', + login: row.login || '', + senha: row.senha || '' + }; +} + +function mapCred(row) { + return { + id: row.id, + nome: row.nome, + usuario: row.usuario || '', + url: row.url || '', + notas: row.notas || '', + passwordEnc: row.password_enc, + updatedAt: Number(row.updated_at || 0) + }; +} + +const app = express(); +app.use(cors()); +app.use(express.json({ limit: '1mb' })); + +app.get('/api/health', (_req, res) => { + res.json({ ok: true }); +}); + +// Auth helpers +async function authRequired(req, res, next) { + const auth = req.headers.authorization || ''; + const token = auth.startsWith('Bearer ') ? auth.slice(7) : null; + if (!token) return res.status(401).json({ error: 'unauthorized' }); + try { + const payload = jwt.verify(token, JWT_SECRET); + const user = await loadUserById(payload.sub); + if (!user) return res.status(401).json({ error: 'unauthorized' }); + req.user = user; next(); + } catch { + return res.status(401).json({ error: 'unauthorized' }); + } +} +function requireMaster(req, res, next) { + if (req.user?.role === 'master') return next(); + return res.status(403).json({ error: 'forbidden' }); +} +function requirePermFlag(flag) { + return (req, res, next) => { + if (req.user?.role === 'master') return next(); + if (req.user?.permissions?.[flag]) return next(); + return res.status(403).json({ error: 'forbidden' }); + }; +} + +// Auth endpoints +app.post('/api/auth/login', async (req, res) => { + const { username, password } = req.body || {}; + if (!username || !password) return res.status(400).json({ error: 'missing' }); + const { rows } = await pool.query('SELECT * FROM users WHERE username=$1', [username]); + if (!rows.length) return res.status(401).json({ error: 'invalid' }); + const u = rows[0]; + const ok = await bcrypt.compare(password, u.password_hash); + if (!ok) return res.status(401).json({ error: 'invalid' }); + const token = signToken(u); + const user = await loadUserById(u.id); + res.json({ token, user }); +}); +app.get('/api/auth/me', authRequired, (req, res) => { + res.json({ user: req.user }); +}); + +// Users management (master only) +app.get('/api/users', authRequired, requireMaster, async (_req, res) => { + const { rows } = await pool.query('SELECT id, username, role, created_at FROM users ORDER BY created_at DESC'); + const list = []; + for (const r of rows) { + const { rows: pr } = await pool.query('SELECT * FROM permissions WHERE user_id=$1', [r.id]); + list.push({ id: r.id, username: r.username, role: r.role, permissions: normalizePerms(pr[0] || {}) }); + } + res.json(list); +}); +app.post('/api/users', authRequired, requireMaster, async (req, res) => { + const { username, password, role, permissions } = req.body || {}; + if (!username || !password) return res.status(400).json({ error: 'missing' }); + const id = genId(); + const hash = await bcrypt.hash(password, 10); + await pool.query('INSERT INTO users (id, username, password_hash, role) VALUES ($1,$2,$3,$4)', [id, username, hash, role === 'master' ? 'master' : 'user']); + const p = normalizePerms(permissions || {}); + await pool.query('INSERT INTO permissions (user_id, units_read, units_write, creds_read, creds_write, notes_read, notes_write) VALUES ($1,$2,$3,$4,$5,$6,$7)', [id, p.units_read, p.units_write, p.creds_read, p.creds_write, p.notes_read, p.notes_write]); + res.status(201).json({ ok: true, id }); +}); +app.put('/api/users/:id', authRequired, requireMaster, async (req, res) => { + const id = req.params.id; const { password, role } = req.body || {}; + if (password) { + const hash = await bcrypt.hash(password, 10); + await pool.query('UPDATE users SET password_hash=$2 WHERE id=$1', [id, hash]); + } + if (role) await pool.query('UPDATE users SET role=$2 WHERE id=$1', [id, role === 'master' ? 'master' : 'user']); + res.json({ ok: true }); +}); +app.put('/api/users/:id/permissions', authRequired, requireMaster, async (req, res) => { + const id = req.params.id; const p = normalizePerms(req.body || {}); + await pool.query('INSERT INTO permissions (user_id, units_read, units_write, creds_read, creds_write, notes_read, notes_write) VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (user_id) DO UPDATE SET units_read=EXCLUDED.units_read, units_write=EXCLUDED.units_write, creds_read=EXCLUDED.creds_read, creds_write=EXCLUDED.creds_write, notes_read=EXCLUDED.notes_read, notes_write=EXCLUDED.notes_write', [id, p.units_read, p.units_write, p.creds_read, p.creds_write, p.notes_read, p.notes_write]); + res.json({ ok: true }); +}); +app.delete('/api/users/:id', authRequired, requireMaster, async (req, res) => { + await pool.query('DELETE FROM users WHERE id=$1', [req.params.id]); + res.json({ ok: true }); +}); + +// Units +app.get('/api/units', authRequired, requirePermFlag('units_read'), async (_req, res) => { + // Números primeiro, depois letras; em seguida ordem alfabética + const { rows } = await pool.query( + `SELECT * FROM units + ORDER BY (nome ~ '^[0-9]') DESC, nome ASC` + ); + res.json(rows.map(mapUnit)); +}); + +app.post('/api/units', authRequired, requirePermFlag('units_write'), async (req, res) => { + const u = req.body; + if (!u || !u.id || !u.nome) return res.status(400).json({ error: 'id e nome são obrigatórios' }); + await pool.query( + `INSERT INTO units (id, nome, data_criacao, ocs, link, ip, login, senha) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) + ON CONFLICT (id) DO NOTHING`, + [u.id, u.nome, u.dataCriacao || null, u.ocs || [], u.link || '', u.ip || '', u.login || '', u.senha || ''] + ); + res.status(201).json({ ok: true }); +}); + +app.put('/api/units/:id', authRequired, requirePermFlag('units_write'), async (req, res) => { + const id = req.params.id; + const u = req.body || {}; + await pool.query( + `UPDATE units SET nome=$2, data_criacao=$3, ocs=$4, link=$5, ip=$6, login=$7, senha=$8, updated_at=now() WHERE id=$1`, + [id, u.nome || '', u.dataCriacao || null, u.ocs || [], u.link || '', u.ip || '', u.login || '', u.senha || ''] + ); + res.json({ ok: true }); +}); + +app.delete('/api/units/:id', authRequired, requirePermFlag('units_write'), async (req, res) => { + await pool.query('DELETE FROM units WHERE id=$1', [req.params.id]); + res.json({ ok: true }); +}); + +// Creds +app.get('/api/creds', authRequired, requirePermFlag('creds_read'), async (_req, res) => { + const { rows } = await pool.query('SELECT * FROM creds ORDER BY updated_at DESC NULLS LAST, created_at DESC'); + res.json(rows.map(mapCred)); +}); + +app.post('/api/creds', authRequired, requirePermFlag('creds_write'), async (req, res) => { + const c = req.body; + if (!c || !c.id || !c.nome || !c.passwordEnc) return res.status(400).json({ error: 'id, nome e passwordEnc são obrigatórios' }); + await pool.query( + `INSERT INTO creds (id, nome, usuario, url, notas, password_enc, updated_at) + VALUES ($1,$2,$3,$4,$5,$6,$7) + ON CONFLICT (id) DO NOTHING`, + [c.id, c.nome, c.usuario || '', c.url || '', c.notas || '', c.passwordEnc, c.updatedAt || Date.now()] + ); + res.status(201).json({ ok: true }); +}); + +app.put('/api/creds/:id', authRequired, requirePermFlag('creds_write'), async (req, res) => { + const id = req.params.id; + const c = req.body || {}; + await pool.query( + `UPDATE creds SET nome=$2, usuario=$3, url=$4, notas=$5, password_enc=$6, updated_at=$7 WHERE id=$1`, + [id, c.nome || '', c.usuario || '', c.url || '', c.notas || '', c.passwordEnc, c.updatedAt || Date.now()] + ); + res.json({ ok: true }); +}); + +app.delete('/api/creds/:id', authRequired, requirePermFlag('creds_write'), async (req, res) => { + await pool.query('DELETE FROM creds WHERE id=$1', [req.params.id]); + res.json({ ok: true }); +}); + +// Notes +app.get('/api/notes', authRequired, requirePermFlag('notes_read'), async (_req, res) => { + const { rows } = await pool.query('SELECT * FROM notes ORDER BY updated_at DESC NULLS LAST, created_at DESC'); + res.json(rows.map(r => ({ id: r.id, title: r.title || '', body: r.body || '', data: r.data || null, updatedAt: Number(r.updated_at || 0) }))); +}); + +app.post('/api/notes', authRequired, requirePermFlag('notes_write'), async (req, res) => { + const n = req.body || {}; + if (!n.id) return res.status(400).json({ error: 'id é obrigatório' }); + await pool.query('INSERT INTO notes (id, title, body, data, updated_at) VALUES ($1,$2,$3,$4,$5) ON CONFLICT (id) DO NOTHING', [n.id, n.title || '', n.body || '', n.data || null, n.updatedAt || Date.now()]); + res.status(201).json({ ok: true }); +}); + +app.put('/api/notes/:id', authRequired, requirePermFlag('notes_write'), async (req, res) => { + const id = req.params.id; const n = req.body || {}; + await pool.query('UPDATE notes SET title=$2, body=$3, data=$4, updated_at=$5 WHERE id=$1', [id, n.title || '', n.body || '', n.data || null, n.updatedAt || Date.now()]); + res.json({ ok: true }); +}); + +app.delete('/api/notes/:id', authRequired, requirePermFlag('notes_write'), async (req, res) => { + await pool.query('DELETE FROM notes WHERE id=$1', [req.params.id]); + res.json({ ok: true }); +}); + +// Vault meta +app.get('/api/vault', authRequired, requirePermFlag('creds_read'), async (_req, res) => { + const { rows } = await pool.query('SELECT * FROM vault WHERE id=1'); + if (!rows.length) return res.json(null); + const v = rows[0]; + res.json({ salt: v.salt, verification: v.verification, iterations: v.iterations, v: v.v || 1 }); +}); + +app.put('/api/vault', authRequired, requireMaster, async (req, res) => { + const v = req.body || {}; + if (!v.salt || !v.verification) return res.status(400).json({ error: 'salt e verification são obrigatórios' }); + await pool.query( + `INSERT INTO vault (id, salt, verification, iterations, v) + VALUES (1, $1, $2, $3, $4) + ON CONFLICT (id) DO UPDATE SET salt=EXCLUDED.salt, verification=EXCLUDED.verification, iterations=EXCLUDED.iterations, v=EXCLUDED.v`, + [v.salt, v.verification, v.iterations || 200000, v.v || 1] + ); + res.json({ ok: true }); +}); + +// Export/Import utilitários +app.get('/api/export', authRequired, requireMaster, async (_req, res) => { + const [units, creds, vault, notes] = await Promise.all([ + pool.query('SELECT * FROM units'), + pool.query('SELECT * FROM creds'), + pool.query('SELECT * FROM vault WHERE id=1'), + pool.query('SELECT * FROM notes') + ]); + res.json({ + version: 1, + exportedAt: new Date().toISOString(), + units: units.rows.map(mapUnit), + creds: creds.rows.map(mapCred), + vault: vault.rows[0] ? { salt: vault.rows[0].salt, verification: vault.rows[0].verification, iterations: vault.rows[0].iterations, v: vault.rows[0].v || 1 } : null, + notes: notes.rows.map(r => ({ id: r.id, title: r.title || '', body: r.body || '', data: r.data || null, updatedAt: Number(r.updated_at || 0) })) + }); +}); + +app.post('/api/import', authRequired, requireMaster, async (req, res) => { + const data = req.body || {}; + const client = await pool.connect(); + try { + await client.query('BEGIN'); + await client.query('DELETE FROM units'); + await client.query('DELETE FROM creds'); + await client.query('DELETE FROM notes'); + if (data.vault) { + await client.query('DELETE FROM vault WHERE id=1'); + await client.query('INSERT INTO vault (id, salt, verification, iterations, v) VALUES (1,$1,$2,$3,$4)', [data.vault.salt, data.vault.verification, data.vault.iterations || 200000, data.vault.v || 1]); + } + if (Array.isArray(data.units)) { + for (const u of data.units) { + await client.query('INSERT INTO units (id, nome, data_criacao, ocs, link, ip, login, senha) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)', [u.id, u.nome, u.dataCriacao || null, u.ocs || [], u.link || '', u.ip || '', u.login || '', u.senha || '']); + } + } + if (Array.isArray(data.creds)) { + for (const c of data.creds) { + await client.query('INSERT INTO creds (id, nome, usuario, url, notas, password_enc, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7)', [c.id, c.nome, c.usuario || '', c.url || '', c.notas || '', c.passwordEnc, c.updatedAt || Date.now()]); + } + } + if (Array.isArray(data.notes)) { + for (const n of data.notes) { + await client.query('INSERT INTO notes (id, title, body, data, updated_at) VALUES ($1,$2,$3,$4,$5)', [n.id, n.title || '', n.body || '', n.data || null, n.updatedAt || Date.now()]); + } + } + await client.query('COMMIT'); + res.json({ ok: true }); + } catch (e) { + await client.query('ROLLBACK'); + console.error('Import error', e); + res.status(500).json({ error: 'import failed' }); + } finally { + client.release(); + } +}); + +initDb().then(() => { + app.listen(PORT, () => console.log(`API listening on :${PORT}`)); +}).catch((e) => { + console.error('Failed to init DB', e); + process.exit(1); +}); diff --git a/assets/Logo tema Claro.png b/assets/Logo tema Claro.png new file mode 100644 index 0000000000000000000000000000000000000000..7507a3a3e4436c45e01cf043235f4113381eca1b GIT binary patch literal 39096 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%0ASoc--Q78QG>p*$ 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�qi%$jH45ZjtNlKs{< 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 el.querySelector(sel); +const $$ = (sel, el = document) => Array.from(el.querySelectorAll(sel)); + +// Detecção de API +const state = { useApi: false, units: [], creds: [], vault: null, notes: [], auth: null }; + +async function probeApi() { + try { + const r = await fetch('/api/health', { cache: 'no-store' }); + state.useApi = r.ok; return state.useApi; + } catch { state.useApi = false; return false; } +} + +function updateStorageInfo() { + const el = document.getElementById('storageInfo'); + if (!el) return; + el.textContent = state.useApi + ? 'Servidor • Dados persistem no banco (Postgres) do container.' + : 'Local • Seus dados ficam somente no seu navegador.'; +} + +// ---- Auth (API) ---- +function getToken() { try { return JSON.parse(localStorage.getItem(STORAGE.AUTH) || 'null')?.token || null; } catch { return null; } } +function setToken(token) { localStorage.setItem(STORAGE.AUTH, JSON.stringify({ token })); } +async function apiFetch(path, opts = {}) { + const o = { ...opts, headers: { ...(opts.headers||{}), 'Content-Type': 'application/json' } }; + const token = getToken(); + if (token) o.headers['Authorization'] = `Bearer ${token}`; + const r = await fetch(path, o); + if (r.status === 401) { showLogin(); throw new Error('unauthorized'); } + return r; +} +function canMaster() { return !!state.auth?.user && state.auth.user.role === 'master'; } +function can(flag) { if (canMaster()) return true; return !!state.auth?.user && !!state.auth.user.permissions?.[flag]; } +function applyPermissionsUI() { + if (!state.useApi || !state.auth?.user) return; + // tabs + const tabs = [ + { id: 'unidades', read: 'units_read' }, + { id: 'senhas', read: 'creds_read' }, + { id: 'notas', read: 'notes_read' }, + ]; + tabs.forEach(t => { + const btn = document.querySelector(`.tab[data-tab="${t.id}"]`); + const view = document.getElementById(`view-${t.id}`); + const visible = can(t.read); + if (btn) btn.style.display = visible ? '' : 'none'; + if (view) view.style.display = visible ? '' : 'none'; + }); + // users tab only master + const userTab = document.querySelector('#tab-users'); + const userView = document.getElementById('view-usuarios'); + if (userTab) userTab.style.display = canMaster() ? '' : 'none'; + if (userView) userView.style.display = canMaster() ? '' : 'none'; + // action buttons + document.getElementById('addUnidadeBtn').style.display = can('units_write') ? '' : 'none'; + document.getElementById('addCredBtn').style.display = can('creds_write') ? '' : 'none'; + document.getElementById('addNoteBtn').style.display = can('notes_write') ? '' : 'none'; +} +function showLogin() { document.getElementById('authGate')?.classList.remove('hidden'); } +function hideLogin() { document.getElementById('authGate')?.classList.add('hidden'); } + +// Tema (claro/escuro) +function loadTheme() { + const t = localStorage.getItem(STORAGE.THEME) || "dark"; + document.documentElement.setAttribute("data-theme", t); +} +function toggleTheme() { + const cur = document.documentElement.getAttribute("data-theme") || "dark"; + const next = cur === "dark" ? "light" : "dark"; + document.documentElement.setAttribute("data-theme", next); + localStorage.setItem(STORAGE.THEME, next); +} + +// Utilidades +function uid() { + // ID simples e estável suficiente para uso local + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; +} + +function saveJSON(key, value) { localStorage.setItem(key, JSON.stringify(value)); } +function loadJSON(key, fallback) { + try { const v = JSON.parse(localStorage.getItem(key) || ""); return v ?? fallback; } + catch { return fallback; } +} + +function todayStr() { return new Date().toISOString().slice(0, 10); } + +function copyText(text) { + navigator.clipboard?.writeText(text).catch(() => { + const ta = document.createElement("textarea"); + ta.value = text; document.body.appendChild(ta); ta.select(); + document.execCommand("copy"); document.body.removeChild(ta); + }); +} + +// Navegação por abas +function initTabs() { + $$(".tab").forEach(btn => { + btn.addEventListener("click", () => { + $$(".tab").forEach(b => b.classList.remove("active")); + btn.classList.add("active"); + const tab = btn.dataset.tab; + $$(".view").forEach(v => v.classList.remove("active")); + $(`#view-${tab}`).classList.add("active"); + if (tab === 'usuarios') renderUsers(); + }); + }); +} + +// ------- Unidades ------- +async function refreshUnits() { + if (state.useApi) { + const r = await apiFetch('/api/units'); + state.units = r.ok ? await r.json() : []; + } else { + state.units = loadJSON(STORAGE.UNITS, []); + } + return state.units; +} +function loadUnits() { return state.units || []; } +function saveUnits(list) { saveJSON(STORAGE.UNITS, list); state.units = list; } + +function parseOCs(str) { + return (str || "") + .split(/[\n,]/g) + .map(s => s.trim()) + .filter(Boolean); +} + +async function renderUnits(filter = "") { + if (state.useApi) await refreshUnits(); + const listEl = $("#unidadesList"); + const items = loadUnits(); + const f = filter.trim().toLowerCase(); + const filtered = !f ? items : items.filter(u => { + return ( + u.nome.toLowerCase().includes(f) || + (u.ip || "").toLowerCase().includes(f) || + (u.ocs || []).some(x => x.toLowerCase().includes(f)) + ); + }); + // Ordena por nome: números primeiro, depois letras (ordem alfabética/natural) + filtered.sort((a, b) => { + const an = (a.nome || '').trim(); + const bn = (b.nome || '').trim(); + const aNum = /^[0-9]/.test(an); + const bNum = /^[0-9]/.test(bn); + if (aNum !== bNum) return aNum ? -1 : 1; // números primeiro + return an.localeCompare(bn, 'pt-BR', { numeric: true, sensitivity: 'base' }); + }); + + const canWrite = !state.useApi || can('units_write'); + listEl.innerHTML = filtered.map(u => { + const ocs = (u.ocs || []).map(x => `OC ${escapeHTML(x)}`).join(" "); + return ` +
+

${escapeHTML(u.nome)}

+
Criado em ${formatDate(u.dataCriacao)}${u.ip ? ` • IP ${escapeHTML(u.ip)}` : ""}
+ ${ocs ? `
${ocs}
` : ""} + ${u.link ? `` : ""} +
+ ${u.link ? `` : ""} + ${u.ip ? `` : ""} + ${canWrite ? `` : ''} + ${canWrite ? `` : ''} +
+
`; + }).join(""); + + // Complementa UI com login/senha + filtered.forEach(u => { + const card = listEl.querySelector(`.card[data-id="${u.id}"]`); + if (!card) return; + const meta = card.querySelector('.meta'); + if (u.login && meta && !meta.textContent.includes('Login')) { + meta.innerHTML = `${meta.innerHTML} • Login ${escapeHTML(u.login)}`; + } + const actions = card.querySelector('.actions'); + if (actions) { + if (u.login && !actions.querySelector('[data-act="copylogin"]')) { + const b = document.createElement('button'); + b.className = 'btn small'; + b.setAttribute('data-act', 'copylogin'); + b.textContent = 'Copiar Login'; + actions.insertBefore(b, actions.querySelector('[data-act="edit"]') || null); + } + if (u.senha && !actions.querySelector('[data-act="copypass"]')) { + const b2 = document.createElement('button'); + b2.className = 'btn small'; + b2.setAttribute('data-act', 'copypass'); + b2.textContent = 'Copiar Senha'; + actions.insertBefore(b2, actions.querySelector('[data-act="edit"]') || null); + } + } + }); + + // Delegação de eventos nos cartões + listEl.onclick = async (ev) => { + const btn = ev.target.closest("[data-act]"); + if (!btn) return; + const card = ev.target.closest(".card"); + const id = card?.dataset.id; + if (!id) return; + const items2 = loadUnits(); + const idx = items2.findIndex(x => x.id === id); + const u = items2[idx]; + if (!u) return; + const act = btn.dataset.act; + if (act === "open" && u.link) { + window.open(u.link, "_blank", "noopener"); + } else if (act === "copyip" && u.ip) { + copyText(u.ip); + } else if (act === "copylogin" && u.login) { + copyText(u.login); + } else if (act === "copypass" && u.senha) { + copyText(u.senha); + } else if (act === "edit") { + openUnidadeModal(u); + } else if (act === "del") { + if (confirm(`Excluir unidade "${u.nome}"?`)) { + if (state.useApi) { + await apiFetch(`/api/units/${encodeURIComponent(u.id)}`, { method: 'DELETE' }); + await refreshUnits(); + } else { + items2.splice(idx, 1); saveUnits(items2); + } + renderUnits($("#searchUnidades").value); + } + } + }; +} + +function openUnidadeModal(u) { + $("#unidadeId").value = u?.id || ""; + $("#unidadeNome").value = u?.nome || ""; + $("#unidadeData").value = u?.dataCriacao || todayStr(); + $("#unidadeOCs").value = (u?.ocs || []).join(", "); + $("#unidadeLink").value = u?.link || ""; + $("#unidadeIP").value = u?.ip || ""; + $("#unidadeLogin").value = u?.login || ""; + $("#unidadeSenha").value = ""; // nunca preencher senha existente + showModal("unidadeModal"); +} + +// ------- Modal Helpers ------- +function showModal(id) { const el = document.getElementById(id); el?.classList.remove("hidden"); } +function hideModal(id) { const el = document.getElementById(id); el?.classList.add("hidden"); } + +// Escapar HTML básico +function escapeHTML(s) { return (s ?? "").replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":">","\"":"""}[c])); } +function escapeAttr(s) { return escapeHTML(s).replace(/'/g, "'"); } +function formatDate(s) { try { const [y,m,d] = (s||"").split("-"); return d && m && y ? `${d}/${m}/${y}` : s; } catch { return s; } } +// ------- Cofre de Senhas (WebCrypto) ------- +async function refreshCreds() { + if (state.useApi) { + const r = await apiFetch('/api/creds'); + state.creds = r.ok ? await r.json() : []; + } else { + state.creds = loadJSON(STORAGE.CREDS, []); + } + return state.creds; +} +function loadCreds() { return state.creds || []; } +function saveCreds(list) { saveJSON(STORAGE.CREDS, list); state.creds = list; } + +async function refreshVault() { + if (state.useApi) { + const r = await apiFetch('/api/vault'); + state.vault = r.ok ? await r.json() : null; + } else { + state.vault = loadJSON(STORAGE.VAULT, null); + } + return state.vault; +} +function getVault() { return state.vault; } +function setVault(v) { saveJSON(STORAGE.VAULT, v); state.vault = v; } + +// ------- Notas ------- +async function refreshNotes() { + if (state.useApi) { + const r = await apiFetch('/api/notes'); + state.notes = r.ok ? await r.json() : []; + } else { + state.notes = loadJSON(STORAGE.NOTES, []); + } + return state.notes; +} +function loadNotes() { return state.notes || []; } +function saveNotes(list) { saveJSON(STORAGE.NOTES, list); state.notes = list; } + +function openNoteModal(n) { + $("#noteId").value = n?.id || ""; + $("#noteTitle").value = n?.title || ""; + $("#noteBody").value = n?.body || ""; + $("#noteType").value = n?.data?.type || 'texto'; + editingSteps = (n?.data?.steps || []).map(s => ({ id: s.id || uid(), text: s.text || '' })); + toggleStepsBlock(); + renderSteps(); + showModal("noteModal"); +} + +async function renderNotes(filter = "") { + if (state.useApi) await refreshNotes(); + const listText = document.getElementById('notesTextList'); + const listSteps = document.getElementById('notesStepsList'); + const items = loadNotes(); + const f = filter.trim().toLowerCase(); + const filtered = !f ? items : items.filter(n => ( + (n.title || "").toLowerCase().includes(f) || (n.body || "").toLowerCase().includes(f) + )); + filtered.sort((a,b) => (b.updatedAt||0)-(a.updatedAt||0)); + + const renderCard = (n) => { + const preview = escapeHTML((n.body || '').split('\n').slice(0,3).join(' ')); + const when = n.updatedAt ? new Date(n.updatedAt).toLocaleString() : ''; + return ` +
+

${escapeHTML(n.title || 'Sem título')}

+
${n.data?.type === 'passos' ? 'Passo a passo' : ''} ${when ? `• Atualizado: ${when}` : ''}
+ ${n.data?.type === 'passos' ? `
${(n.data.steps||[]).slice(0,3).map((s,i)=>`${i+1}. ${escapeHTML(s.text||'')}`).join('
')}
` : (preview ? `
${preview}
` : '')} +
`; + }; + + const texts = filtered.filter(n => (n.data?.type || 'texto') !== 'passos'); + const steps = filtered.filter(n => n.data?.type === 'passos'); + + if (listText) listText.innerHTML = texts.map(renderCard).join(""); + if (listSteps) listSteps.innerHTML = steps.map(renderCard).join(""); + + const attach = (el) => { + if (!el) return; + el.onclick = (ev) => { + const card = ev.target.closest('.card'); + if (!card) return; + const id = card.dataset.id; + const n = loadNotes().find(x => x.id === id); + if (n) openNoteView(n); + }; + }; + attach(listText); + attach(listSteps); +} + +// --- Editor de passos (modal Nota) +let editingSteps = []; +function toggleStepsBlock() { + const type = $("#noteType").value; + const block = $("#stepsBlock"); + if (type === 'passos') block.classList.remove('hidden'); else block.classList.add('hidden'); +} +function renderSteps() { + const el = $("#stepsList"); + el.innerHTML = editingSteps.map((s, idx) => ` +
+
${escapeHTML(s.text)}
+ +
+ `).join(""); +} + +// Visualização de nota (popup) +function openNoteView(n) { + const body = document.getElementById('noteViewBody'); + const title = document.getElementById('noteViewTitle'); + title.textContent = n.title || 'Sem título'; + const when = n.updatedAt ? new Date(n.updatedAt).toLocaleString() : ''; + const chip = n.data?.type === 'passos' ? 'Passo a passo' : 'Texto'; + const sections = []; + if (n.data?.type === 'passos' && (n.data.steps || []).length) { + const items = (n.data.steps || []).map(s => `
  • ${escapeHTML(s.text || '')}
  • `).join(''); + sections.push(`
    Passos
      ${items}
    `); + } + if ((n.body || '').trim()) { + sections.push(`
    Conteúdo
    ${escapeHTML(n.body)}
    `); + } + if (!sections.length) { + sections.push('
    Sem conteúdo
    '); + } + body.innerHTML = `
    ${chip} ${when ? `• Atualizado: ${when}` : ''}
    ${sections.join('')}
    `; + // Ações + document.getElementById('noteViewEdit').onclick = () => { hideModal('noteViewModal'); openNoteModal(n); }; + document.getElementById('noteViewDelete').onclick = async () => { + if (!confirm('Excluir esta nota?')) return; + if (state.useApi) { + await apiFetch(`/api/notes/${encodeURIComponent(n.id)}`, { method: 'DELETE' }); + await refreshNotes(); + } else { + const list = loadNotes().filter(x => x.id !== n.id); saveNotes(list); + } + hideModal('noteViewModal'); + renderNotes($("#searchNotes").value); + }; + showModal('noteViewModal'); +} + +const textEnc = new TextEncoder(); +const textDec = new TextDecoder(); + +function ab2b64(ab) { + const bytes = new Uint8Array(ab); + let bin = ""; for (let i=0; i { + return ( + c.nome.toLowerCase().includes(f) || + (c.usuario || "").toLowerCase().includes(f) || + (c.url || "").toLowerCase().includes(f) + ); + }); + filtered.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)); + + const canWrite = !state.useApi || can('creds_write'); + listEl.innerHTML = filtered.map(c => { + const preview = escapeHTML((c.notas || '').split('\n').slice(0,2).join(' ')); + const when = c.updatedAt ? new Date(c.updatedAt).toLocaleString() : ''; + return ` +
    +

    ${escapeHTML(c.nome)}

    +
    ${c.usuario ? `Usuário ${escapeHTML(c.usuario)} • `: ''}${c.url ? `${escapeHTML(c.url)}` : ''} ${when ? `• Atualizado: ${when}` : ''}
    + ${preview ? `
    ${preview}
    ` : ''} +
    `; + }).join(""); + + listEl.onclick = async (ev) => { + const btn = ev.target.closest("[data-act]"); + const card = ev.target.closest('.card'); + if (!card) return; + const id = card.dataset.id; + const items2 = loadCreds(); + const idx = items2.findIndex(x => x.id === id); + const c = items2[idx]; + if (!c) return; + if (btn) { + // Mantido por compatibilidade caso algum botão exista + if (!vaultKey) { alert("Cofre bloqueado."); return; } + const act = btn.dataset.act; + if (act === "open" && c.url) { + window.open(c.url, "_blank", "noopener"); + } else if (act === "copyuser" && c.usuario) { + copyText(c.usuario); + } else if (act === "copypass") { + try { const pass = await decryptString(vaultKey, c.passwordEnc); copyText(pass); } catch { alert("Falha ao descriptografar."); } + } else if (act === "reveal") { + try { const pass = await decryptString(vaultKey, c.passwordEnc); alert(`Senha: ${pass}`); } catch { alert("Falha ao descriptografar."); } + } else if (act === "edit") { + openCredModal(c); + } else if (act === "del") { + if (confirm(`Excluir credencial "${c.nome}"?`)) { + if (state.useApi) { + await apiFetch(`/api/creds/${encodeURIComponent(c.id)}`, { method: 'DELETE' }); + await refreshCreds(); + } else { + items2.splice(idx,1); saveCreds(items2); + } + renderCreds($("#searchCreds").value); + } + } + } else { + // Clique no card abre visualização + if (!vaultKey) { alert("Cofre bloqueado."); return; } + openCredView(c, canWrite); + } + }; +} + +function openCredModal(c) { + $("#credId").value = c?.id || ""; + $("#credNome").value = c?.nome || ""; + $("#credUsuario").value = c?.usuario || ""; + $("#credSenha").value = ""; // nunca preencher + $("#credUrl").value = c?.url || ""; + $("#credNotas").value = c?.notas || ""; + showModal("credModal"); +} + +// Visualização de credencial (popup) +function openCredView(c, canWrite) { + const body = document.getElementById('credViewBody'); + const title = document.getElementById('credViewTitle'); + title.textContent = c.nome || 'Credencial'; + const when = c.updatedAt ? new Date(c.updatedAt).toLocaleString() : ''; + const sections = []; + const access = []; + if (c.usuario) access.push(`
    Usuário: ${escapeHTML(c.usuario)}
    `); + if (c.url) access.push(``); + if (access.length) sections.push(`
    Acesso
    ${access.join('')}
    `); + if ((c.notas || '').trim()) sections.push(`
    Notas
    ${escapeHTML(c.notas)}
    `); + if (!sections.length) sections.push('
    Sem detalhes
    '); + body.innerHTML = `
    ${when ? `Atualizado: ${when}` : ''}
    ${sections.join('')}
    `; + + // Ações + const btnOpen = document.getElementById('credViewOpen'); + const btnCopyUser = document.getElementById('credViewCopyUser'); + const btnCopyPass = document.getElementById('credViewCopyPass'); + const btnReveal = document.getElementById('credViewReveal'); + const btnEdit = document.getElementById('credViewEdit'); + const btnDelete = document.getElementById('credViewDelete'); + + btnOpen.style.display = c.url ? '' : 'none'; + btnCopyUser.style.display = c.usuario ? '' : 'none'; + btnEdit.style.display = canWrite ? '' : 'none'; + btnDelete.style.display = canWrite ? '' : 'none'; + + btnOpen.onclick = () => { if (c.url) window.open(c.url, '_blank', 'noopener'); }; + btnCopyUser.onclick = () => { if (c.usuario) copyText(c.usuario); }; + btnCopyPass.onclick = async () => { + try { const pass = await decryptString(vaultKey, c.passwordEnc); copyText(pass); } catch { alert('Falha ao descriptografar.'); } + }; + btnReveal.onclick = async () => { + try { const pass = await decryptString(vaultKey, c.passwordEnc); alert(`Senha: ${pass}`); } catch { alert('Falha ao descriptografar.'); } + }; + btnEdit.onclick = () => { hideModal('credViewModal'); openCredModal(c); }; + btnDelete.onclick = async () => { + if (!confirm(`Excluir credencial "${c.nome}"?`)) return; + const items2 = loadCreds(); + const idx = items2.findIndex(x => x.id === c.id); + if (state.useApi) { + await apiFetch(`/api/creds/${encodeURIComponent(c.id)}`, { method: 'DELETE' }); + await refreshCreds(); + } else if (idx >= 0) { + items2.splice(idx,1); saveCreds(items2); + } + hideModal('credViewModal'); + renderCreds($("#searchCreds").value); + }; + + showModal('credViewModal'); +} + +// ------- Exportar / Importar ------- +function exportAll() { + const payload = { + version: 1, + exportedAt: new Date().toISOString(), + theme: document.documentElement.getAttribute("data-theme"), + units: loadUnits(), + vault: getVault(), + creds: loadCreds(), + notes: loadNotes(), + }; + const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); + const a = document.createElement("a"); + const d = new Date(); + const y = d.getFullYear(); + const m = String(d.getMonth()+1).padStart(2,"0"); + const day = String(d.getDate()).padStart(2,"0"); + a.download = `telseg_dashboard_backup_${y}${m}${day}.json`; + a.href = URL.createObjectURL(blob); + a.click(); + setTimeout(() => URL.revokeObjectURL(a.href), 3000); +} + +function importAll(file) { + const reader = new FileReader(); + reader.onload = () => { + try { + const data = JSON.parse(String(reader.result || "{}")); + if (!confirm("Importar backup e substituir dados atuais?")) return; + if (data.theme) { localStorage.setItem(STORAGE.THEME, data.theme); document.documentElement.setAttribute("data-theme", data.theme); } + if (Array.isArray(data.units)) saveUnits(data.units); + if (Array.isArray(data.creds)) saveCreds(data.creds); + if (data.vault && data.vault.salt && data.vault.verification) setVault(data.vault); + if (Array.isArray(data.notes)) saveNotes(data.notes); + alert("Importação concluída."); + renderUnits($("#searchUnidades").value); + renderNotes($("#searchNotes").value); + lockVault(); // força rechecagem do cofre + } catch (e) { + alert("Arquivo inválido."); + } + }; + reader.readAsText(file); +} + +// ------- Inicialização ------- +document.addEventListener("DOMContentLoaded", async () => { + loadTheme(); + await probeApi(); + // Auth flow when API is enabled + if (state.useApi) { + // try existing token + const token = getToken(); + if (token) { + try { + const r = await apiFetch('/api/auth/me'); + const data = await r.json(); + state.auth = data; hideLogin(); + applyPermissionsUI(); + if (canMaster()) await renderUsers(); + } catch { showLogin(); } + } else { + showLogin(); + } + } + if (!state.useApi || state.auth?.user) { + await Promise.all([refreshUnits(), refreshCreds(), refreshVault(), refreshNotes()]); + } + updateStorageInfo(); + applyPermissionsUI(); + + $("#themeToggle").addEventListener("click", toggleTheme); + initTabs(); + + // Unidades + $("#addUnidadeBtn").addEventListener("click", () => openUnidadeModal()); + $("#searchUnidades").addEventListener("input", ev => renderUnits(ev.target.value)); + $("#unidadeForm").addEventListener("submit", async (ev) => { + ev.preventDefault(); + const id = $("#unidadeId").value || uid(); + const listBefore = loadUnits(); + const idxBefore = listBefore.findIndex(x => x.id === id); + const existing = idxBefore >= 0 ? listBefore[idxBefore] : null; + const item = { + id, + nome: $("#unidadeNome").value.trim(), + dataCriacao: $("#unidadeData").value || todayStr(), + ocs: parseOCs($("#unidadeOCs").value), + link: $("#unidadeLink").value.trim(), + ip: $("#unidadeIP").value.trim(), + login: $("#unidadeLogin").value.trim(), + senha: $("#unidadeSenha").value ? $("#unidadeSenha").value : (existing?.senha || ''), + }; + if (!item.nome) { alert("Informe o nome do cliente."); return; } + if (state.useApi) { + const exists = idxBefore >= 0; + await apiFetch(exists ? `/api/units/${encodeURIComponent(id)}` : '/api/units', { + method: exists ? 'PUT' : 'POST', + body: JSON.stringify(item) + }); + await refreshUnits(); + } else { + const list = listBefore.slice(); + if (idxBefore >= 0) list[idxBefore] = item; else list.push(item); + saveUnits(list); + } + $("#unidadeSenha").value = ""; + hideModal("unidadeModal"); + renderUnits($("#searchUnidades").value); + }); + + // Fechar modais + document.body.addEventListener("click", (ev) => { + const btn = ev.target.closest("[data-close-modal]"); + if (btn) hideModal(btn.getAttribute("data-close-modal")); + }); + + // Senhas/Cofre + renderVaultGate(); + $("#lockVaultBtn").addEventListener("click", lockVault); + $("#searchCreds").addEventListener("input", ev => renderCreds(ev.target.value)); + $("#addCredBtn").addEventListener("click", () => { + if (!vaultKey) { alert("Desbloqueie o cofre primeiro."); return; } + openCredModal(); + }); + + // Notas + $("#addNoteBtn").addEventListener("click", () => openNoteModal()); + $("#searchNotes").addEventListener("input", ev => renderNotes(ev.target.value)); + $("#noteType").addEventListener("change", toggleStepsBlock); + $("#addStepBtn").addEventListener("click", () => { + const input = $("#newStepText"); + const text = input.value.trim(); + if (!text) return; + editingSteps.push({ id: uid(), text }); + input.value = ""; + renderSteps(); + }); + $("#stepsList").addEventListener("click", (ev) => { + const del = ev.target.closest('[data-del]'); + const row = ev.target.closest('.step-card'); + if (del && row) { + const idx = Number(row.dataset.idx); + editingSteps.splice(idx,1); + renderSteps(); + } + }); + $("#stepsList").addEventListener("blur", (ev) => { + const row = ev.target.closest('.step-card'); + if (!row) return; + const idx = Number(row.dataset.idx); + const textEl = row.querySelector('.text'); + if (textEl) editingSteps[idx].text = textEl.textContent.trim(); + }, true); + $("#noteForm").addEventListener("submit", async (ev) => { + ev.preventDefault(); + const id = $("#noteId").value || uid(); + const note = { + id, + title: $("#noteTitle").value.trim(), + body: $("#noteBody").value, + updatedAt: Date.now(), + data: $("#noteType").value === 'passos' ? { type: 'passos', steps: editingSteps.slice() } : { type: 'texto' } + }; + if (state.useApi) { + const exists = loadNotes().some(n => n.id === id); + await apiFetch(exists ? `/api/notes/${encodeURIComponent(id)}` : '/api/notes', { + method: exists ? 'PUT' : 'POST', + body: JSON.stringify(note) + }); + await refreshNotes(); + } else { + const list = loadNotes(); + const idx = list.findIndex(n => n.id === id); + if (idx>=0) list[idx] = note; else list.push(note); + saveNotes(list); + } + hideModal('noteModal'); + renderNotes($("#searchNotes").value); + }); + + $("#setVaultForm").addEventListener("submit", async (ev) => { + ev.preventDefault(); + const p1 = $("#vaultPass1").value; + const p2 = $("#vaultPass2").value; + if (!p1 || p1 !== p2) { alert("Senhas não conferem."); return; } + const salt = ab2b64(crypto.getRandomValues(new Uint8Array(16)).buffer); + const key = await deriveKey(p1, salt, VAULT_ITERS); + const verification = await encryptString(key, "ok"); + const meta = { salt, verification, iterations: VAULT_ITERS, v: 1 }; + if (state.useApi) { + await apiFetch('/api/vault', { method: 'PUT', body: JSON.stringify(meta) }); + await refreshVault(); + } else { + setVault(meta); + } + alert("Cofre configurado. Guarde sua senha mestra!"); + $("#vaultPass1").value = ""; $("#vaultPass2").value = ""; + unlockVault(p1); + }); + + $("#unlockVaultForm").addEventListener("submit", async (ev) => { + ev.preventDefault(); + const p = $("#vaultUnlockPass").value; + await unlockVault(p); + }); + + $("#credForm").addEventListener("submit", async (ev) => { + ev.preventDefault(); + if (!vaultKey) { alert("Cofre bloqueado."); return; } + const id = $("#credId").value || uid(); + const list = loadCreds(); + const idx = list.findIndex(x => x.id === id); + const existing = idx >= 0 ? list[idx] : null; + const passwordPlain = $("#credSenha").value; // pode estar vazio ao editar + let passwordEnc = existing?.passwordEnc; + if (passwordPlain) passwordEnc = await encryptString(vaultKey, passwordPlain); + const item = { + id, + nome: $("#credNome").value.trim(), + usuario: $("#credUsuario").value.trim(), + url: $("#credUrl").value.trim(), + notas: $("#credNotas").value.trim(), + passwordEnc, + updatedAt: Date.now(), + }; + if (!item.nome) { alert("Informe o nome."); return; } + if (!item.passwordEnc) { alert("Informe a senha."); return; } + if (state.useApi) { + const exists = idx >= 0; + await apiFetch(exists ? `/api/creds/${encodeURIComponent(id)}` : '/api/creds', { method: exists ? 'PUT' : 'POST', body: JSON.stringify(item) }); + await refreshCreds(); + } else { + if (idx >= 0) list[idx] = item; else list.push(item); + saveCreds(list); + } + hideModal("credModal"); + renderCreds($("#searchCreds").value); + }); + + // Exportar / Importar + $("#exportBtn").addEventListener("click", async () => { + if (state.useApi) { + const r = await apiFetch('/api/export'); + const payload = await r.json(); + const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); + const a = document.createElement("a"); + a.download = `telseg_dashboard_backup_api_${new Date().toISOString().slice(0,10)}.json`; + a.href = URL.createObjectURL(blob); a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 3000); + } else { + exportAll(); + } + }); + $("#importFile").addEventListener("change", async (ev) => { + const f = ev.target.files?.[0]; if (!f) return; + if (state.useApi) { + const text = await f.text(); + const data = JSON.parse(text); + if (!confirm("Importar backup e substituir dados atuais no servidor?")) return; + await apiFetch('/api/import', { method: 'POST', body: JSON.stringify(data) }); + await Promise.all([refreshUnits(), refreshCreds(), refreshVault(), refreshNotes()]); + lockVault(); + renderUnits($("#searchUnidades").value); // re-render + renderNotes($("#searchNotes").value); + } else { + importAll(f); + } + ev.target.value = ""; // permite importar o mesmo arquivo novamente + }); + + // Login form + const loginForm = document.getElementById('loginForm'); + if (loginForm) { + loginForm.addEventListener('submit', async (ev) => { + ev.preventDefault(); + const username = document.getElementById('loginUser').value.trim(); + const password = document.getElementById('loginPass').value; + try { + const r = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); + if (!r.ok) throw new Error(); + const data = await r.json(); + setToken(data.token); + state.auth = { user: data.user }; + hideLogin(); + applyPermissionsUI(); + await Promise.all([refreshUnits(), refreshCreds(), refreshVault(), refreshNotes()]); + if (canMaster()) await renderUsers(); + renderUnits(document.getElementById('searchUnidades').value); + renderNotes(document.getElementById('searchNotes').value); + } catch { + alert('Login inválido'); + } + }); + } + + // Render inicial + if (!state.useApi || state.auth?.user) { + renderUnits(""); + renderNotes(""); + } +}); + +// ---- Usuários (somente Master) ---- +async function renderUsers() { + if (!state.useApi || !canMaster()) return; + try { + const r = await apiFetch('/api/users'); + const users = await r.json(); + const list = document.getElementById('usersList'); + list.innerHTML = users.map(u => { + const p = u.permissions || {}; + return ` +
    +

    ${escapeHTML(u.username)} ${u.role === 'master' ? 'Master' : ''}

    +
    + + + + + + +
    +
    + + + +
    +
    `; + }).join(''); + + // Handlers + list.onchange = async (ev) => { + const card = ev.target.closest('.card'); if (!card) return; + const id = card.dataset.id; + if (ev.target.matches('[data-perm]')) { + // Collect card perms + const inputs = Array.from(card.querySelectorAll('[data-perm]')); + const body = Object.fromEntries(inputs.map(i => [i.getAttribute('data-perm'), i.checked])); + await apiFetch(`/api/users/${encodeURIComponent(id)}/permissions`, { method: 'PUT', body: JSON.stringify(body) }); + } else if (ev.target.matches('[data-role]')) { + const role = ev.target.value; + await apiFetch(`/api/users/${encodeURIComponent(id)}`, { method: 'PUT', body: JSON.stringify({ role }) }); + await renderUsers(); + } + }; + list.onclick = async (ev) => { + const card = ev.target.closest('.card'); if (!card) return; + const id = card.dataset.id; + const actBtn = ev.target.closest('[data-act]'); if (!actBtn) return; + if (actBtn.dataset.act === 'reset') { + const pass = prompt('Nova senha:'); if (!pass) return; + await apiFetch(`/api/users/${encodeURIComponent(id)}`, { method: 'PUT', body: JSON.stringify({ password: pass }) }); + alert('Senha atualizada'); + } else if (actBtn.dataset.act === 'del') { + if (!confirm('Excluir este usuário?')) return; + await apiFetch(`/api/users/${encodeURIComponent(id)}`, { method: 'DELETE' }); + await renderUsers(); + } + }; + } catch (e) { + console.error('renderUsers', e); + } +} + +// Create user form +document.addEventListener('DOMContentLoaded', () => { + const form = document.getElementById('userForm'); + if (!form) return; + form.addEventListener('submit', async (ev) => { + ev.preventDefault(); + const username = document.getElementById('newUserName').value.trim(); + const password = document.getElementById('newUserPass').value; + const role = document.getElementById('newUserRole').value; + const body = { + username, password, role, + permissions: { + units_read: document.getElementById('p_units_read').checked, + units_write: document.getElementById('p_units_write').checked, + creds_read: document.getElementById('p_creds_read').checked, + creds_write: document.getElementById('p_creds_write').checked, + notes_read: document.getElementById('p_notes_read').checked, + notes_write: document.getElementById('p_notes_write').checked, + } + }; + try { + await apiFetch('/api/users', { method: 'POST', body: JSON.stringify(body) }); + form.reset(); + await renderUsers(); + alert('Usuário criado'); + } catch { alert('Falha ao criar usuário'); } + }); +}); diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..8ba8751 --- /dev/null +++ b/assets/style.css @@ -0,0 +1,159 @@ +/* Dashboard Telseg - estilos base */ +:root { + /* Tema escuro neutro (preto) */ + --bg: #000000; /* preto puro */ + --panel: #0a0a0a; /* preto levemente mais claro que o bg */ + --card: #121212; /* cards um tom mais claro (igual à barra) */ + --muted: #cbd5e1; /* texto secundário claro */ + --text: #f1f5f9; /* texto principal muito claro */ + --primary: #22c55e; /* green-500 */ + --primary-600: #16a34a; + --border: #262626; /* cinza neutro para contornos sutis */ + --danger: #ef4444; /* red-500 */ + --warning: #f59e0b; /* amber-500 */ + --link: #93c5fd; /* blue-300 */ +} + +[data-theme="light"] { + --bg: #f8fafc; /* slate-50 */ + --panel: #ffffff; + --muted: #475569; /* slate-600 */ + --text: #0f172a; /* slate-900 */ + --primary: #16a34a; + --primary-600: #15803d; + --border: #e2e8f0; + --link: #2563eb; /* blue-600 */ +} + +* { box-sizing: border-box; } +html, body { height: 100%; } +body { + margin: 0; + font: 16px/1.5 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, "Apple Color Emoji", "Segoe UI Emoji"; + color: var(--text); + background: radial-gradient(1200px 800px at 20% 0%, rgba(34,197,94,0.06), transparent 45%), var(--bg); +} + +.app-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 20px; +} +.brand { display: flex; align-items: center; gap: 12px; } +.brand h1 { font-size: 20px; margin: 0; font-weight: 700; letter-spacing: 0.3px; } +.logo { display: none; } +.logo-img { height: 64px; width: auto; display: inline-block; margin-right: 8px; } +.logo-img.dark { display: none; } +[data-theme="dark"] .logo-img.dark { display: inline-block; } +[data-theme="dark"] .logo-img.light { display: none; } +.top-actions { display: flex; gap: 8px; align-items: center; } + +/* Links legíveis no escuro */ +a { color: var(--link); text-decoration: underline; } +a:hover { filter: brightness(1.1); } + +.tabs { display: flex; gap: 8px; padding: 0 12px 8px; border-bottom: 1px solid var(--border); } +.tab { background: transparent; color: var(--muted); border: none; padding: 10px 14px; border-radius: 8px; cursor: pointer; } +.tab.active { background: var(--panel); color: var(--text); box-shadow: inset 0 0 0 1px var(--border); } + +.views { padding: 16px; max-width: 1680px; margin: 0 auto; } +.view { display: none; } +.view.active { display: block; } +.view-bar { display: flex; justify-content: space-between; gap: 12px; margin: 12px 0 16px; } +.view-bar .right, .view-bar .left { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } + +.input { width: 100%; padding: 10px 12px; background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 10px; outline: none; } +.input::placeholder { color: var(--muted); } + +.btn { padding: 10px 14px; border: 1px solid var(--border); background: var(--panel); color: var(--text); border-radius: 10px; cursor: pointer; } +.btn:hover { filter: brightness(1.05); } +.btn.primary { background: linear-gradient(180deg, var(--primary), var(--primary-600)); border: none; color: white; } +.btn.danger { background: linear-gradient(180deg, #f87171, #ef4444); border: none; color: white; } +.btn.small { padding: 6px 10px; font-size: 13px; } +.icon-btn { padding: 8px 10px; border-radius: 8px; background: var(--panel); color: var(--text); border: 1px solid var(--border); cursor: pointer; } + +.cards { display: grid; grid-template-columns: 1fr; gap: 16px; } +/* Unidades em duas colunas em telas largas */ +@media (min-width: 980px) { + /* Em telas largas, usar colunas responsivas com largura mínima + suficiente para manter os botões na mesma linha. */ + #unidadesList.cards, + #credsList.cards { grid-template-columns: repeat(auto-fit, minmax(720px, 1fr)); } + /* notas agora usam contêiner de colunas */ + /* Manter as ações numa única linha; se apertar muito, permite rolagem. */ + .card .actions { flex-wrap: nowrap; overflow-x: auto; } +} +.card { background: var(--card, var(--panel)); border: 1px solid var(--border); border-radius: 14px; padding: 20px; display: grid; gap: 10px; font-size: 1.12rem; line-height: 1.45; } +.card.clickable { cursor: pointer; } +.card.clickable:hover { box-shadow: 0 0 0 1px var(--border); } +.card h4 { margin: 0; font-size: 22px; } +.meta { color: var(--muted); font-size: 15px; } +.chips { display: flex; gap: 6px; flex-wrap: wrap; } +.chip { font-size: 14px; padding: 4px 10px; border-radius: 999px; color: var(--text); background: rgba(148,163,184,0.25); border: 1px solid var(--border); } +.actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; } +/* botões mais legíveis dentro dos cards */ +.card .btn { font-size: 14px; padding: 10px 14px; } +.card .btn.small { font-size: 14px; padding: 8px 12px; } + +/* Evitar quebra de layout com textos longos (ex.: notas, URLs, tokens) */ +/* Mantém quebras de linha digitadas e permite quebrar palavras muito grandes */ +.card .k { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; } +.card .meta { overflow-wrap: anywhere; word-break: break-word; } +.card a { overflow-wrap: anywhere; word-break: break-word; } + +/* Passos (notas) */ +.step-card { background: var(--panel); border: 1px dashed var(--border); border-radius: 10px; padding: 10px; display: flex; justify-content: space-between; align-items: center; gap: 8px; } +.step-card .text { flex: 1; } +.step-card .text[contenteditable="true"] { outline: 1px dashed var(--border); border-radius: 6px; padding: 3px 6px; } + +/* Visualização de nota */ +.note-view .title { font-size: 22px; margin: 0; } +.note-view .meta { margin-top: 4px; } +.note-view .content { white-space: pre-wrap; line-height: 1.5; font-size: 1.05rem; overflow-wrap: anywhere; word-break: break-word; } +.note-view ol { margin: 6px 0 0 22px; } + +/* Visualização de credencial */ +.cred-view .content { white-space: pre-wrap; line-height: 1.5; font-size: 1.05rem; overflow-wrap: anywhere; word-break: break-word; } +.note-view .modal-body, .cred-view .modal-body { max-height: 70vh; overflow: auto; } +.note-view .modal-body a, .cred-view .modal-body a { overflow-wrap: anywhere; word-break: break-word; } + +.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; } +label span { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; } + +.modal { position: fixed; inset: 0; background: rgba(2,6,23,0.6); display: grid; place-items: center; padding: 16px; z-index: 20; } +.modal.hidden { display: none; } +.modal-card { width: min(720px, 100%); background: var(--card, var(--panel)); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 20px 60px rgba(0,0,0,.35); overflow-wrap: anywhere; } +.modal .modal-card { background: var(--card, var(--panel)); } +.modal-card.wide { width: min(960px, 100%); } +.modal-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; border-bottom: 1px solid var(--border); } +.modal-head h3 { margin: 0; font-size: 16px; } +.modal-body { padding: 14px; display: grid; gap: 12px; } +.modal-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; } + +.vault-lock { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px; display: grid; gap: 10px; max-width: 720px; margin: 18px auto; } +.vault-form { display: grid; gap: 12px; } +.hidden { display: none !important; } + +.app-footer { padding: 18px; text-align: center; color: var(--muted); } + +/* Helpers */ +.k { color: var(--muted); font-size: 13px; } +.row a { display: inline-block; max-width: 100%; } +.row { display: flex; gap: 8px; align-items: center; } + +/* Notas: duas colunas (Notas | Passo a Passo) */ +.notes-columns { display: grid; grid-template-columns: 1fr; gap: 16px; } +.notes-col { display: grid; gap: 8px; align-content: start; } +.column-title { margin: 2px 4px 4px; font-size: 16px; color: var(--muted); font-weight: 600; } +@media (min-width: 980px) { + .notes-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +@media (max-width: 520px) { + .view-bar { flex-direction: column; align-items: stretch; } + .top-actions { flex-wrap: wrap; } + /* Logo um pouco menor em telas muito pequenas */ + .logo-img { height: 40px; } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bd07079 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +version: "3.8" +services: + dashboard: + build: . + container_name: telseg_dashboard + ports: + - "4242:80" + depends_on: + - api + restart: unless-stopped + + db: + image: postgres:15-alpine + container_name: telseg_db + environment: + - POSTGRES_DB=${POSTGRES_DB:-telseg} + - POSTGRES_USER=${POSTGRES_USER:-telseg} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-telseg} + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" # opcional: expõe acesso ao host + healthcheck: + test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB:-telseg} -U ${POSTGRES_USER:-telseg}"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + adminer: + image: adminer:latest + container_name: telseg_adminer + depends_on: + - db + environment: + - ADMINER_DEFAULT_SERVER=db + ports: + - "8081:8080" + restart: unless-stopped + + api: + build: ./api + container_name: telseg_api + environment: + - DATABASE_URL=postgres://${POSTGRES_USER:-telseg}:${POSTGRES_PASSWORD:-telseg}@db:5432/${POSTGRES_DB:-telseg} + - PORT=3000 + - JWT_SECRET=${JWT_SECRET:-change_this_secret} + - ADMIN_USER=${ADMIN_USER:-admin} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123} + depends_on: + - db + restart: unless-stopped + +volumes: + pgdata: diff --git a/index.html b/index.html new file mode 100644 index 0000000..125d365 --- /dev/null +++ b/index.html @@ -0,0 +1,347 @@ + + + + + + Dashboard Telseg + + + + + +
    +
    + Logo + +

    Dashboard Telseg

    +
    + +
    + +
    + + + + +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + +
    +
    +

    Cofre de Senhas

    +

    Proteja suas senhas com uma senha mestra.

    + + + +
    + + +
    + + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +

    Notas

    +
    +
    +
    +

    Passo a Passo

    +
    +
    +
    +
    + + + +
    +
    +
    +

    Gerenciar Usuários

    +
    +
    +
    +
    +

    Novo Usuário

    +
    + + + +
    +
    Permissões
    +
    + + + + + + +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + +
    + Detectando modo de armazenamento… +
    + + + + + + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..0675ef5 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,28 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Proxy para API (mesma origem) + location /api/ { + proxy_pass http://api:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Authorization $http_authorization; + } + + # SPA fallback: serve index.html para rotas desconhecidas + location / { + try_files $uri $uri/ /index.html; + } + + # Cache de assets por 7 dias (HTML não é cacheado) + location /assets/ { + expires 7d; + add_header Cache-Control "public, max-age=604800, must-revalidate"; + } +}