@yiaany/ghostapi 0.1.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/CHANGELOG.md +29 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +354 -0
- package/SECURITY.md +37 -0
- package/dist/agents/agentPrompt.d.ts +5 -0
- package/dist/agents/agentPrompt.js +30 -0
- package/dist/agents/agentPrompt.js.map +1 -0
- package/dist/ai/aiClient.d.ts +7 -0
- package/dist/ai/aiClient.js +30 -0
- package/dist/ai/aiClient.js.map +1 -0
- package/dist/ai/aiGenerator.d.ts +10 -0
- package/dist/ai/aiGenerator.js +101 -0
- package/dist/ai/aiGenerator.js.map +1 -0
- package/dist/ai/genericInference.d.ts +5 -0
- package/dist/ai/genericInference.js +80 -0
- package/dist/ai/genericInference.js.map +1 -0
- package/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.js +2 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/jsonRepair.d.ts +2 -0
- package/dist/ai/jsonRepair.js +31 -0
- package/dist/ai/jsonRepair.js.map +1 -0
- package/dist/ai/prompts.d.ts +4 -0
- package/dist/ai/prompts.js +81 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/behavior/behaviorStore.d.ts +12 -0
- package/dist/behavior/behaviorStore.js +72 -0
- package/dist/behavior/behaviorStore.js.map +1 -0
- package/dist/cache/index.d.ts +9 -0
- package/dist/cache/index.js +34 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cli/errors.d.ts +4 -0
- package/dist/cli/errors.js +9 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +277 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parser.d.ts +56 -0
- package/dist/cli/parser.js +169 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/config/localConfig.d.ts +16 -0
- package/dist/config/localConfig.js +58 -0
- package/dist/config/localConfig.js.map +1 -0
- package/dist/config/serverConfig.d.ts +13 -0
- package/dist/config/serverConfig.js +44 -0
- package/dist/config/serverConfig.js.map +1 -0
- package/dist/dashboard/app.js +615 -0
- package/dist/dashboard/dashboard.d.ts +4 -0
- package/dist/dashboard/dashboard.js +20 -0
- package/dist/dashboard/dashboard.js.map +1 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.html +298 -0
- package/dist/dashboard/index.js +2 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/styles.css +349 -0
- package/dist/errors/errorEngine.d.ts +3 -0
- package/dist/errors/errorEngine.js +65 -0
- package/dist/errors/errorEngine.js.map +1 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/providerErrors.d.ts +3 -0
- package/dist/errors/providerErrors.js +6 -0
- package/dist/errors/providerErrors.js.map +1 -0
- package/dist/fault/faultLab.d.ts +27 -0
- package/dist/fault/faultLab.js +89 -0
- package/dist/fault/faultLab.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/landing/assets/index-BXR41fAD.css +1 -0
- package/dist/landing/assets/index-D8_ggMRE.js +55 -0
- package/dist/landing/index.html +15 -0
- package/dist/landing/landing.d.ts +3 -0
- package/dist/landing/landing.js +12 -0
- package/dist/landing/landing.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +52 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/discord.d.ts +2 -0
- package/dist/providers/discord.js +11 -0
- package/dist/providers/discord.js.map +1 -0
- package/dist/providers/generic.d.ts +2 -0
- package/dist/providers/generic.js +13 -0
- package/dist/providers/generic.js.map +1 -0
- package/dist/providers/github.d.ts +2 -0
- package/dist/providers/github.js +11 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +2 -0
- package/dist/providers/openai.js +15 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +12 -0
- package/dist/providers/registry.js +23 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/resend.d.ts +2 -0
- package/dist/providers/resend.js +12 -0
- package/dist/providers/resend.js.map +1 -0
- package/dist/providers/stripe.d.ts +2 -0
- package/dist/providers/stripe.js +15 -0
- package/dist/providers/stripe.js.map +1 -0
- package/dist/providers/twilio.d.ts +2 -0
- package/dist/providers/twilio.js +14 -0
- package/dist/providers/twilio.js.map +1 -0
- package/dist/providers/types.d.ts +13 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/proxy/cacheKey.d.ts +2 -0
- package/dist/proxy/cacheKey.js +37 -0
- package/dist/proxy/cacheKey.js.map +1 -0
- package/dist/proxy/providerDetector.d.ts +11 -0
- package/dist/proxy/providerDetector.js +74 -0
- package/dist/proxy/providerDetector.js.map +1 -0
- package/dist/proxy/proxyHandler.d.ts +3 -0
- package/dist/proxy/proxyHandler.js +114 -0
- package/dist/proxy/proxyHandler.js.map +1 -0
- package/dist/proxy/requestNormalizer.d.ts +11 -0
- package/dist/proxy/requestNormalizer.js +30 -0
- package/dist/proxy/requestNormalizer.js.map +1 -0
- package/dist/report/safetyReport.d.ts +13 -0
- package/dist/report/safetyReport.js +65 -0
- package/dist/report/safetyReport.js.map +1 -0
- package/dist/rules/aiRules.d.ts +7 -0
- package/dist/rules/aiRules.js +31 -0
- package/dist/rules/aiRules.js.map +1 -0
- package/dist/scenarios/scenarioStore.d.ts +37 -0
- package/dist/scenarios/scenarioStore.js +201 -0
- package/dist/scenarios/scenarioStore.js.map +1 -0
- package/dist/security/headerSanitizer.d.ts +3 -0
- package/dist/security/headerSanitizer.js +12 -0
- package/dist/security/headerSanitizer.js.map +1 -0
- package/dist/security/maskSecrets.d.ts +1 -0
- package/dist/security/maskSecrets.js +2 -0
- package/dist/security/maskSecrets.js.map +1 -0
- package/dist/security/secrets.d.ts +4 -0
- package/dist/security/secrets.js +61 -0
- package/dist/security/secrets.js.map +1 -0
- package/dist/server/createServer.d.ts +8 -0
- package/dist/server/createServer.js +42 -0
- package/dist/server/createServer.js.map +1 -0
- package/dist/server/eventsStore.d.ts +17 -0
- package/dist/server/eventsStore.js +31 -0
- package/dist/server/eventsStore.js.map +1 -0
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.js +189 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/sse.d.ts +5 -0
- package/dist/server/sse.js +32 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/setup/sdkDetector.d.ts +9 -0
- package/dist/setup/sdkDetector.js +31 -0
- package/dist/setup/sdkDetector.js.map +1 -0
- package/dist/setup/setupGenerator.d.ts +28 -0
- package/dist/setup/setupGenerator.js +146 -0
- package/dist/setup/setupGenerator.js.map +1 -0
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +2 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/stateExtractor.d.ts +3 -0
- package/dist/state/stateExtractor.js +36 -0
- package/dist/state/stateExtractor.js.map +1 -0
- package/dist/state/stateResolver.d.ts +8 -0
- package/dist/state/stateResolver.js +95 -0
- package/dist/state/stateResolver.js.map +1 -0
- package/dist/state/stateStore.d.ts +4 -0
- package/dist/state/stateStore.js +72 -0
- package/dist/state/stateStore.js.map +1 -0
- package/dist/tests/testGenerator.d.ts +6 -0
- package/dist/tests/testGenerator.js +33 -0
- package/dist/tests/testGenerator.js.map +1 -0
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.js +10 -0
- package/dist/utils/json.js.map +1 -0
- package/docs/release-checklist.md +71 -0
- package/examples/agent-instructions/README.md +39 -0
- package/examples/generic-rest/README.md +27 -0
- package/examples/github-fetch/README.md +35 -0
- package/examples/resend-node/README.md +22 -0
- package/examples/stripe-node/README.md +29 -0
- package/examples/twilio-node/README.md +21 -0
- package/package.json +67 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readLocalConfigSync } from "./localConfig.js";
|
|
2
|
+
export const DEFAULT_HOST = "127.0.0.1";
|
|
3
|
+
export const DEFAULT_PORT = 8080;
|
|
4
|
+
export const DEFAULT_MODEL = "gpt-4o-mini";
|
|
5
|
+
export function loadServerConfig(env = process.env, args = process.argv, overrides = {}) {
|
|
6
|
+
const localConfig = readLocalConfigSync();
|
|
7
|
+
const offline = parseBoolean(overrides.offline, parseBooleanEnv(env.GHOSTAPI_OFFLINE, localConfig.offline ?? false));
|
|
8
|
+
return {
|
|
9
|
+
host: overrides.host ?? env.GHOSTAPI_HOST ?? localConfig.host ?? DEFAULT_HOST,
|
|
10
|
+
port: overrides.port ?? parsePort(env.GHOSTAPI_PORT, localConfig.port),
|
|
11
|
+
model: overrides.model ?? parseModel(env.GHOSTAPI_MODEL, args, localConfig.model),
|
|
12
|
+
offline,
|
|
13
|
+
https: parseBoolean(overrides.https, parseBooleanEnv(env.GHOSTAPI_HTTPS, localConfig.https ?? false)),
|
|
14
|
+
apiKey: offline ? undefined : env.GHOSTAPI_LLM_API_KEY ?? env.OPENAI_API_KEY
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function parseModel(envModel, args, configModel) {
|
|
18
|
+
const modelIndex = args.indexOf("--model");
|
|
19
|
+
if (modelIndex !== -1 && args[modelIndex + 1]) {
|
|
20
|
+
return args[modelIndex + 1];
|
|
21
|
+
}
|
|
22
|
+
if (envModel && envModel.trim() !== "")
|
|
23
|
+
return envModel;
|
|
24
|
+
return configModel && configModel.trim() !== "" ? configModel : DEFAULT_MODEL;
|
|
25
|
+
}
|
|
26
|
+
function parsePort(value, configPort) {
|
|
27
|
+
if (value === undefined || value.trim() === "") {
|
|
28
|
+
return configPort ?? DEFAULT_PORT;
|
|
29
|
+
}
|
|
30
|
+
const port = Number(value);
|
|
31
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
32
|
+
throw new Error(`Invalid GHOSTAPI_PORT: ${value}`);
|
|
33
|
+
}
|
|
34
|
+
return port;
|
|
35
|
+
}
|
|
36
|
+
function parseBoolean(value, fallback) {
|
|
37
|
+
return value ?? fallback;
|
|
38
|
+
}
|
|
39
|
+
function parseBooleanEnv(value, fallback) {
|
|
40
|
+
if (value === undefined || value.trim() === "")
|
|
41
|
+
return fallback;
|
|
42
|
+
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=serverConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverConfig.js","sourceRoot":"","sources":["../../src/config/serverConfig.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
// DOM References
|
|
2
|
+
const els = {
|
|
3
|
+
sidebar: document.getElementById('sidebar'),
|
|
4
|
+
toggleBtn: document.getElementById('sidebar-toggle'),
|
|
5
|
+
statusIndicator: document.getElementById('status-indicator'),
|
|
6
|
+
statusText: document.getElementById('status-text'),
|
|
7
|
+
eventList: document.getElementById('event-list'),
|
|
8
|
+
eventCount: document.getElementById('event-count'),
|
|
9
|
+
emptyDetails: document.getElementById('empty-details'),
|
|
10
|
+
detailsContent: document.getElementById('details-content'),
|
|
11
|
+
searchModal: document.getElementById('search-modal'),
|
|
12
|
+
searchInput: document.getElementById('search-input'),
|
|
13
|
+
rulesModal: document.getElementById('rules-modal'),
|
|
14
|
+
rulesOutput: document.getElementById('rules-output'),
|
|
15
|
+
rulesSubtitle: document.getElementById('rules-subtitle'),
|
|
16
|
+
setupModal: document.getElementById('setup-modal'),
|
|
17
|
+
setupList: document.getElementById('setup-list'),
|
|
18
|
+
setupOutput: document.getElementById('setup-output'),
|
|
19
|
+
setupSubtitle: document.getElementById('setup-subtitle'),
|
|
20
|
+
scenariosModal: document.getElementById('scenarios-modal'),
|
|
21
|
+
scenarioList: document.getElementById('scenario-list'),
|
|
22
|
+
scenarioOutput: document.getElementById('scenario-output'),
|
|
23
|
+
scenariosSubtitle: document.getElementById('scenarios-subtitle'),
|
|
24
|
+
dMethod: document.getElementById('d-method'),
|
|
25
|
+
dPath: document.getElementById('d-path'),
|
|
26
|
+
dStatus: document.getElementById('d-status'),
|
|
27
|
+
dTime: document.getElementById('d-time'),
|
|
28
|
+
dProvider: document.getElementById('d-provider'),
|
|
29
|
+
dSource: document.getElementById('d-source'),
|
|
30
|
+
dReq: document.getElementById('d-req-body'),
|
|
31
|
+
dRes: document.getElementById('d-res-body'),
|
|
32
|
+
faultToggle: document.getElementById('fault-toggle'),
|
|
33
|
+
faultState: document.getElementById('fault-state'),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let events = [];
|
|
37
|
+
let activeId = null;
|
|
38
|
+
let currentFilter = 'all';
|
|
39
|
+
let setupPayload = null;
|
|
40
|
+
let selectedSetupText = '';
|
|
41
|
+
let scenarios = [];
|
|
42
|
+
|
|
43
|
+
// Initialize
|
|
44
|
+
async function init() {
|
|
45
|
+
setupInteractions();
|
|
46
|
+
await fetchFaultLab();
|
|
47
|
+
await fetchHistory();
|
|
48
|
+
setupSSE();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Side-effects & Listeners
|
|
52
|
+
function setupInteractions() {
|
|
53
|
+
els.toggleBtn.onclick = () => els.sidebar.classList.toggle('closed');
|
|
54
|
+
|
|
55
|
+
// Command + K
|
|
56
|
+
document.addEventListener('keydown', (e) => {
|
|
57
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
openSearch();
|
|
60
|
+
}
|
|
61
|
+
if (e.key === 'Escape') closeSearch();
|
|
62
|
+
if (e.key === 'Escape') closeRulesModal();
|
|
63
|
+
if (e.key === 'Escape') closeSetupModal();
|
|
64
|
+
if (e.key === 'Escape') closeScenariosModal();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Modal overlay click
|
|
68
|
+
els.searchModal.onclick = (e) => {
|
|
69
|
+
if (e.target === els.searchModal) closeSearch();
|
|
70
|
+
};
|
|
71
|
+
els.rulesModal.onclick = (e) => {
|
|
72
|
+
if (e.target === els.rulesModal) closeRulesModal();
|
|
73
|
+
};
|
|
74
|
+
els.setupModal.onclick = (e) => {
|
|
75
|
+
if (e.target === els.setupModal) closeSetupModal();
|
|
76
|
+
};
|
|
77
|
+
els.scenariosModal.onclick = (e) => {
|
|
78
|
+
if (e.target === els.scenariosModal) closeScenariosModal();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
els.searchInput.addEventListener('input', renderList);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function openSearch() {
|
|
85
|
+
els.searchModal.classList.add('open');
|
|
86
|
+
els.searchInput.focus();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function closeSearch() {
|
|
90
|
+
els.searchModal.classList.remove('open');
|
|
91
|
+
els.searchInput.value = '';
|
|
92
|
+
renderList();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Data Handling
|
|
96
|
+
async function fetchHistory() {
|
|
97
|
+
try {
|
|
98
|
+
const res = await fetch('/api/events');
|
|
99
|
+
const data = await res.json();
|
|
100
|
+
events = data.reverse();
|
|
101
|
+
renderList();
|
|
102
|
+
} catch(e) {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function setupSSE() {
|
|
106
|
+
const sse = new EventSource('/events');
|
|
107
|
+
sse.onopen = () => {
|
|
108
|
+
els.statusIndicator.className = 'status-indicator connected';
|
|
109
|
+
els.statusText.textContent = 'Connected';
|
|
110
|
+
};
|
|
111
|
+
sse.onerror = () => {
|
|
112
|
+
els.statusIndicator.className = 'status-indicator error';
|
|
113
|
+
els.statusText.textContent = 'Reconnecting...';
|
|
114
|
+
};
|
|
115
|
+
sse.onmessage = (e) => {
|
|
116
|
+
const payload = JSON.parse(e.data);
|
|
117
|
+
if (payload.type === 'proxy_event') {
|
|
118
|
+
events.unshift(payload.event);
|
|
119
|
+
if (events.length > 200) events.pop();
|
|
120
|
+
renderList();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Filter Management
|
|
126
|
+
window.setFilter = (filter) => {
|
|
127
|
+
currentFilter = filter;
|
|
128
|
+
document.querySelectorAll('.nav-scroll-area .nav-item').forEach(el => el.classList.remove('active'));
|
|
129
|
+
const activeEl = document.getElementById('nav-' + filter);
|
|
130
|
+
if (activeEl) activeEl.classList.add('active');
|
|
131
|
+
|
|
132
|
+
if (filter === 'all') {
|
|
133
|
+
document.querySelector('.breadcrumb-active').textContent = 'Live Traffic';
|
|
134
|
+
} else {
|
|
135
|
+
document.querySelector('.breadcrumb-active').textContent = activeEl.querySelector('.nav-item-title').textContent + ' Routing';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
renderList();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
window.clearStorage = async (target, event) => {
|
|
142
|
+
try {
|
|
143
|
+
const btnText = event.currentTarget.querySelector('.nav-item-title');
|
|
144
|
+
const originalText = btnText.textContent;
|
|
145
|
+
btnText.textContent = 'Clearing...';
|
|
146
|
+
|
|
147
|
+
await fetch('/api/clear', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: { 'Content-Type': 'application/json' },
|
|
150
|
+
body: JSON.stringify({ target })
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
btnText.textContent = 'Cleared!';
|
|
154
|
+
setTimeout(() => { btnText.textContent = originalText; }, 1500);
|
|
155
|
+
} catch(e) {
|
|
156
|
+
console.error(e);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
async function fetchFaultLab() {
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch('/api/fault-lab');
|
|
163
|
+
renderFaultLab(await res.json());
|
|
164
|
+
} catch(e) {}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
window.toggleFaultLab = async (event) => {
|
|
168
|
+
const label = event.currentTarget.querySelector('.nav-item-title');
|
|
169
|
+
const original = label.textContent;
|
|
170
|
+
try {
|
|
171
|
+
const enabled = !els.faultToggle.classList.contains('active');
|
|
172
|
+
label.textContent = enabled ? 'Arming...' : 'Disarming...';
|
|
173
|
+
|
|
174
|
+
const res = await fetch('/api/fault-lab', {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
headers: { 'Content-Type': 'application/json' },
|
|
177
|
+
body: JSON.stringify({ enabled, latencyMs: 0, latencyMinMs: 2000, latencyMaxMs: 5000, errorRate: enabled ? 15 : 15, statusCode: 429, retryAfterSeconds: 2 })
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const payload = await res.json();
|
|
181
|
+
if (!res.ok) throw new Error(payload?.error?.message || 'Unable to toggle Chaos Mode');
|
|
182
|
+
renderFaultLab(payload);
|
|
183
|
+
} catch(e) {
|
|
184
|
+
console.error(e);
|
|
185
|
+
} finally {
|
|
186
|
+
label.textContent = original;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
function renderFaultLab(config) {
|
|
191
|
+
els.faultToggle.classList.toggle('active', config.enabled);
|
|
192
|
+
els.faultState.textContent = config.enabled ? `${config.errorRate}%` : 'Off';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
window.generateAiRules = async (event) => {
|
|
196
|
+
const label = event.currentTarget.querySelector('.nav-item-title');
|
|
197
|
+
const original = label.textContent;
|
|
198
|
+
label.textContent = 'Generating...';
|
|
199
|
+
els.rulesModal.classList.add('open');
|
|
200
|
+
els.rulesOutput.textContent = 'Generating .cursorrules from package.json...';
|
|
201
|
+
els.rulesSubtitle.textContent = 'Scanning dependencies';
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch('/api/ai-rules', { method: 'POST' });
|
|
205
|
+
const payload = await res.json();
|
|
206
|
+
if (!res.ok) throw new Error(payload?.error?.message || 'Unable to generate AI rules');
|
|
207
|
+
els.rulesOutput.textContent = payload.content;
|
|
208
|
+
els.rulesSubtitle.textContent = payload.detected.length > 0 ? `Detected: ${payload.detected.join(', ')}` : 'No known provider SDKs detected';
|
|
209
|
+
} catch (e) {
|
|
210
|
+
els.rulesOutput.textContent = `# GhostAPI Agent Rules\n\nGeneration failed: ${e.message}`;
|
|
211
|
+
els.rulesSubtitle.textContent = 'Generation failed';
|
|
212
|
+
} finally {
|
|
213
|
+
label.textContent = original;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
window.copyAgentPrompt = async (event) => {
|
|
218
|
+
const label = event.currentTarget.querySelector('.nav-item-title');
|
|
219
|
+
const original = label.textContent;
|
|
220
|
+
label.textContent = 'Copying...';
|
|
221
|
+
try {
|
|
222
|
+
const res = await fetch('/api/agent-prompt');
|
|
223
|
+
const payload = await res.json();
|
|
224
|
+
if (!res.ok) throw new Error(payload?.error?.message || 'Unable to generate prompt');
|
|
225
|
+
await copyText(payload.content);
|
|
226
|
+
els.rulesModal.classList.add('open');
|
|
227
|
+
els.rulesSubtitle.textContent = 'Copied to clipboard';
|
|
228
|
+
els.rulesOutput.textContent = payload.content;
|
|
229
|
+
} catch (e) {
|
|
230
|
+
els.rulesModal.classList.add('open');
|
|
231
|
+
els.rulesSubtitle.textContent = 'Prompt generation failed';
|
|
232
|
+
els.rulesOutput.textContent = `GhostAPI agent prompt failed: ${e.message}`;
|
|
233
|
+
} finally {
|
|
234
|
+
label.textContent = original;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
window.openSafetyReport = async (event) => {
|
|
239
|
+
const label = event.currentTarget.querySelector('.nav-item-title');
|
|
240
|
+
const original = label.textContent;
|
|
241
|
+
label.textContent = 'Scanning...';
|
|
242
|
+
els.rulesModal.classList.add('open');
|
|
243
|
+
els.rulesSubtitle.textContent = 'Scanning repo';
|
|
244
|
+
els.rulesOutput.textContent = 'Generating GhostAPI safety report...';
|
|
245
|
+
try {
|
|
246
|
+
const res = await fetch('/api/safety-report');
|
|
247
|
+
const payload = await res.json();
|
|
248
|
+
if (!res.ok) throw new Error(payload?.error?.message || 'Unable to generate safety report');
|
|
249
|
+
els.rulesSubtitle.textContent = `${payload.findings.length} findings`;
|
|
250
|
+
els.rulesOutput.textContent = formatSafetyReport(payload);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
els.rulesSubtitle.textContent = 'Safety report failed';
|
|
253
|
+
els.rulesOutput.textContent = `Safety report failed: ${e.message}`;
|
|
254
|
+
} finally {
|
|
255
|
+
label.textContent = original;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
function closeRulesModal() {
|
|
260
|
+
els.rulesModal.classList.remove('open');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function copyRules() {
|
|
264
|
+
copyText(els.rulesOutput.textContent);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
window.generateRepoSetup = async (event) => {
|
|
268
|
+
const label = event.currentTarget.querySelector('.nav-item-title');
|
|
269
|
+
const original = label.textContent;
|
|
270
|
+
label.textContent = 'Generating...';
|
|
271
|
+
selectedSetupText = '';
|
|
272
|
+
setupPayload = null;
|
|
273
|
+
els.setupModal.classList.add('open');
|
|
274
|
+
els.setupList.innerHTML = '';
|
|
275
|
+
els.setupOutput.textContent = 'Scanning package.json and building Cursor/Cline/Claude setup...';
|
|
276
|
+
els.setupSubtitle.textContent = 'Scanning this repo';
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const res = await fetch('/api/setup', { method: 'POST' });
|
|
280
|
+
setupPayload = await res.json();
|
|
281
|
+
if (!res.ok) throw new Error(setupPayload?.error?.message || 'Unable to generate setup');
|
|
282
|
+
renderSetup(setupPayload);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
els.setupOutput.textContent = `Setup generation failed: ${e.message}`;
|
|
285
|
+
els.setupSubtitle.textContent = 'Generation failed';
|
|
286
|
+
} finally {
|
|
287
|
+
label.textContent = original;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
function renderSetup(setup) {
|
|
292
|
+
els.setupSubtitle.textContent = setup.detected.length > 0 ? `Detected SDKs: ${setup.detected.join(', ')}` : 'No known SDKs detected';
|
|
293
|
+
els.setupList.innerHTML = '';
|
|
294
|
+
|
|
295
|
+
addSetupItem('Summary', setup.summary);
|
|
296
|
+
addSetupItem('Commands', setup.commands.join('\n'));
|
|
297
|
+
for (const file of setup.files) addSetupItem(file.path, file.content, file.description);
|
|
298
|
+
for (const patch of setup.patches) addSetupItem(patch.title, patch.content, patch.appliesTo);
|
|
299
|
+
|
|
300
|
+
selectSetupText('Summary', setup.summary);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function addSetupItem(title, content, description = '') {
|
|
304
|
+
const button = document.createElement('button');
|
|
305
|
+
button.className = 'setup-item';
|
|
306
|
+
button.type = 'button';
|
|
307
|
+
button.innerHTML = `<strong>${escapeHtml(title)}</strong><span>${escapeHtml(description || 'Copy-ready setup block')}</span>`;
|
|
308
|
+
button.onclick = () => selectSetupText(title, content);
|
|
309
|
+
els.setupList.appendChild(button);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function selectSetupText(title, content) {
|
|
313
|
+
selectedSetupText = content;
|
|
314
|
+
els.setupOutput.textContent = content;
|
|
315
|
+
els.setupList.querySelectorAll('.setup-item').forEach((item) => item.classList.toggle('active', item.querySelector('strong')?.textContent === title));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function closeSetupModal() {
|
|
319
|
+
els.setupModal.classList.remove('open');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function copySetupOutput() {
|
|
323
|
+
copyText(selectedSetupText || els.setupOutput.textContent);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
window.openScenarios = async (event) => {
|
|
327
|
+
const label = event.currentTarget.querySelector('.nav-item-title');
|
|
328
|
+
const original = label.textContent;
|
|
329
|
+
label.textContent = 'Loading...';
|
|
330
|
+
scenarios = [];
|
|
331
|
+
els.scenariosModal.classList.add('open');
|
|
332
|
+
els.scenarioList.textContent = '';
|
|
333
|
+
els.scenarioOutput.textContent = 'Loading scenario presets...';
|
|
334
|
+
els.scenariosSubtitle.textContent = 'Loading presets';
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const res = await fetch('/api/scenarios');
|
|
338
|
+
const payload = await res.json();
|
|
339
|
+
if (!res.ok) throw new Error(payload?.error?.message || 'Unable to load scenarios');
|
|
340
|
+
scenarios = payload;
|
|
341
|
+
renderScenarios();
|
|
342
|
+
} catch (e) {
|
|
343
|
+
els.scenarioOutput.textContent = `Unable to load scenarios: ${e.message}`;
|
|
344
|
+
} finally {
|
|
345
|
+
label.textContent = original;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
function renderScenarios() {
|
|
350
|
+
els.scenarioList.innerHTML = '';
|
|
351
|
+
els.scenariosSubtitle.textContent = `${scenarios.length} presets ready`;
|
|
352
|
+
const saveCard = document.createElement('div');
|
|
353
|
+
saveCard.className = 'scenario-card scenario-save-card';
|
|
354
|
+
const saveTitle = document.createElement('strong');
|
|
355
|
+
saveTitle.textContent = 'Save current traffic';
|
|
356
|
+
const saveDescription = document.createElement('p');
|
|
357
|
+
saveDescription.textContent = 'Turn the latest dashboard traffic into a replayable custom scenario.';
|
|
358
|
+
const saveActions = document.createElement('div');
|
|
359
|
+
saveActions.className = 'scenario-actions';
|
|
360
|
+
saveActions.append(scenarioButton('Save', saveTrafficScenario));
|
|
361
|
+
saveCard.append(saveTitle, saveDescription, saveActions);
|
|
362
|
+
els.scenarioList.appendChild(saveCard);
|
|
363
|
+
for (const scenario of scenarios) {
|
|
364
|
+
const card = document.createElement('div');
|
|
365
|
+
card.className = 'scenario-card';
|
|
366
|
+
const top = document.createElement('div');
|
|
367
|
+
top.className = 'scenario-card-top';
|
|
368
|
+
const title = document.createElement('strong');
|
|
369
|
+
title.textContent = scenario.title;
|
|
370
|
+
const provider = document.createElement('span');
|
|
371
|
+
provider.className = 'pill';
|
|
372
|
+
provider.textContent = scenario.provider;
|
|
373
|
+
top.append(title, provider);
|
|
374
|
+
|
|
375
|
+
const description = document.createElement('p');
|
|
376
|
+
description.textContent = scenario.description;
|
|
377
|
+
|
|
378
|
+
const actions = document.createElement('div');
|
|
379
|
+
actions.className = 'scenario-actions';
|
|
380
|
+
actions.append(
|
|
381
|
+
scenarioButton('Replay', () => replayScenario(scenario.id)),
|
|
382
|
+
scenarioButton('Export', () => exportScenario(scenario.id)),
|
|
383
|
+
scenarioButton('Share', () => shareScenario(scenario.id))
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
card.append(top, description, actions);
|
|
387
|
+
els.scenarioList.appendChild(card);
|
|
388
|
+
}
|
|
389
|
+
els.scenarioOutput.textContent = JSON.stringify(scenarios[0] ?? {}, null, 2);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
window.replayScenario = async (id) => {
|
|
393
|
+
const payload = await fetchScenarioAction(`/api/scenarios/${encodeURIComponent(id)}/replay`, { method: 'POST' });
|
|
394
|
+
const text = JSON.stringify(payload, null, 2);
|
|
395
|
+
els.scenarioOutput.textContent = text;
|
|
396
|
+
await copyText(text);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
window.exportScenario = async (id) => {
|
|
400
|
+
const payload = await fetchScenarioAction(`/api/scenarios/${encodeURIComponent(id)}/export`);
|
|
401
|
+
const text = JSON.stringify(payload, null, 2);
|
|
402
|
+
els.scenarioOutput.textContent = text;
|
|
403
|
+
await copyText(text);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
window.shareScenario = async (id) => {
|
|
407
|
+
const payload = await fetchScenarioAction(`/api/scenarios/${encodeURIComponent(id)}/share`, { method: 'POST' });
|
|
408
|
+
els.scenarioOutput.textContent = JSON.stringify(payload, null, 2);
|
|
409
|
+
await copyText(payload.shareText || JSON.stringify(payload));
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
async function saveTrafficScenario() {
|
|
413
|
+
const payload = await fetchScenarioAction('/api/scenarios/save-from-traffic', {
|
|
414
|
+
method: 'POST',
|
|
415
|
+
headers: { 'content-type': 'application/json' },
|
|
416
|
+
body: JSON.stringify({ title: `Saved traffic ${new Date().toLocaleString()}` })
|
|
417
|
+
});
|
|
418
|
+
const text = JSON.stringify(payload, null, 2);
|
|
419
|
+
els.scenarioOutput.textContent = text;
|
|
420
|
+
await copyText(text);
|
|
421
|
+
const res = await fetch('/api/scenarios');
|
|
422
|
+
scenarios = await res.json();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function scenarioButton(label, handler) {
|
|
426
|
+
const button = document.createElement('button');
|
|
427
|
+
button.type = 'button';
|
|
428
|
+
button.textContent = label;
|
|
429
|
+
button.onclick = () => handler().catch((error) => {
|
|
430
|
+
els.scenarioOutput.textContent = `Scenario action failed: ${error.message}`;
|
|
431
|
+
});
|
|
432
|
+
return button;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function fetchScenarioAction(url, options) {
|
|
436
|
+
const res = await fetch(url, options);
|
|
437
|
+
const payload = await res.json();
|
|
438
|
+
if (!res.ok) throw new Error(payload?.error?.message || 'Scenario action failed');
|
|
439
|
+
return payload;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function closeScenariosModal() {
|
|
443
|
+
els.scenariosModal.classList.remove('open');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Render Logic
|
|
447
|
+
function renderList() {
|
|
448
|
+
const query = els.searchInput.value.toLowerCase();
|
|
449
|
+
const list = events.filter(e => {
|
|
450
|
+
const path = String(e.path ?? '');
|
|
451
|
+
const provider = String(e.provider ?? '');
|
|
452
|
+
const requestText = safeStringify(e.request).toLowerCase();
|
|
453
|
+
const matchesSearch = path.toLowerCase().includes(query) ||
|
|
454
|
+
provider.toLowerCase().includes(query) ||
|
|
455
|
+
requestText.includes(query);
|
|
456
|
+
const matchesFilter = currentFilter === 'all' || provider === currentFilter;
|
|
457
|
+
return matchesSearch && matchesFilter;
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
els.eventCount.textContent = events.length;
|
|
461
|
+
els.eventList.innerHTML = '';
|
|
462
|
+
|
|
463
|
+
const frag = document.createDocumentFragment();
|
|
464
|
+
list.forEach(ev => {
|
|
465
|
+
const div = document.createElement('div');
|
|
466
|
+
div.className = `event-card ${ev.id === activeId ? 'active' : ''}`;
|
|
467
|
+
div.onclick = () => selectEvent(ev.id);
|
|
468
|
+
|
|
469
|
+
const method = String(ev.method ?? 'GET');
|
|
470
|
+
const statusCode = Number(ev.statusCode ?? 0);
|
|
471
|
+
const top = document.createElement('div');
|
|
472
|
+
top.className = 'ec-top';
|
|
473
|
+
const methodEl = document.createElement('span');
|
|
474
|
+
methodEl.className = `ec-method m-${method.toLowerCase()}`;
|
|
475
|
+
methodEl.textContent = method;
|
|
476
|
+
const time = document.createElement('span');
|
|
477
|
+
time.className = 'ec-time';
|
|
478
|
+
time.textContent = new Date(ev.timestamp ?? Date.now()).toLocaleTimeString();
|
|
479
|
+
top.append(methodEl, time);
|
|
480
|
+
|
|
481
|
+
const path = document.createElement('div');
|
|
482
|
+
path.className = 'ec-path';
|
|
483
|
+
path.textContent = String(ev.path ?? '');
|
|
484
|
+
|
|
485
|
+
const bottom = document.createElement('div');
|
|
486
|
+
bottom.className = 'ec-bottom';
|
|
487
|
+
const status = document.createElement('span');
|
|
488
|
+
status.className = `pill pill-status-${strType(statusCode)}`;
|
|
489
|
+
status.textContent = String(statusCode || 'n/a');
|
|
490
|
+
const duration = document.createElement('span');
|
|
491
|
+
duration.className = 'pill';
|
|
492
|
+
duration.textContent = `${Number(ev.durationMs ?? 0)}ms`;
|
|
493
|
+
const provider = document.createElement('span');
|
|
494
|
+
provider.className = 'pill provider-pill';
|
|
495
|
+
provider.textContent = String(ev.provider ?? 'unknown');
|
|
496
|
+
bottom.append(status, duration, provider);
|
|
497
|
+
|
|
498
|
+
div.append(top, path, bottom);
|
|
499
|
+
frag.appendChild(div);
|
|
500
|
+
});
|
|
501
|
+
els.eventList.appendChild(frag);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function selectEvent(id) {
|
|
505
|
+
activeId = id;
|
|
506
|
+
renderList();
|
|
507
|
+
const ev = events.find(e => e.id === id);
|
|
508
|
+
if (!ev) return;
|
|
509
|
+
|
|
510
|
+
els.emptyDetails.style.display = 'none';
|
|
511
|
+
els.detailsContent.classList.add('visible');
|
|
512
|
+
|
|
513
|
+
els.dMethod.textContent = ev.method;
|
|
514
|
+
els.dMethod.className = `ec-method m-${ev.method.toLowerCase()}`;
|
|
515
|
+
els.dPath.textContent = ev.path;
|
|
516
|
+
|
|
517
|
+
els.dStatus.textContent = ev.statusCode;
|
|
518
|
+
els.dStatus.className = `pill pill-status-${strType(ev.statusCode)}`;
|
|
519
|
+
|
|
520
|
+
els.dTime.textContent = `${ev.durationMs}ms`;
|
|
521
|
+
els.dProvider.textContent = ev.provider;
|
|
522
|
+
els.dSource.textContent = ev.source;
|
|
523
|
+
|
|
524
|
+
els.dReq.innerHTML = highlight(ev.request);
|
|
525
|
+
els.dRes.innerHTML = highlight(ev.response);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
window.generateSelectedTest = async () => {
|
|
529
|
+
if (!activeId) return;
|
|
530
|
+
try {
|
|
531
|
+
const res = await fetch(`/api/events/${encodeURIComponent(activeId)}/test`);
|
|
532
|
+
const payload = await res.json();
|
|
533
|
+
if (!res.ok) throw new Error(payload?.error?.message || 'Unable to generate test');
|
|
534
|
+
els.rulesModal.classList.add('open');
|
|
535
|
+
els.rulesSubtitle.textContent = payload.filename;
|
|
536
|
+
els.rulesOutput.textContent = payload.content;
|
|
537
|
+
await copyText(payload.content);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
els.rulesModal.classList.add('open');
|
|
540
|
+
els.rulesSubtitle.textContent = 'Test generation failed';
|
|
541
|
+
els.rulesOutput.textContent = `Test generation failed: ${e.message}`;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Utils
|
|
546
|
+
function strType(code) {
|
|
547
|
+
if (code >= 200 && code < 300) return '200';
|
|
548
|
+
if (code >= 300 && code < 400) return '300';
|
|
549
|
+
if (code >= 400 && code < 500) return '400';
|
|
550
|
+
return '500';
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function highlight(obj) {
|
|
554
|
+
if (!obj) return '{}';
|
|
555
|
+
const str = JSON.stringify(obj, null, 2);
|
|
556
|
+
const scaped = str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
557
|
+
return scaped.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, (match) => {
|
|
558
|
+
let cls = 'j-num';
|
|
559
|
+
if (/^"/.test(match)) { cls = /:$/.test(match) ? 'j-key' : 'j-str'; }
|
|
560
|
+
else if (/true|false/.test(match)) cls = 'j-bool';
|
|
561
|
+
else if (/null/.test(match)) cls = 'j-null';
|
|
562
|
+
return `<span class="${cls}">${match}</span>`;
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function copy(type) {
|
|
567
|
+
const text = (type === 'req') ? els.dReq.textContent : els.dRes.textContent;
|
|
568
|
+
copyText(text);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
window.closeRulesModal = closeRulesModal;
|
|
572
|
+
window.copyRules = copyRules;
|
|
573
|
+
window.closeSetupModal = closeSetupModal;
|
|
574
|
+
window.copySetupOutput = copySetupOutput;
|
|
575
|
+
window.closeScenariosModal = closeScenariosModal;
|
|
576
|
+
|
|
577
|
+
function escapeHtml(value) {
|
|
578
|
+
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function formatSafetyReport(report) {
|
|
582
|
+
const findings = report.findings.length > 0
|
|
583
|
+
? report.findings.map((finding) => `- ${finding.severity.toUpperCase()} ${finding.file ? `${finding.file}: ` : ''}${finding.message}`).join('\n')
|
|
584
|
+
: '- No high-risk provider usage found.';
|
|
585
|
+
return [
|
|
586
|
+
'# GhostAPI Safety Report',
|
|
587
|
+
'',
|
|
588
|
+
`Project: ${report.projectRoot}`,
|
|
589
|
+
`Detected SDKs: ${report.detected.length > 0 ? report.detected.join(', ') : 'none'}`,
|
|
590
|
+
'',
|
|
591
|
+
'## Findings',
|
|
592
|
+
findings,
|
|
593
|
+
'',
|
|
594
|
+
'## Recommendations',
|
|
595
|
+
...report.recommendations.map((item) => `- ${item}`)
|
|
596
|
+
].join('\n');
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function safeStringify(value) {
|
|
600
|
+
try {
|
|
601
|
+
return JSON.stringify(value ?? '');
|
|
602
|
+
} catch {
|
|
603
|
+
return '';
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function copyText(text) {
|
|
608
|
+
try {
|
|
609
|
+
await navigator.clipboard.writeText(text ?? '');
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.warn('Clipboard copy failed', error);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
init();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Request, Response } from "express";
|
|
2
|
+
export declare function dashboardHandler(_request: Request, response: Response): Promise<void>;
|
|
3
|
+
export declare function dashboardCssHandler(_request: Request, response: Response): Promise<void>;
|
|
4
|
+
export declare function dashboardJsHandler(_request: Request, response: Response): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export async function dashboardHandler(_request, response) {
|
|
6
|
+
response.setHeader("Content-Type", "text/html");
|
|
7
|
+
const html = await readFile(join(__dirname, "index.html"), "utf8");
|
|
8
|
+
response.status(200).send(html);
|
|
9
|
+
}
|
|
10
|
+
export async function dashboardCssHandler(_request, response) {
|
|
11
|
+
response.setHeader("Content-Type", "text/css");
|
|
12
|
+
const css = await readFile(join(__dirname, "styles.css"), "utf8");
|
|
13
|
+
response.status(200).send(css);
|
|
14
|
+
}
|
|
15
|
+
export async function dashboardJsHandler(_request, response) {
|
|
16
|
+
response.setHeader("Content-Type", "application/javascript");
|
|
17
|
+
const js = await readFile(join(__dirname, "app.js"), "utf8");
|
|
18
|
+
response.status(200).send(js);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=dashboard.js.map
|