@cccarv82/freya 1.0.56 → 1.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/web-ui.css +61 -13
- package/cli/web-ui.js +151 -27
- package/cli/web.js +110 -8
- package/package.json +1 -1
package/cli/web-ui.css
CHANGED
|
@@ -155,22 +155,24 @@ body {
|
|
|
155
155
|
color: var(--muted);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
.statusPill {
|
|
159
|
-
display: flex;
|
|
160
|
-
align-items: center;
|
|
161
|
-
gap: 8px;
|
|
162
|
-
padding: 6px 10px;
|
|
163
|
-
border: 1px solid var(--line);
|
|
164
|
-
border-radius: 999px;
|
|
165
|
-
background: var(--paper2);
|
|
166
|
-
font-family: var(--mono);
|
|
167
|
-
font-size: 12px;
|
|
168
|
-
color: var(--muted);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
158
|
.dot { width: 10px; height: 10px; border-radius: 6px; background: rgba(148,163,184,.6); }
|
|
172
159
|
.dot.ok { background: rgba(16,185,129,.85); }
|
|
173
160
|
.dot.err { background: rgba(239,68,68,.85); }
|
|
161
|
+
.dot.run { background: rgba(56,189,248,.9); }
|
|
162
|
+
.dot.plan { background: rgba(250,204,21,.9); }
|
|
163
|
+
|
|
164
|
+
.railStatus {
|
|
165
|
+
width: 16px;
|
|
166
|
+
height: 16px;
|
|
167
|
+
border-radius: 999px;
|
|
168
|
+
border: 1px solid var(--line2);
|
|
169
|
+
background: rgba(148,163,184,.6);
|
|
170
|
+
box-shadow: 0 0 0 4px rgba(0,0,0,.25);
|
|
171
|
+
}
|
|
172
|
+
.railStatus.ok { background: rgba(16,185,129,.9); }
|
|
173
|
+
.railStatus.err { background: rgba(239,68,68,.9); }
|
|
174
|
+
.railStatus.run { background: rgba(56,189,248,.9); }
|
|
175
|
+
.railStatus.plan { background: rgba(250,204,21,.9); }
|
|
174
176
|
|
|
175
177
|
.sidePath {
|
|
176
178
|
margin: 10px 6px 10px;
|
|
@@ -285,6 +287,52 @@ body {
|
|
|
285
287
|
color: var(--muted);
|
|
286
288
|
margin-bottom: 10px;
|
|
287
289
|
}
|
|
290
|
+
|
|
291
|
+
.reportsHeader {
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: flex-end;
|
|
294
|
+
justify-content: space-between;
|
|
295
|
+
gap: 16px;
|
|
296
|
+
margin-bottom: 12px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.reportsTitle { font-size: 18px; font-weight: 700; }
|
|
300
|
+
.reportsSubtitle { font-size: 12px; color: var(--faint); margin-top: 4px; }
|
|
301
|
+
.reportsActions { display: flex; gap: 10px; }
|
|
302
|
+
|
|
303
|
+
.reportsTools { margin-bottom: 12px; }
|
|
304
|
+
.reportsTools input { width: 100%; }
|
|
305
|
+
|
|
306
|
+
.reportsGrid {
|
|
307
|
+
display: grid;
|
|
308
|
+
gap: 14px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.reportCard {
|
|
312
|
+
border: 1px solid var(--line);
|
|
313
|
+
border-radius: 16px;
|
|
314
|
+
padding: 14px;
|
|
315
|
+
background: var(--paper);
|
|
316
|
+
box-shadow: var(--shadow2);
|
|
317
|
+
display: grid;
|
|
318
|
+
gap: 12px;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.reportHead {
|
|
322
|
+
display: flex;
|
|
323
|
+
justify-content: space-between;
|
|
324
|
+
gap: 14px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.reportName { font-weight: 700; }
|
|
328
|
+
.reportMeta { font-size: 12px; color: var(--faint); margin-top: 4px; }
|
|
329
|
+
.reportHeadActions { display: flex; gap: 8px; align-items: center; }
|
|
330
|
+
|
|
331
|
+
.reportBody { display: grid; gap: 10px; }
|
|
332
|
+
.reportPreview { border: 1px solid var(--line); border-radius: 12px; padding: 12px; background: rgba(0,0,0,.18); }
|
|
333
|
+
.reportRaw { display: none; }
|
|
334
|
+
.reportCard.raw .reportPreview { display: none; }
|
|
335
|
+
.reportCard.raw .reportRaw { display: block; }
|
|
288
336
|
.centerHead { display: flex; justify-content: space-between; align-items: flex-end; gap: 18px; margin-bottom: 14px; }
|
|
289
337
|
.statusLine { display:flex; align-items:center; justify-content:flex-end; gap: 12px; }
|
|
290
338
|
|
package/cli/web-ui.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
lastReportPath: null,
|
|
9
9
|
lastText: '',
|
|
10
10
|
reports: [],
|
|
11
|
+
reportTexts: {},
|
|
12
|
+
reportModes: {},
|
|
11
13
|
selectedReport: null,
|
|
12
14
|
lastPlan: '',
|
|
13
15
|
lastApplied: null,
|
|
@@ -18,24 +20,18 @@
|
|
|
18
20
|
chatLoaded: false
|
|
19
21
|
};
|
|
20
22
|
|
|
21
|
-
function
|
|
22
|
-
document.documentElement.setAttribute('data-theme',
|
|
23
|
-
localStorage.setItem('freya.theme', theme);
|
|
24
|
-
const t = $('themeToggle');
|
|
25
|
-
if (t) t.textContent = theme === 'dark' ? 'Claro' : 'Escuro';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function toggleTheme() {
|
|
29
|
-
const t = localStorage.getItem('freya.theme') || 'dark';
|
|
30
|
-
applyTheme(t === 'dark' ? 'light' : 'dark');
|
|
23
|
+
function applyDarkTheme() {
|
|
24
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
function setPill(kind, text) {
|
|
34
28
|
const dot = $('dot');
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
if (
|
|
29
|
+
const rail = $('railStatus');
|
|
30
|
+
const classes = ['ok', 'err', 'run', 'plan'];
|
|
31
|
+
if (dot) dot.classList.remove(...classes);
|
|
32
|
+
if (rail) rail.classList.remove(...classes);
|
|
33
|
+
if (dot && classes.includes(kind)) dot.classList.add(kind);
|
|
34
|
+
if (rail && classes.includes(kind)) rail.classList.add(kind);
|
|
39
35
|
const pill = $('pill');
|
|
40
36
|
if (pill) pill.textContent = text;
|
|
41
37
|
const status = $('status');
|
|
@@ -378,9 +374,13 @@
|
|
|
378
374
|
const def = (window.__FREYA_DEFAULT_DIR && window.__FREYA_DEFAULT_DIR !== '__FREYA_DEFAULT_DIR__')
|
|
379
375
|
? window.__FREYA_DEFAULT_DIR
|
|
380
376
|
: (localStorage.getItem('freya.dir') || './freya');
|
|
381
|
-
$('dir')
|
|
382
|
-
|
|
383
|
-
|
|
377
|
+
const dirEl = $('dir');
|
|
378
|
+
if (dirEl) {
|
|
379
|
+
dirEl.value = def;
|
|
380
|
+
localStorage.setItem('freya.dir', dirEl.value || './freya');
|
|
381
|
+
}
|
|
382
|
+
const side = $('sidePath');
|
|
383
|
+
if (side) side.textContent = def || './freya';
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
async function api(p, body) {
|
|
@@ -409,8 +409,9 @@
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
function dirOrDefault() {
|
|
412
|
-
const
|
|
413
|
-
|
|
412
|
+
const dEl = $('dir');
|
|
413
|
+
const d = dEl ? dEl.value.trim() : '';
|
|
414
|
+
return d || (localStorage.getItem('freya.dir') || './freya');
|
|
414
415
|
}
|
|
415
416
|
|
|
416
417
|
function fmtWhen(ms) {
|
|
@@ -492,6 +493,116 @@
|
|
|
492
493
|
}
|
|
493
494
|
}
|
|
494
495
|
|
|
496
|
+
function renderReportsPage() {
|
|
497
|
+
const grid = $('reportsGrid');
|
|
498
|
+
if (!grid) return;
|
|
499
|
+
const q = ($('reportsFilter') ? $('reportsFilter').value : '').trim().toLowerCase();
|
|
500
|
+
const list = (state.reports || []).filter((it) => {
|
|
501
|
+
if (!q) return true;
|
|
502
|
+
return (it.name + ' ' + it.kind).toLowerCase().includes(q);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
grid.innerHTML = '';
|
|
506
|
+
for (const item of list) {
|
|
507
|
+
const card = document.createElement('div');
|
|
508
|
+
const mode = state.reportModes[item.relPath] || 'preview';
|
|
509
|
+
card.className = 'reportCard' + (mode === 'raw' ? ' raw' : '');
|
|
510
|
+
|
|
511
|
+
const meta = fmtWhen(item.mtimeMs);
|
|
512
|
+
card.innerHTML =
|
|
513
|
+
'<div class="reportHead">'
|
|
514
|
+
+ '<div>'
|
|
515
|
+
+ '<div class="reportName">' + escapeHtml(item.name) + '</div>'
|
|
516
|
+
+ '<div class="reportMeta">' + escapeHtml(item.relPath) + ' • ' + escapeHtml(meta) + '</div>'
|
|
517
|
+
+ '</div>'
|
|
518
|
+
+ '<div class="reportHeadActions">'
|
|
519
|
+
+ '<button class="btn small" data-action="toggle">' + (mode === 'raw' ? 'Preview' : 'Markdown') + '</button>'
|
|
520
|
+
+ '<button class="btn small primary" data-action="save">Salvar</button>'
|
|
521
|
+
+ '</div>'
|
|
522
|
+
+ '</div>'
|
|
523
|
+
+ '<div class="reportBody">'
|
|
524
|
+
+ '<div class="reportPreview"></div>'
|
|
525
|
+
+ '<textarea class="reportRaw" rows="12"></textarea>'
|
|
526
|
+
+ '</div>';
|
|
527
|
+
|
|
528
|
+
const text = state.reportTexts[item.relPath] || '';
|
|
529
|
+
const preview = card.querySelector('.reportPreview');
|
|
530
|
+
if (preview) preview.innerHTML = renderMarkdown(text || '');
|
|
531
|
+
const raw = card.querySelector('.reportRaw');
|
|
532
|
+
if (raw) raw.value = text;
|
|
533
|
+
|
|
534
|
+
const toggleBtn = card.querySelector('[data-action="toggle"]');
|
|
535
|
+
if (toggleBtn) {
|
|
536
|
+
toggleBtn.onclick = () => {
|
|
537
|
+
state.reportModes[item.relPath] = (state.reportModes[item.relPath] === 'raw') ? 'preview' : 'raw';
|
|
538
|
+
renderReportsPage();
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const saveBtn = card.querySelector('[data-action="save"]');
|
|
543
|
+
if (saveBtn) {
|
|
544
|
+
saveBtn.onclick = async () => {
|
|
545
|
+
try {
|
|
546
|
+
const content = (raw && typeof raw.value === 'string') ? raw.value : '';
|
|
547
|
+
setPill('run', 'salvando…');
|
|
548
|
+
await api('/api/reports/write', { dir: dirOrDefault(), relPath: item.relPath, text: content });
|
|
549
|
+
state.reportTexts[item.relPath] = content;
|
|
550
|
+
setPill('ok', 'salvo');
|
|
551
|
+
setTimeout(() => setPill('ok', 'pronto'), 800);
|
|
552
|
+
renderReportsPage();
|
|
553
|
+
} catch (e) {
|
|
554
|
+
setPill('err', 'falhou');
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
grid.appendChild(card);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async function refreshReportsPage() {
|
|
564
|
+
try {
|
|
565
|
+
setPill('run', 'carregando…');
|
|
566
|
+
const r = await api('/api/reports/list', { dir: dirOrDefault() });
|
|
567
|
+
state.reports = (r.reports || []);
|
|
568
|
+
state.reportTexts = {};
|
|
569
|
+
await Promise.all(state.reports.map(async (item) => {
|
|
570
|
+
try {
|
|
571
|
+
const rr = await api('/api/reports/read', { dir: dirOrDefault(), relPath: item.relPath });
|
|
572
|
+
state.reportTexts[item.relPath] = rr.text || '';
|
|
573
|
+
} catch {
|
|
574
|
+
state.reportTexts[item.relPath] = '';
|
|
575
|
+
}
|
|
576
|
+
}));
|
|
577
|
+
renderReportsPage();
|
|
578
|
+
setPill('ok', 'pronto');
|
|
579
|
+
} catch (e) {
|
|
580
|
+
setPill('err', 'falhou');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function wireRailNav() {
|
|
585
|
+
const dash = $('railDashboard');
|
|
586
|
+
const rep = $('railReports');
|
|
587
|
+
if (dash) {
|
|
588
|
+
dash.onclick = () => {
|
|
589
|
+
const isReports = document.body && document.body.dataset && document.body.dataset.page === 'reports';
|
|
590
|
+
if (isReports) {
|
|
591
|
+
window.location.href = '/';
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const c = document.querySelector('.centerBody');
|
|
595
|
+
if (c) c.scrollTo({ top: 0, behavior: 'smooth' });
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
if (rep) {
|
|
599
|
+
rep.onclick = () => {
|
|
600
|
+
const isReports = document.body && document.body.dataset && document.body.dataset.page === 'reports';
|
|
601
|
+
if (!isReports) window.location.href = '/reports';
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
495
606
|
async function editTask(t) {
|
|
496
607
|
try {
|
|
497
608
|
const currentSlug = t.projectSlug ? String(t.projectSlug) : '';
|
|
@@ -988,10 +1099,11 @@
|
|
|
988
1099
|
}
|
|
989
1100
|
|
|
990
1101
|
// init
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1102
|
+
applyDarkTheme();
|
|
1103
|
+
const chipPort = $('chipPort');
|
|
1104
|
+
if (chipPort) chipPort.textContent = location.host;
|
|
994
1105
|
loadLocal();
|
|
1106
|
+
wireRailNav();
|
|
995
1107
|
|
|
996
1108
|
// Developer drawer (persist open/close)
|
|
997
1109
|
try {
|
|
@@ -1005,23 +1117,34 @@
|
|
|
1005
1117
|
}
|
|
1006
1118
|
} catch {}
|
|
1007
1119
|
|
|
1120
|
+
const isReportsPage = document.body && document.body.dataset && document.body.dataset.page === 'reports';
|
|
1121
|
+
|
|
1008
1122
|
// Load persisted settings from the workspace + bootstrap (auto-init + auto-health)
|
|
1009
1123
|
(async () => {
|
|
1010
1124
|
let defaults = null;
|
|
1011
1125
|
try {
|
|
1012
1126
|
defaults = await api('/api/defaults', { dir: dirOrDefault() });
|
|
1013
1127
|
if (defaults && defaults.workspaceDir) {
|
|
1014
|
-
$('dir')
|
|
1015
|
-
|
|
1128
|
+
const dirEl = $('dir');
|
|
1129
|
+
if (dirEl) dirEl.value = defaults.workspaceDir;
|
|
1130
|
+
const side = $('sidePath');
|
|
1131
|
+
if (side) side.textContent = defaults.workspaceDir;
|
|
1016
1132
|
}
|
|
1017
1133
|
if (defaults && defaults.settings) {
|
|
1018
|
-
$('discord')
|
|
1019
|
-
$('teams')
|
|
1134
|
+
const discord = $('discord');
|
|
1135
|
+
const teams = $('teams');
|
|
1136
|
+
if (discord) discord.value = defaults.settings.discordWebhookUrl || '';
|
|
1137
|
+
if (teams) teams.value = defaults.settings.teamsWebhookUrl || '';
|
|
1020
1138
|
}
|
|
1021
1139
|
} catch (e) {
|
|
1022
1140
|
// ignore
|
|
1023
1141
|
}
|
|
1024
1142
|
|
|
1143
|
+
if (isReportsPage) {
|
|
1144
|
+
await refreshReportsPage();
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1025
1148
|
// If workspace isn't initialized yet, auto-init (reduces clicks)
|
|
1026
1149
|
try {
|
|
1027
1150
|
if (defaults && defaults.workspaceOk === false) {
|
|
@@ -1058,12 +1181,13 @@
|
|
|
1058
1181
|
window.exportObsidian = exportObsidian;
|
|
1059
1182
|
window.rebuildIndex = rebuildIndex;
|
|
1060
1183
|
window.renderReportsList = renderReportsList;
|
|
1184
|
+
window.renderReportsPage = renderReportsPage;
|
|
1185
|
+
window.refreshReportsPage = refreshReportsPage;
|
|
1061
1186
|
window.copyOut = copyOut;
|
|
1062
1187
|
window.copyPath = copyPath;
|
|
1063
1188
|
window.openSelected = openSelected;
|
|
1064
1189
|
window.downloadSelected = downloadSelected;
|
|
1065
1190
|
window.clearOut = clearOut;
|
|
1066
|
-
window.toggleTheme = toggleTheme;
|
|
1067
1191
|
window.saveInbox = saveInbox;
|
|
1068
1192
|
window.saveAndPlan = saveAndPlan;
|
|
1069
1193
|
window.toggleAutoApply = toggleAutoApply;
|
package/cli/web.js
CHANGED
|
@@ -881,6 +881,11 @@ function html(defaultDir) {
|
|
|
881
881
|
return buildHtml(safeDefault, APP_VERSION);
|
|
882
882
|
}
|
|
883
883
|
|
|
884
|
+
function reportsHtml(defaultDir) {
|
|
885
|
+
const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
886
|
+
return buildReportsHtml(safeDefault, APP_VERSION);
|
|
887
|
+
}
|
|
888
|
+
|
|
884
889
|
function buildHtml(safeDefault, appVersion) {
|
|
885
890
|
const safeVersion = escapeHtml(appVersion || 'unknown');
|
|
886
891
|
return `<!doctype html>
|
|
@@ -901,14 +906,11 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
901
906
|
<div class="railLogo">F</div>
|
|
902
907
|
</div>
|
|
903
908
|
<div class="railNav">
|
|
904
|
-
<button class="railBtn active" type="button" title="Dashboard">
|
|
905
|
-
<button class="railBtn" type="button" title="
|
|
906
|
-
<button class="railBtn" type="button" title="Relatórios">R</button>
|
|
907
|
-
<button class="railBtn" type="button" title="Preview">P</button>
|
|
908
|
-
<button class="railBtn" type="button" title="Conversa">C</button>
|
|
909
|
+
<button class="railBtn active" id="railDashboard" type="button" title="Dashboard">D</button>
|
|
910
|
+
<button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
|
|
909
911
|
</div>
|
|
910
912
|
<div class="railBottom">
|
|
911
|
-
<
|
|
913
|
+
<div class="railStatus" id="railStatus" title="status"></div>
|
|
912
914
|
</div>
|
|
913
915
|
</aside>
|
|
914
916
|
|
|
@@ -920,7 +922,6 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
920
922
|
<div class="brand">FREYA</div>
|
|
921
923
|
<div class="brandSub">Assistente de status local-first</div>
|
|
922
924
|
</div>
|
|
923
|
-
<div class="statusPill"><span class="dot" id="dot"></span><span id="pill">pronto</span></div>
|
|
924
925
|
</div>
|
|
925
926
|
<div class="topActions">
|
|
926
927
|
<span class="chip" id="chipVersion">v${safeVersion}</span>
|
|
@@ -954,7 +955,7 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
954
955
|
</div>
|
|
955
956
|
</section>
|
|
956
957
|
|
|
957
|
-
<section class="utilityGrid">
|
|
958
|
+
<section class="utilityGrid" id="reportsSection">
|
|
958
959
|
<div class="utilityCard">
|
|
959
960
|
<div class="utilityHead">Área de trabalho</div>
|
|
960
961
|
<div class="sidePath" id="sidePath">./freya</div>
|
|
@@ -1130,6 +1131,82 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
1130
1131
|
</html>`;
|
|
1131
1132
|
}
|
|
1132
1133
|
|
|
1134
|
+
function buildReportsHtml(safeDefault, appVersion) {
|
|
1135
|
+
const safeVersion = escapeHtml(appVersion || 'unknown');
|
|
1136
|
+
return `<!doctype html>
|
|
1137
|
+
<html>
|
|
1138
|
+
<head>
|
|
1139
|
+
<meta charset="utf-8" />
|
|
1140
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1141
|
+
<title>FREYA Reports</title>
|
|
1142
|
+
<link rel="stylesheet" href="/app.css" />
|
|
1143
|
+
</head>
|
|
1144
|
+
<body data-page="reports">
|
|
1145
|
+
<div class="app">
|
|
1146
|
+
<div class="frame">
|
|
1147
|
+
<div class="shell">
|
|
1148
|
+
|
|
1149
|
+
<aside class="rail">
|
|
1150
|
+
<div class="railTop">
|
|
1151
|
+
<div class="railLogo">F</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
<div class="railNav">
|
|
1154
|
+
<button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
|
|
1155
|
+
<button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
|
|
1156
|
+
</div>
|
|
1157
|
+
<div class="railBottom">
|
|
1158
|
+
<div class="railStatus" id="railStatus" title="status"></div>
|
|
1159
|
+
</div>
|
|
1160
|
+
</aside>
|
|
1161
|
+
|
|
1162
|
+
<main class="center reportsPage" id="reportsPage">
|
|
1163
|
+
<div class="topbar">
|
|
1164
|
+
<div class="brandLine">
|
|
1165
|
+
<span class="spark"></span>
|
|
1166
|
+
<div class="brandStack">
|
|
1167
|
+
<div class="brand">FREYA</div>
|
|
1168
|
+
<div class="brandSub">Relatórios</div>
|
|
1169
|
+
</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
<div class="topActions">
|
|
1172
|
+
<span class="chip" id="chipVersion">v${safeVersion}</span>
|
|
1173
|
+
<span class="chip" id="chipPort">127.0.0.1:3872</span>
|
|
1174
|
+
</div>
|
|
1175
|
+
</div>
|
|
1176
|
+
|
|
1177
|
+
<div class="centerBody">
|
|
1178
|
+
<input id="dir" type="hidden" />
|
|
1179
|
+
|
|
1180
|
+
<section class="reportsHeader">
|
|
1181
|
+
<div>
|
|
1182
|
+
<div class="reportsTitle">Relatórios</div>
|
|
1183
|
+
<div class="reportsSubtitle">Edite e refine seus relatórios com preview em Markdown.</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
<div class="reportsActions">
|
|
1186
|
+
<button class="btn small" type="button" onclick="refreshReportsPage()">Atualizar</button>
|
|
1187
|
+
</div>
|
|
1188
|
+
</section>
|
|
1189
|
+
|
|
1190
|
+
<section class="reportsTools">
|
|
1191
|
+
<input id="reportsFilter" placeholder="filtrar (ex: daily, executive, 2026-01-29)" oninput="renderReportsPage()" />
|
|
1192
|
+
</section>
|
|
1193
|
+
|
|
1194
|
+
<section class="reportsGrid" id="reportsGrid"></section>
|
|
1195
|
+
</div>
|
|
1196
|
+
</main>
|
|
1197
|
+
|
|
1198
|
+
</div>
|
|
1199
|
+
</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
|
|
1202
|
+
<script>
|
|
1203
|
+
window.__FREYA_DEFAULT_DIR = "${safeDefault}";
|
|
1204
|
+
</script>
|
|
1205
|
+
<script src="/app.js"></script>
|
|
1206
|
+
</body>
|
|
1207
|
+
</html>`;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1133
1210
|
function ensureDir(p) {
|
|
1134
1211
|
fs.mkdirSync(p, { recursive: true });
|
|
1135
1212
|
}
|
|
@@ -1316,6 +1393,14 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1316
1393
|
return;
|
|
1317
1394
|
}
|
|
1318
1395
|
|
|
1396
|
+
if (req.method === 'GET' && req.url === '/reports') {
|
|
1397
|
+
try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
|
|
1398
|
+
const body = reportsHtml(dir || './freya');
|
|
1399
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
1400
|
+
res.end(body);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1319
1404
|
if (req.method === 'GET' && req.url === '/app.css') {
|
|
1320
1405
|
const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
|
|
1321
1406
|
res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
@@ -1412,6 +1497,23 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1412
1497
|
return safeJson(res, 200, { relPath: rel, fullPath: full });
|
|
1413
1498
|
}
|
|
1414
1499
|
|
|
1500
|
+
if (req.url === '/api/reports/write') {
|
|
1501
|
+
const rel = payload.relPath;
|
|
1502
|
+
const text = payload.text;
|
|
1503
|
+
if (!rel) return safeJson(res, 400, { error: 'Missing relPath' });
|
|
1504
|
+
if (typeof text !== 'string') return safeJson(res, 400, { error: 'Missing text' });
|
|
1505
|
+
const reportsDir = path.join(workspaceDir, 'docs', 'reports');
|
|
1506
|
+
const full = path.join(workspaceDir, rel);
|
|
1507
|
+
const safeReportsDir = path.resolve(reportsDir);
|
|
1508
|
+
const safeFull = path.resolve(full);
|
|
1509
|
+
if (!safeFull.startsWith(safeReportsDir + path.sep)) {
|
|
1510
|
+
return safeJson(res, 400, { error: 'Invalid report path' });
|
|
1511
|
+
}
|
|
1512
|
+
if (!exists(safeFull)) return safeJson(res, 404, { error: 'Report not found' });
|
|
1513
|
+
fs.writeFileSync(safeFull, text, 'utf8');
|
|
1514
|
+
return safeJson(res, 200, { ok: true, relPath: rel });
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1415
1517
|
if (req.url === '/api/inbox/add') {
|
|
1416
1518
|
const text = String(payload.text || '').trim();
|
|
1417
1519
|
if (!text) return safeJson(res, 400, { error: 'Missing text' });
|