omnigate-ai 1.0.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/Dockerfile +37 -0
- package/Omnigate AI.txt +72 -0
- package/bin/omnigate.js +2 -0
- package/docker-compose.yml +16 -0
- package/package.json +30 -0
- package/payload-theme.json +1 -0
- package/proxy-helper.js +60 -0
- package/public/app.js +432 -0
- package/public/index.html +367 -0
- package/public/styles.css +1060 -0
- package/server.js +586 -0
- package/test-payload.json +1 -0
- package/test-payload2.json +1 -0
- package/test-real-api.js +228 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>OmniGate AI - Telemetry Dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css">
|
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="dashboard-container">
|
|
12
|
+
<header class="dashboard-header">
|
|
13
|
+
<div class="logo">
|
|
14
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
15
|
+
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="url(#headerGrad)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
16
|
+
<path d="M2 17L12 22L22 17" stroke="url(#headerGrad)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
17
|
+
<path d="M2 12L12 17L22 12" stroke="url(#headerGrad)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
18
|
+
<defs>
|
|
19
|
+
<linearGradient id="headerGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
20
|
+
<stop offset="0%" stop-color="#00f2fe" />
|
|
21
|
+
<stop offset="100%" stop-color="#4facfe" />
|
|
22
|
+
</linearGradient>
|
|
23
|
+
</defs>
|
|
24
|
+
</svg>
|
|
25
|
+
<div>
|
|
26
|
+
<h1>OmniGate AI <span class="badge">LIVE TELEMETRY</span></h1>
|
|
27
|
+
<span style="font-size:0.7rem;color:var(--text-dim);font-weight:400;display:block;margin-top:0.1rem;">Privacy-First API Gateway · Token Cost Tracker</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="status-indicator">
|
|
31
|
+
<span class="pulse-dot"></span>
|
|
32
|
+
<span>Gateway Connected</span>
|
|
33
|
+
</div>
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
<!-- Glass Gauge Cluster -->
|
|
37
|
+
<div class="gauge-cluster-wrapper locked-section" id="gauge-cluster-wrapper">
|
|
38
|
+
<div class="lock-overlay">
|
|
39
|
+
<div class="lock-card">
|
|
40
|
+
<span class="lock-icon">🔒</span>
|
|
41
|
+
<h4>Telemetry Locked</h4>
|
|
42
|
+
<p>Enter your Gateway Secret Key in the configuration panel to unlock live metrics.</p>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="gauge-cluster">
|
|
46
|
+
|
|
47
|
+
<!-- Token Volume Gauge -->
|
|
48
|
+
<div class="gauge-card">
|
|
49
|
+
<h3>Token Velocity</h3>
|
|
50
|
+
<div class="gauge-wrapper">
|
|
51
|
+
<svg class="gauge-svg" viewBox="0 0 100 100">
|
|
52
|
+
<defs>
|
|
53
|
+
<linearGradient id="cyan-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
54
|
+
<stop offset="0%" stop-color="#00f2fe" />
|
|
55
|
+
<stop offset="100%" stop-color="#4facfe" />
|
|
56
|
+
</linearGradient>
|
|
57
|
+
</defs>
|
|
58
|
+
<circle class="gauge-bg" cx="50" cy="50" r="45"></circle>
|
|
59
|
+
<circle class="gauge-value" id="tokens-gauge" cx="50" cy="50" r="45" stroke="url(#cyan-gradient)"></circle>
|
|
60
|
+
</svg>
|
|
61
|
+
<div class="gauge-center-text">
|
|
62
|
+
<span class="value" id="total-tokens">0</span>
|
|
63
|
+
<span class="unit">Total Tokens</span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="gauge-stats">
|
|
67
|
+
<div class="stat"><span class="label">IN</span> <span class="val" id="input-tokens">0</span></div>
|
|
68
|
+
<div class="stat"><span class="label">OUT</span> <span class="val" id="output-tokens">0</span></div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- Cost Gauge -->
|
|
73
|
+
<div class="gauge-card">
|
|
74
|
+
<h3>Estimated Spend</h3>
|
|
75
|
+
<div class="gauge-wrapper">
|
|
76
|
+
<svg class="gauge-svg" viewBox="0 0 100 100">
|
|
77
|
+
<defs>
|
|
78
|
+
<linearGradient id="red-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
79
|
+
<stop offset="0%" stop-color="#ff0844" />
|
|
80
|
+
<stop offset="100%" stop-color="#ffb199" />
|
|
81
|
+
</linearGradient>
|
|
82
|
+
</defs>
|
|
83
|
+
<circle class="gauge-bg" cx="50" cy="50" r="45"></circle>
|
|
84
|
+
<circle class="gauge-value" id="cost-gauge" cx="50" cy="50" r="45" stroke="url(#red-gradient)"></circle>
|
|
85
|
+
</svg>
|
|
86
|
+
<div class="gauge-center-text">
|
|
87
|
+
<span class="value" id="total-cost">$0.000</span>
|
|
88
|
+
<span class="unit">USD</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="gauge-stats">
|
|
92
|
+
<div class="stat"><span class="label">AVG REQ</span> <span class="val" id="avg-cost">$0.000</span></div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- RPM Gauge -->
|
|
97
|
+
<div class="gauge-card">
|
|
98
|
+
<h3>Network RPM</h3>
|
|
99
|
+
<div class="gauge-wrapper">
|
|
100
|
+
<svg class="gauge-svg" viewBox="0 0 100 100">
|
|
101
|
+
<defs>
|
|
102
|
+
<linearGradient id="purple-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
103
|
+
<stop offset="0%" stop-color="#c471ed" />
|
|
104
|
+
<stop offset="100%" stop-color="#f64f59" />
|
|
105
|
+
</linearGradient>
|
|
106
|
+
</defs>
|
|
107
|
+
<circle class="gauge-bg" cx="50" cy="50" r="45"></circle>
|
|
108
|
+
<circle class="gauge-value" id="rpm-gauge" cx="50" cy="50" r="45" stroke="url(#purple-gradient)"></circle>
|
|
109
|
+
</svg>
|
|
110
|
+
<div class="gauge-center-text">
|
|
111
|
+
<span class="value" id="rpm-value">0</span>
|
|
112
|
+
<span class="unit">Req / Min</span>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="gauge-stats">
|
|
116
|
+
<div class="stat"><span class="label">TOTAL REQ</span> <span class="val" id="total-requests">0</span></div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Main Content Grid -->
|
|
124
|
+
<div class="main-content-grid">
|
|
125
|
+
|
|
126
|
+
<!-- Control Panel (Config, Onboarding, Features) -->
|
|
127
|
+
<div class="config-panel">
|
|
128
|
+
<div class="tabs-header">
|
|
129
|
+
<button class="tab-btn active" data-tab="config">⚙️ Configuration</button>
|
|
130
|
+
<button class="tab-btn" data-tab="onboarding">📖 Setup Guide</button>
|
|
131
|
+
<button class="tab-btn" data-tab="features">✨ Features Info</button>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<!-- Tab 1: Configuration -->
|
|
135
|
+
<div id="tab-config" class="tab-content active">
|
|
136
|
+
<form id="config-form" class="config-form">
|
|
137
|
+
<div class="form-group">
|
|
138
|
+
<label for="agencyGatewayKey">Gateway Secret Key</label>
|
|
139
|
+
<input type="text" id="agencyGatewayKey" name="agencyGatewayKey" placeholder="Enter proxy access key...">
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div class="form-divider">OpenAI-Compatible Providers</div>
|
|
143
|
+
<div class="form-group">
|
|
144
|
+
<label for="openaiPreset">Provider Preset</label>
|
|
145
|
+
<select id="openaiPreset" name="openaiPreset">
|
|
146
|
+
<option value="https://api.openai.com/v1/chat/completions">OpenAI Official</option>
|
|
147
|
+
<option value="https://api.deepseek.com/v1/chat/completions">DeepSeek API</option>
|
|
148
|
+
<option value="https://generativelanguage.googleapis.com/v1beta/openai/chat/completions">Gemini API</option>
|
|
149
|
+
<option value="https://api.x.ai/v1/chat/completions">Grok (xAI)</option>
|
|
150
|
+
<option value="custom">Custom Endpoint...</option>
|
|
151
|
+
</select>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="form-group">
|
|
154
|
+
<label for="openaiApiUrl">API Endpoint URL</label>
|
|
155
|
+
<input type="text" id="openaiApiUrl" name="openaiApiUrl" placeholder="https://api.openai.com/v1/chat/completions">
|
|
156
|
+
</div>
|
|
157
|
+
<div class="form-group">
|
|
158
|
+
<label for="openaiApiKey">API Secret Key</label>
|
|
159
|
+
<input type="password" id="openaiApiKey" name="openaiApiKey" placeholder="Enter API key (leave blank to keep current)">
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="form-divider">Anthropic Provider</div>
|
|
163
|
+
<div class="form-group">
|
|
164
|
+
<label for="anthropicApiUrl">API Endpoint URL</label>
|
|
165
|
+
<input type="text" id="anthropicApiUrl" name="anthropicApiUrl" placeholder="https://api.anthropic.com/v1/messages">
|
|
166
|
+
</div>
|
|
167
|
+
<div class="form-group">
|
|
168
|
+
<label for="anthropicApiKey">API Secret Key</label>
|
|
169
|
+
<input type="password" id="anthropicApiKey" name="anthropicApiKey" placeholder="Enter API key (leave blank to keep current)">
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div class="form-actions">
|
|
173
|
+
<button type="submit" class="btn-save">Save Configurations</button>
|
|
174
|
+
<div id="config-status" class="config-status"></div>
|
|
175
|
+
</div>
|
|
176
|
+
</form>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<!-- Tab 2: Onboarding Setup Guide -->
|
|
180
|
+
<div id="tab-onboarding" class="tab-content">
|
|
181
|
+
<div class="onboarding-guide">
|
|
182
|
+
<div class="onboarding-section">
|
|
183
|
+
<h4>1. Send raw request via CLI (curl)</h4>
|
|
184
|
+
<p>Test the proxy from any terminal, compiler, or shell environment:</p>
|
|
185
|
+
<div class="code-container">
|
|
186
|
+
<button class="copy-btn" onclick="copySnippet('curl-code', this)">Copy</button>
|
|
187
|
+
<pre id="curl-code">curl -X POST http://localhost:8080/v1/chat/completions \
|
|
188
|
+
-H "Content-Type: application/json" \
|
|
189
|
+
-H "X-Gateway-Key: stub-agency-key-for-local-testing" \
|
|
190
|
+
-H "X-Gateway-User-ID: developer-erfan" \
|
|
191
|
+
-H "X-Gateway-Project-ID: local-test-v1" \
|
|
192
|
+
-d '{
|
|
193
|
+
"model": "deepseek-v4-pro",
|
|
194
|
+
"messages": [{"role": "user", "content": "Say Hello!"}],
|
|
195
|
+
"stream": true
|
|
196
|
+
}'</pre>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div class="onboarding-section">
|
|
201
|
+
<h4>2. VS Code (Continue Extension)</h4>
|
|
202
|
+
<p>Add this model block to your <code>~/.continue/config.yaml</code> config file:</p>
|
|
203
|
+
<div class="code-container">
|
|
204
|
+
<button class="copy-btn" onclick="copySnippet('continue-code', this)">Copy</button>
|
|
205
|
+
<pre id="continue-code">models:
|
|
206
|
+
- name: DeepSeek (via OmniGate)
|
|
207
|
+
provider: openai
|
|
208
|
+
model: deepseek-v4-pro
|
|
209
|
+
apiBase: http://localhost:8080/v1/
|
|
210
|
+
apiKey: stub-agency-key-for-local-testing
|
|
211
|
+
requestOptions:
|
|
212
|
+
headers:
|
|
213
|
+
X-Gateway-Key: stub-agency-key-for-local-testing
|
|
214
|
+
X-Gateway-User-ID: developer-erfan
|
|
215
|
+
X-Gateway-Project-ID: vscode-project-omega</pre>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div class="onboarding-section">
|
|
220
|
+
<h4>3. Roo Code or Cline</h4>
|
|
221
|
+
<p>Choose <b>OpenAI Compatible</b> provider, set Base URL to <code>http://localhost:8080/v1</code>, and add these custom headers in settings:</p>
|
|
222
|
+
<div class="code-container">
|
|
223
|
+
<button class="copy-btn" onclick="copySnippet('cline-code', this)">Copy</button>
|
|
224
|
+
<pre id="cline-code">{
|
|
225
|
+
"X-Gateway-Key": "stub-agency-key-for-local-testing",
|
|
226
|
+
"X-Gateway-User-ID": "developer-erfan",
|
|
227
|
+
"X-Gateway-Project-ID": "roo-code-evaluation"
|
|
228
|
+
}</pre>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<!-- Tab 3: Features Overview -->
|
|
235
|
+
<div id="tab-features" class="tab-content">
|
|
236
|
+
<div class="onboarding-guide">
|
|
237
|
+
<div class="onboarding-section">
|
|
238
|
+
<h4>🛠️ Dashboard Features Guide</h4>
|
|
239
|
+
<ul class="features-list">
|
|
240
|
+
<li>
|
|
241
|
+
<div><span class="feature-badge badge-gauge">Token Velocity</span></div>
|
|
242
|
+
<p>Tracks your active token usage (Input vs Output) in real-time. Reflects changes immediately as requests stream through.</p>
|
|
243
|
+
</li>
|
|
244
|
+
<li>
|
|
245
|
+
<div><span class="feature-badge badge-gauge">Estimated Spend</span></div>
|
|
246
|
+
<p>Calculates cumulative costs based on official provider rates (per 1M tokens) to alert you before any bill shock occurs.</p>
|
|
247
|
+
</li>
|
|
248
|
+
<li>
|
|
249
|
+
<div><span class="feature-badge badge-gauge">Network RPM</span></div>
|
|
250
|
+
<p>Monitors Requests Per Minute. Crucial for detecting runaway agent loops or automated tool loops.</p>
|
|
251
|
+
</li>
|
|
252
|
+
<li>
|
|
253
|
+
<div><span class="feature-badge badge-config">Gateway Config</span></div>
|
|
254
|
+
<p>Allows saving custom endpoints (DeepSeek, OpenAI, Gemini) and keys. Settings are written directly to your self-hosted instance's disk.</p>
|
|
255
|
+
</li>
|
|
256
|
+
<li>
|
|
257
|
+
<div><span class="feature-badge badge-table">Live Interceptions</span></div>
|
|
258
|
+
<p>Displays an audit trail of incoming requests. Shows timestamp, model, project, user, and precise token costs.</p>
|
|
259
|
+
</li>
|
|
260
|
+
</ul>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
<!-- Feed / Data Grid -->
|
|
268
|
+
<div class="data-panel-wrapper locked-section" id="data-panel-wrapper">
|
|
269
|
+
<div class="lock-overlay">
|
|
270
|
+
<div class="lock-card">
|
|
271
|
+
<span class="lock-icon">🔒</span>
|
|
272
|
+
<h4>Audit Log Locked</h4>
|
|
273
|
+
<p>Enter your Gateway Secret Key in the configuration panel to unlock live request interceptions.</p>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="data-panel">
|
|
277
|
+
<div class="panel-header">
|
|
278
|
+
<h3>📡 Live Request Interceptions</h3>
|
|
279
|
+
</div>
|
|
280
|
+
<div class="table-wrapper">
|
|
281
|
+
<table class="telemetry-table">
|
|
282
|
+
<thead>
|
|
283
|
+
<tr>
|
|
284
|
+
<th>Timestamp</th>
|
|
285
|
+
<th>Provider / Model</th>
|
|
286
|
+
<th>Project ID</th>
|
|
287
|
+
<th>User ID</th>
|
|
288
|
+
<th class="text-right">Input Tokens</th>
|
|
289
|
+
<th class="text-right">Output Tokens</th>
|
|
290
|
+
<th class="text-right">Est. Cost</th>
|
|
291
|
+
</tr>
|
|
292
|
+
</thead>
|
|
293
|
+
<tbody id="telemetry-tbody">
|
|
294
|
+
<!-- Rows injected by app.js -->
|
|
295
|
+
</tbody>
|
|
296
|
+
</table>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
<!-- Onboarding Modal Overlay -->
|
|
304
|
+
<div id="onboarding-modal" class="modal-overlay hidden">
|
|
305
|
+
<div class="modal-content">
|
|
306
|
+
<div class="modal-header">
|
|
307
|
+
<h2>Welcome to OmniGate AI</h2>
|
|
308
|
+
<div class="step-indicator">
|
|
309
|
+
<span class="step-dot active" data-step="1"></span>
|
|
310
|
+
<span class="step-dot" data-step="2"></span>
|
|
311
|
+
<span class="step-dot" data-step="3"></span>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div class="modal-body">
|
|
316
|
+
<!-- Step 1: Overview -->
|
|
317
|
+
<div class="wizard-step active" id="step-1">
|
|
318
|
+
<h3>Your Privacy-First API Gateway</h3>
|
|
319
|
+
<p>OmniGate AI is a high-performance proxy that intercepts, logs, and routes your LLM API requests.</p>
|
|
320
|
+
<ul class="features-list" style="margin-top: 1.5rem;">
|
|
321
|
+
<li><div><span class="feature-badge badge-gauge">Track Costs</span></div><p>See exactly how much you spend in real time.</p></li>
|
|
322
|
+
<li><div><span class="feature-badge badge-table">Audit Logs</span></div><p>View all intercepted requests and responses locally.</p></li>
|
|
323
|
+
<li><div><span class="feature-badge badge-config">Secure</span></div><p>Your API keys never leave your machine.</p></li>
|
|
324
|
+
</ul>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<!-- Step 2: Gateway Secret Key -->
|
|
328
|
+
<div class="wizard-step" id="step-2">
|
|
329
|
+
<h3>Secure Your Gateway</h3>
|
|
330
|
+
<p>To use OmniGate AI, you need to configure a <strong>Gateway Secret Key</strong>. This acts as a password for your local proxy, ensuring only your tools can route traffic through it.</p>
|
|
331
|
+
<div class="form-group" style="margin-top: 1.5rem;">
|
|
332
|
+
<label for="onboardingGatewayKey">Create or Enter your Gateway Secret Key</label>
|
|
333
|
+
<input type="text" id="onboardingGatewayKey" placeholder="e.g., my-secret-local-key-123" style="width: 100%; box-sizing: border-box; font-size: 1.1rem; padding: 0.75rem;">
|
|
334
|
+
<p style="font-size: 0.8rem; color: var(--text-dim); margin-top: 0.5rem;">You will use this key in your code editors (VS Code, Roo Code) to authenticate with this dashboard.</p>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<!-- Step 3: Integration Guide -->
|
|
339
|
+
<div class="wizard-step" id="step-3">
|
|
340
|
+
<h3>Connect Your Tools</h3>
|
|
341
|
+
<p>Now that your key is set, update your AI coding assistants to route traffic to <code>http://localhost:8080/v1</code>.</p>
|
|
342
|
+
|
|
343
|
+
<div class="onboarding-section" style="margin-top: 1rem;">
|
|
344
|
+
<h4 style="font-size: 0.9rem; color: var(--accent-cyan);">Roo Code / Cline</h4>
|
|
345
|
+
<p style="font-size: 0.85rem; color: var(--text-dim);">Set Provider to <b>OpenAI Compatible</b>, Base URL to <code>http://localhost:8080/v1</code>, and add Custom Headers:</p>
|
|
346
|
+
<div class="code-container" style="margin-top: 0.5rem;">
|
|
347
|
+
<pre style="font-size: 0.8rem; padding: 0.5rem;">{
|
|
348
|
+
"X-Gateway-Key": "<span id="preview-key">your-key</span>",
|
|
349
|
+
"X-Gateway-User-ID": "developer",
|
|
350
|
+
"X-Gateway-Project-ID": "my-project"
|
|
351
|
+
}</pre>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<div class="modal-footer">
|
|
358
|
+
<button id="btn-prev" class="btn-secondary" style="visibility: hidden;">Back</button>
|
|
359
|
+
<button id="btn-next" class="btn-primary">Next</button>
|
|
360
|
+
<button id="btn-finish" class="btn-save hidden">Launch Dashboard</button>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
|
|
365
|
+
<script src="app.js"></script>
|
|
366
|
+
</body>
|
|
367
|
+
</html>
|