pyre-cli 10.1.0 → 10.2.0
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/README.md +195 -494
- package/dist/{checkCmd-RKW6QGDR.js → checkCmd-WQAHAUWO.js} +1 -1
- package/dist/{chunk-GHRDW646.js → chunk-23YWCI3O.js} +375 -135
- package/dist/{service-Q74BBILO.js → chunk-KFPTD266.js} +43 -3
- package/dist/{doctor-GYHFTNZQ.js → chunk-KVUMOA6C.js} +7 -6
- package/dist/{chunk-53O2RGSL.js → chunk-QM5L43EK.js} +19 -2
- package/dist/{chunk-6UHZP4QP.js → chunk-XTOOEKLM.js} +24 -12
- package/dist/{collectors-H43PN4PS.js → collectors-JDN5Y7EV.js} +1 -1
- package/dist/{completions-JKYEJNGR.js → completions-ZVTNE52B.js} +15 -11
- package/dist/{config-GYGJXU2Q.js → config-J4YMADEF.js} +3 -1
- package/dist/doctor-LHFW4UJP.js +10 -0
- package/dist/firebaseSync-ON3RZ6KX.js +91 -0
- package/dist/index.js +4243 -735
- package/dist/monitors/platform/windows-ui.ps1 +42 -0
- package/dist/{netUsageCmd-WKBTW2YG.js → netUsageCmd-67IBZZYN.js} +2 -2
- package/dist/{pipeCmd-7REDBLBU.js → pipeCmd-2N4JPKYE.js} +1 -1
- package/dist/{prometheusCmd-PL64BLN5.js → prometheusCmd-LEUM5WCZ.js} +1 -1
- package/dist/serveCmd-L7LVJOZN.js +353 -0
- package/dist/service-7GIX3YNC.js +16 -0
- package/dist/syncCmd-42IRF633.js +246 -0
- package/dist/{topoCmd-DU2HHZNH.js → topoCmd-DZ3YS5IL.js} +1 -1
- package/dist/{watchdogCmd-UF53JPZB.js → watchdogCmd-6GR4UN6B.js} +7 -7
- package/dist/{wizard-TD7BGX6O.js → wizard-JQMQWFAW.js} +1 -1
- package/package.json +4 -3
- package/parity.md +2 -0
- package/src/completions.ts +15 -11
- package/src/easy_features.test.ts +149 -0
- package/src/firebaseSync.ts +157 -0
- package/src/formatters/html_template.ts +3086 -598
- package/src/formatters/render.ts +25 -12
- package/src/index.ts +516 -23
- package/src/live/input.ts +501 -148
- package/src/live/render.ts +163 -18
- package/src/live/state.ts +52 -2
- package/src/live/types.ts +1 -1
- package/src/monitors/collectors.ts +3 -10
- package/src/monitors/platform/windows-ui.ps1 +42 -0
- package/src/monitors/platform/windows.ts +442 -147
- package/src/monitors/types.ts +8 -0
- package/src/serveCmd.ts +17 -2
- package/src/service.ts +49 -0
- package/src/state/config.ts +48 -0
- package/src/syncCmd.ts +159 -11
- package/src/web/dashboard.ts +205 -0
- package/dist/serveCmd-2H5VLF3P.js +0 -153
- package/dist/syncCmd-A2W3QB3S.js +0 -126
|
@@ -33,6 +33,43 @@ function uninstallService() {
|
|
|
33
33
|
console.log(chalk.yellow(`\u26A0 Background service uninstallation not supported on ${platform}.`));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
function isServiceInstalled() {
|
|
37
|
+
const platform = process.platform;
|
|
38
|
+
if (platform === "darwin") {
|
|
39
|
+
return fs.existsSync(PLIST_PATH);
|
|
40
|
+
} else if (platform === "linux") {
|
|
41
|
+
return fs.existsSync(SYSTEMD_UNIT_PATH);
|
|
42
|
+
} else if (platform === "win32") {
|
|
43
|
+
try {
|
|
44
|
+
execSync(`schtasks /query /tn "${TASK_NAME}"`, { stdio: "pipe" });
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function getServiceStatus() {
|
|
53
|
+
const platform = process.platform;
|
|
54
|
+
const installed = isServiceInstalled();
|
|
55
|
+
let serviceLabel = SERVICE_LABEL;
|
|
56
|
+
let servicePath = PLIST_PATH;
|
|
57
|
+
const logPath = path.join(os.homedir(), ".config", "pyre", "web.log");
|
|
58
|
+
if (platform === "linux") {
|
|
59
|
+
serviceLabel = "pyre-web.service";
|
|
60
|
+
servicePath = SYSTEMD_UNIT_PATH;
|
|
61
|
+
} else if (platform === "win32") {
|
|
62
|
+
serviceLabel = TASK_NAME;
|
|
63
|
+
servicePath = "Windows Task Scheduler (onlogon)";
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
installed,
|
|
67
|
+
platform,
|
|
68
|
+
serviceLabel,
|
|
69
|
+
path: servicePath,
|
|
70
|
+
logPath
|
|
71
|
+
};
|
|
72
|
+
}
|
|
36
73
|
var installLaunchdAgent = installService;
|
|
37
74
|
var uninstallLaunchdAgent = uninstallService;
|
|
38
75
|
function installLaunchd(port) {
|
|
@@ -173,9 +210,12 @@ function uninstallWindowsTask() {
|
|
|
173
210
|
console.log(chalk.yellow(` No scheduled task '${TASK_NAME}' found or deletion failed: ${err.message}`));
|
|
174
211
|
}
|
|
175
212
|
}
|
|
213
|
+
|
|
176
214
|
export {
|
|
177
|
-
installLaunchdAgent,
|
|
178
215
|
installService,
|
|
179
|
-
|
|
180
|
-
|
|
216
|
+
uninstallService,
|
|
217
|
+
isServiceInstalled,
|
|
218
|
+
getServiceStatus,
|
|
219
|
+
installLaunchdAgent,
|
|
220
|
+
uninstallLaunchdAgent
|
|
181
221
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CONFIG_FILE,
|
|
3
|
-
readConfig
|
|
4
|
-
} from "./chunk-53O2RGSL.js";
|
|
5
1
|
import {
|
|
6
2
|
run
|
|
7
3
|
} from "./chunk-MZO5QKUR.js";
|
|
4
|
+
import {
|
|
5
|
+
CONFIG_FILE,
|
|
6
|
+
readConfig
|
|
7
|
+
} from "./chunk-QM5L43EK.js";
|
|
8
8
|
|
|
9
9
|
// src/doctor.ts
|
|
10
10
|
import fs from "fs";
|
|
@@ -381,7 +381,8 @@ function printDoctorReport(checks) {
|
|
|
381
381
|
}
|
|
382
382
|
console.log();
|
|
383
383
|
}
|
|
384
|
+
|
|
384
385
|
export {
|
|
385
|
-
|
|
386
|
-
|
|
386
|
+
runDoctor,
|
|
387
|
+
printDoctorReport
|
|
387
388
|
};
|
|
@@ -36,6 +36,8 @@ var DEFAULT_CONFIG = {
|
|
|
36
36
|
aiModel: "expert-rules-v1",
|
|
37
37
|
aiApiKey: "",
|
|
38
38
|
dockerModeConfirmed: false,
|
|
39
|
+
firebaseSyncEnabled: false,
|
|
40
|
+
firebaseHostId: "",
|
|
39
41
|
visiblePanels: {
|
|
40
42
|
cpu: true,
|
|
41
43
|
mem: true,
|
|
@@ -52,7 +54,17 @@ var DEFAULT_CONFIG = {
|
|
|
52
54
|
blender: true,
|
|
53
55
|
tree: false
|
|
54
56
|
},
|
|
55
|
-
panelLayout: ["mem", "disk", "net"]
|
|
57
|
+
panelLayout: ["mem", "disk", "net"],
|
|
58
|
+
bookmarks: [],
|
|
59
|
+
graphZoomLevel: 1,
|
|
60
|
+
compact: false,
|
|
61
|
+
alwaysOnTop: false,
|
|
62
|
+
exposeLan: false,
|
|
63
|
+
port: 3e3,
|
|
64
|
+
historyDuration: 60,
|
|
65
|
+
tableDensity: "standard",
|
|
66
|
+
defaultFilter: "all",
|
|
67
|
+
defaultSort: "cpu"
|
|
56
68
|
};
|
|
57
69
|
function getConfigDir() {
|
|
58
70
|
return process.env.PYRE_CONFIG_DIR || path.join(os.homedir(), ".config", "pyre");
|
|
@@ -133,6 +145,10 @@ function deepMerge(target, source) {
|
|
|
133
145
|
function getConfigPath() {
|
|
134
146
|
return getConfigFile();
|
|
135
147
|
}
|
|
148
|
+
function resetConfig() {
|
|
149
|
+
writeConfig(DEFAULT_CONFIG);
|
|
150
|
+
return { ...DEFAULT_CONFIG };
|
|
151
|
+
}
|
|
136
152
|
|
|
137
153
|
export {
|
|
138
154
|
DEFAULT_CONFIG,
|
|
@@ -147,5 +163,6 @@ export {
|
|
|
147
163
|
saveProfile,
|
|
148
164
|
loadProfile,
|
|
149
165
|
listProfiles,
|
|
150
|
-
getConfigPath
|
|
166
|
+
getConfigPath,
|
|
167
|
+
resetConfig
|
|
151
168
|
};
|
|
@@ -580,10 +580,11 @@ var TAB_DEFS = [
|
|
|
580
580
|
{ id: "power", label: "Power", key: "4" },
|
|
581
581
|
{ id: "battery", label: "Battery", key: "5" },
|
|
582
582
|
{ id: "thermal", label: "Thermal", key: "6" },
|
|
583
|
-
{ id: "
|
|
584
|
-
{ id: "
|
|
585
|
-
{ id: "
|
|
586
|
-
{ id: "
|
|
583
|
+
{ id: "network", label: "Network", key: "7" },
|
|
584
|
+
{ id: "packets", label: "Conns", key: "8" },
|
|
585
|
+
{ id: "tasks", label: "Tasks", key: "9" },
|
|
586
|
+
{ id: "disk", label: "Disk", key: "0" },
|
|
587
|
+
{ id: "process", label: "Process", key: "P" },
|
|
587
588
|
{ id: "p2p", label: "P2P", key: "R" },
|
|
588
589
|
{ id: "anomalies", label: "Anomalies", key: "A" },
|
|
589
590
|
{ id: "containers", label: "Containers", key: "C" },
|
|
@@ -591,22 +592,24 @@ var TAB_DEFS = [
|
|
|
591
592
|
];
|
|
592
593
|
var TAB_BAR_INDENT = 2;
|
|
593
594
|
var TAB_SEPARATOR_WIDTH = 3;
|
|
594
|
-
function getTabHitboxes() {
|
|
595
|
+
function getTabHitboxes(visible) {
|
|
596
|
+
const tabs = visible ? TAB_DEFS.filter((t) => visible[t.id] !== false) : TAB_DEFS;
|
|
595
597
|
const boxes = [];
|
|
596
598
|
let col = TAB_BAR_INDENT;
|
|
597
|
-
|
|
599
|
+
tabs.forEach((t, i) => {
|
|
598
600
|
const text = `[${t.key}]${t.label}`;
|
|
599
601
|
const start = col;
|
|
600
602
|
const end = col + text.length;
|
|
601
603
|
boxes.push({ id: t.id, start, end });
|
|
602
604
|
col = end;
|
|
603
|
-
if (i <
|
|
605
|
+
if (i < tabs.length - 1) col += TAB_SEPARATOR_WIDTH;
|
|
604
606
|
});
|
|
605
607
|
return boxes;
|
|
606
608
|
}
|
|
607
609
|
var TAB_BAR_ROW = 4;
|
|
608
|
-
function tabBar(activePanel, width, theme) {
|
|
609
|
-
const
|
|
610
|
+
function tabBar(activePanel, width, theme, visible) {
|
|
611
|
+
const visibleTabs = visible ? TAB_DEFS.filter((t) => visible[t.id] !== false) : TAB_DEFS;
|
|
612
|
+
const rendered = visibleTabs.map((t) => {
|
|
610
613
|
const isActive = activePanel === t.id;
|
|
611
614
|
const label = isActive ? chalk2.bold(t.label) : chalk2.dim(t.label);
|
|
612
615
|
const keyHint = chalk2.dim(`[${t.key}]`);
|
|
@@ -1006,7 +1009,12 @@ function btopCpuBox(data, width, theme, opts) {
|
|
|
1006
1009
|
function btopLeftColumn(data, width, theme, opts) {
|
|
1007
1010
|
const contentWidth = width - 4;
|
|
1008
1011
|
const out = [];
|
|
1009
|
-
const
|
|
1012
|
+
const rawLayout = opts.panelLayout || ["mem", "disk", "net"];
|
|
1013
|
+
const visible = opts.visible || {};
|
|
1014
|
+
const layout = rawLayout.filter((name) => {
|
|
1015
|
+
const key = name === "net" ? "network" : name;
|
|
1016
|
+
return visible[key] !== false;
|
|
1017
|
+
});
|
|
1010
1018
|
for (const panelName of layout) {
|
|
1011
1019
|
if (panelName === "mem") {
|
|
1012
1020
|
const memLines = [];
|
|
@@ -1128,7 +1136,11 @@ function btopProcBox(data, width, targetHeight, theme, opts) {
|
|
|
1128
1136
|
}
|
|
1129
1137
|
function formatTable(data, opts = {}) {
|
|
1130
1138
|
const width = clampWidth(opts.width);
|
|
1131
|
-
|
|
1139
|
+
let activePanel = opts.activePanel || "grid";
|
|
1140
|
+
const visible = opts.visible || {};
|
|
1141
|
+
if (activePanel !== "grid" && visible[activePanel] === false) {
|
|
1142
|
+
activePanel = "grid";
|
|
1143
|
+
}
|
|
1132
1144
|
const themeName = opts.theme || "default";
|
|
1133
1145
|
const theme = THEMES[themeName] || THEMES.default;
|
|
1134
1146
|
const out = [];
|
|
@@ -1139,7 +1151,7 @@ function formatTable(data, opts = {}) {
|
|
|
1139
1151
|
out.push(...anomalyPanel(anomalies, width, theme));
|
|
1140
1152
|
out.push("");
|
|
1141
1153
|
}
|
|
1142
|
-
out.push(tabBar(activePanel, width, theme));
|
|
1154
|
+
out.push(tabBar(activePanel, width, theme, opts.visible));
|
|
1143
1155
|
out.push("");
|
|
1144
1156
|
if (opts.minimal) {
|
|
1145
1157
|
const contentWidth = width - 4;
|
|
@@ -9,6 +9,8 @@ _pyre() {
|
|
|
9
9
|
'info:Display static system overview'
|
|
10
10
|
'doctor:Run system permissions and diagnostic checks'
|
|
11
11
|
'web:Launch web dashboard server'
|
|
12
|
+
'ui:Launch Activity Monitor UI window'
|
|
13
|
+
'launch:Launch Activity Monitor UI window'
|
|
12
14
|
'config:Show or reset configuration options'
|
|
13
15
|
'p2p:P2P streaming server or client'
|
|
14
16
|
'ssh:Stream metrics over SSH'
|
|
@@ -56,38 +58,38 @@ _pyre_completions() {
|
|
|
56
58
|
COMPREPLY=()
|
|
57
59
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
58
60
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
59
|
-
commands="live info doctor web config p2p ssh bench completions"
|
|
61
|
+
commands="live info doctor web ui launch config p2p ssh bench completions"
|
|
60
62
|
opts="--json --html --md --csv --tsv --detailed --theme --interval --once --out --export-dir --log --tree --sort --packets --limit --alert-cpu --alert-temp --temp-unit"
|
|
61
63
|
|
|
62
|
-
case "
|
|
64
|
+
case "${prev}" in
|
|
63
65
|
--theme)
|
|
64
|
-
COMPREPLY=( $(compgen -W "default dracula cyberpunk monochrome nord gruvbox" --
|
|
66
|
+
COMPREPLY=( $(compgen -W "default dracula cyberpunk monochrome nord gruvbox" -- ${cur}) )
|
|
65
67
|
return 0
|
|
66
68
|
;;
|
|
67
69
|
--sort)
|
|
68
|
-
COMPREPLY=( $(compgen -W "cpu mem pid user command state threads runtime" --
|
|
70
|
+
COMPREPLY=( $(compgen -W "cpu mem pid user command state threads runtime" -- ${cur}) )
|
|
69
71
|
return 0
|
|
70
72
|
;;
|
|
71
73
|
--temp-unit)
|
|
72
|
-
COMPREPLY=( $(compgen -W "c f" --
|
|
74
|
+
COMPREPLY=( $(compgen -W "c f" -- ${cur}) )
|
|
73
75
|
return 0
|
|
74
76
|
;;
|
|
75
77
|
config)
|
|
76
|
-
COMPREPLY=( $(compgen -W "show reset" --
|
|
78
|
+
COMPREPLY=( $(compgen -W "show reset" -- ${cur}) )
|
|
77
79
|
return 0
|
|
78
80
|
;;
|
|
79
81
|
p2p)
|
|
80
|
-
COMPREPLY=( $(compgen -W "server connect" --
|
|
82
|
+
COMPREPLY=( $(compgen -W "server connect" -- ${cur}) )
|
|
81
83
|
return 0
|
|
82
84
|
;;
|
|
83
85
|
esac
|
|
84
86
|
|
|
85
|
-
if [[
|
|
86
|
-
COMPREPLY=( $(compgen -W "
|
|
87
|
+
if [[ ${cur} == -* ]] ; then
|
|
88
|
+
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
87
89
|
return 0
|
|
88
90
|
fi
|
|
89
91
|
|
|
90
|
-
COMPREPLY=( $(compgen -W "
|
|
92
|
+
COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
complete -F _pyre_completions pyre
|
|
@@ -100,6 +102,8 @@ complete -c pyre -n "__fish_use_subcommand" -a live -d "Start interactive live m
|
|
|
100
102
|
complete -c pyre -n "__fish_use_subcommand" -a info -d "Display static system overview"
|
|
101
103
|
complete -c pyre -n "__fish_use_subcommand" -a doctor -d "Run system diagnostics"
|
|
102
104
|
complete -c pyre -n "__fish_use_subcommand" -a web -d "Launch web dashboard server"
|
|
105
|
+
complete -c pyre -n "__fish_use_subcommand" -a ui -d "Launch Activity Monitor UI window"
|
|
106
|
+
complete -c pyre -n "__fish_use_subcommand" -a launch -d "Launch Activity Monitor UI window"
|
|
103
107
|
complete -c pyre -n "__fish_use_subcommand" -a config -d "Show or reset configuration options"
|
|
104
108
|
complete -c pyre -n "__fish_use_subcommand" -a p2p -d "P2P streaming mode"
|
|
105
109
|
complete -c pyre -n "__fish_use_subcommand" -a completions -d "Generate shell completions"
|
|
@@ -119,7 +123,7 @@ function generatePowerShellCompletions() {
|
|
|
119
123
|
return `# PowerShell completion for pyre
|
|
120
124
|
Register-ArgumentCompleter -Native -CommandName pyre -ScriptBlock {
|
|
121
125
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
122
|
-
$commands = @('live', 'check', 'pipe', 'stress', 'ui', 'web', 'ssh', 'fleet', 'bench', 'benchmark', 'anomalies', 'doctor', 'extensions', 'brew', 'update', 'profile', 'config', 'history', 'diff', 'info', 'completions', 'p2p', 'blender', 'prometheus', 'smart', 'replay', 'explain')
|
|
126
|
+
$commands = @('live', 'check', 'pipe', 'stress', 'ui', 'launch', 'web', 'ssh', 'fleet', 'bench', 'benchmark', 'anomalies', 'doctor', 'extensions', 'brew', 'update', 'profile', 'config', 'history', 'diff', 'info', 'completions', 'p2p', 'blender', 'prometheus', 'smart', 'replay', 'explain')
|
|
123
127
|
$options = @('--json', '--html', '--md', '--csv', '--tsv', '--detailed', '--theme', '--interval', '--once', '--out', '--export-dir', '--log', '--tree', '--sort', '--packets', '--limit', '--alert-cpu', '--alert-temp', '--temp-unit', '--plain', '--help', '--version')
|
|
124
128
|
|
|
125
129
|
$elements = $commandAst.CommandElements
|
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
listProfiles,
|
|
11
11
|
loadProfile,
|
|
12
12
|
readConfig,
|
|
13
|
+
resetConfig,
|
|
13
14
|
saveProfile,
|
|
14
15
|
writeConfig
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-QM5L43EK.js";
|
|
16
17
|
export {
|
|
17
18
|
CONFIG_DIR,
|
|
18
19
|
CONFIG_FILE,
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
listProfiles,
|
|
26
27
|
loadProfile,
|
|
27
28
|
readConfig,
|
|
29
|
+
resetConfig,
|
|
28
30
|
saveProfile,
|
|
29
31
|
writeConfig
|
|
30
32
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/firebaseSync.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { initializeApp, getApps } from "firebase/app";
|
|
5
|
+
import {
|
|
6
|
+
getFirestore,
|
|
7
|
+
doc,
|
|
8
|
+
setDoc,
|
|
9
|
+
getDoc,
|
|
10
|
+
getDocs,
|
|
11
|
+
collection,
|
|
12
|
+
query,
|
|
13
|
+
orderBy,
|
|
14
|
+
limit
|
|
15
|
+
} from "firebase/firestore";
|
|
16
|
+
var FIREBASE_CONFIG = {
|
|
17
|
+
apiKey: "AIzaSyAA86V_wRGJpzc27ZHZd1OBIt4Q-72ASdI",
|
|
18
|
+
authDomain: "dearborn-internal-tool.firebaseapp.com",
|
|
19
|
+
projectId: "dearborn-internal-tool",
|
|
20
|
+
storageBucket: "dearborn-internal-tool.firebasestorage.app",
|
|
21
|
+
messagingSenderId: "77553447918",
|
|
22
|
+
appId: "1:77553447918:web:5c9128660ceeb087c794ff"
|
|
23
|
+
};
|
|
24
|
+
var COLLECTION = "pyre-sync";
|
|
25
|
+
var _db = null;
|
|
26
|
+
function getDb() {
|
|
27
|
+
if (_db) return _db;
|
|
28
|
+
const app = getApps().length ? getApps()[0] : initializeApp(FIREBASE_CONFIG);
|
|
29
|
+
_db = getFirestore(app);
|
|
30
|
+
return _db;
|
|
31
|
+
}
|
|
32
|
+
function computeChecksum(config, profiles) {
|
|
33
|
+
const hash = crypto.createHash("sha256");
|
|
34
|
+
hash.update(JSON.stringify(config));
|
|
35
|
+
hash.update(JSON.stringify(profiles));
|
|
36
|
+
return hash.digest("hex").slice(0, 16);
|
|
37
|
+
}
|
|
38
|
+
function resolveHostId(hostId) {
|
|
39
|
+
return (hostId || os.hostname()).replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
40
|
+
}
|
|
41
|
+
async function firebasePush(config, profiles, hostId) {
|
|
42
|
+
const db = getDb();
|
|
43
|
+
const id = resolveHostId(hostId);
|
|
44
|
+
const checksum = computeChecksum(config, profiles);
|
|
45
|
+
const lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
46
|
+
const payload = {
|
|
47
|
+
version: 1,
|
|
48
|
+
lastUpdated,
|
|
49
|
+
sourceHost: os.hostname(),
|
|
50
|
+
checksum,
|
|
51
|
+
profilesCount: Object.keys(profiles).length,
|
|
52
|
+
config,
|
|
53
|
+
profiles
|
|
54
|
+
};
|
|
55
|
+
await setDoc(doc(db, COLLECTION, id), payload);
|
|
56
|
+
return { hostId: id, checksum, lastUpdated };
|
|
57
|
+
}
|
|
58
|
+
async function firebasePull(hostId) {
|
|
59
|
+
const db = getDb();
|
|
60
|
+
if (hostId) {
|
|
61
|
+
const id = resolveHostId(hostId);
|
|
62
|
+
const snap = await getDoc(doc(db, COLLECTION, id));
|
|
63
|
+
if (!snap.exists()) return null;
|
|
64
|
+
return { doc: snap.data(), hostId: id };
|
|
65
|
+
}
|
|
66
|
+
const q = query(collection(db, COLLECTION), orderBy("lastUpdated", "desc"), limit(1));
|
|
67
|
+
const snaps = await getDocs(q);
|
|
68
|
+
if (snaps.empty) return null;
|
|
69
|
+
const first = snaps.docs[0];
|
|
70
|
+
return { doc: first.data(), hostId: first.id };
|
|
71
|
+
}
|
|
72
|
+
async function firebaseStatus() {
|
|
73
|
+
const db = getDb();
|
|
74
|
+
const q = query(collection(db, COLLECTION), orderBy("lastUpdated", "desc"));
|
|
75
|
+
const snaps = await getDocs(q);
|
|
76
|
+
return snaps.docs.map((d) => {
|
|
77
|
+
const data = d.data();
|
|
78
|
+
return {
|
|
79
|
+
hostId: d.id,
|
|
80
|
+
sourceHost: data.sourceHost,
|
|
81
|
+
lastUpdated: data.lastUpdated,
|
|
82
|
+
profilesCount: data.profilesCount,
|
|
83
|
+
checksum: data.checksum
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
firebasePull,
|
|
89
|
+
firebasePush,
|
|
90
|
+
firebaseStatus
|
|
91
|
+
};
|