summaryrefslogtreecommitdiffstats
path: root/cmd/deploy/templates/webui.html
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-01-24 09:48:34 -0800
committerbndw <ben@bdw.to>2026-01-24 09:48:34 -0800
commit5861e465a2ccf31d87ea25ac145770786f9cc96e (patch)
tree4ac6b57a06b46d8492717b235909f9e0db0b4f22 /cmd/deploy/templates/webui.html
parentef37850c7090493cf2b26d2e565511fe23cc9bfc (diff)
Rename project from deploy to ship
- Rename module to github.com/bdw/ship - Rename cmd/deploy to cmd/ship - Update all import paths - Update config path from ~/.config/deploy to ~/.config/ship - Update VPS env path from /etc/deploy to /etc/ship - Update README, Makefile, and docs
Diffstat (limited to 'cmd/deploy/templates/webui.html')
-rw-r--r--cmd/deploy/templates/webui.html440
1 files changed, 0 insertions, 440 deletions
diff --git a/cmd/deploy/templates/webui.html b/cmd/deploy/templates/webui.html
deleted file mode 100644
index 052d599..0000000
--- a/cmd/deploy/templates/webui.html
+++ /dev/null
@@ -1,440 +0,0 @@
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>Deploy - Web UI</title>
7 <style>
8 * {
9 margin: 0;
10 padding: 0;
11 box-sizing: border-box;
12 }
13
14 body {
15 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16 background: #f5f5f5;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 header {
22 background: #2c3e50;
23 color: white;
24 padding: 1.5rem 2rem;
25 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
26 }
27
28 header h1 {
29 font-size: 1.8rem;
30 font-weight: 600;
31 }
32
33 header p {
34 color: #bdc3c7;
35 margin-top: 0.25rem;
36 font-size: 0.9rem;
37 }
38
39 .container {
40 max-width: 1200px;
41 margin: 2rem auto;
42 padding: 0 2rem;
43 }
44
45 .empty-state {
46 text-align: center;
47 padding: 4rem 2rem;
48 background: white;
49 border-radius: 8px;
50 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
51 }
52
53 .empty-state h2 {
54 color: #7f8c8d;
55 font-weight: 500;
56 margin-bottom: 0.5rem;
57 }
58
59 .empty-state p {
60 color: #95a5a6;
61 }
62
63 .host-section {
64 margin-bottom: 2rem;
65 }
66
67 .host-header {
68 background: white;
69 padding: 1rem 1.5rem;
70 border-radius: 8px 8px 0 0;
71 border-left: 4px solid #3498db;
72 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
73 }
74
75 .host-header h2 {
76 font-size: 1.3rem;
77 color: #2c3e50;
78 font-weight: 600;
79 }
80
81 .apps-grid {
82 display: grid;
83 grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
84 gap: 1rem;
85 padding: 1rem;
86 background: #ecf0f1;
87 border-radius: 0 0 8px 8px;
88 }
89
90 .app-card {
91 background: white;
92 padding: 1.5rem;
93 border-radius: 6px;
94 box-shadow: 0 1px 3px rgba(0,0,0,0.1);
95 transition: transform 0.2s, box-shadow 0.2s;
96 }
97
98 .app-card:hover {
99 transform: translateY(-2px);
100 box-shadow: 0 4px 8px rgba(0,0,0,0.15);
101 }
102
103 .app-header {
104 display: flex;
105 justify-content: space-between;
106 align-items: center;
107 margin-bottom: 1rem;
108 }
109
110 .app-name {
111 font-size: 1.2rem;
112 font-weight: 600;
113 color: #2c3e50;
114 }
115
116 .app-type {
117 padding: 0.25rem 0.75rem;
118 border-radius: 12px;
119 font-size: 0.75rem;
120 font-weight: 500;
121 text-transform: uppercase;
122 }
123
124 .app-type.app {
125 background: #3498db;
126 color: white;
127 }
128
129 .app-type.static {
130 background: #2ecc71;
131 color: white;
132 }
133
134 .app-info {
135 margin-bottom: 0.5rem;
136 }
137
138 .app-info-label {
139 color: #7f8c8d;
140 font-size: 0.85rem;
141 font-weight: 500;
142 margin-bottom: 0.25rem;
143 }
144
145 .app-info-value {
146 color: #2c3e50;
147 font-family: 'Monaco', 'Courier New', monospace;
148 font-size: 0.9rem;
149 word-break: break-all;
150 }
151
152 .app-info-value a {
153 color: #3498db;
154 text-decoration: none;
155 }
156
157 .app-info-value a:hover {
158 text-decoration: underline;
159 }
160
161 .config-buttons {
162 margin-top: 1rem;
163 padding-top: 1rem;
164 border-top: 1px solid #ecf0f1;
165 display: flex;
166 gap: 0.5rem;
167 flex-wrap: wrap;
168 }
169
170 .config-btn {
171 padding: 0.4rem 0.8rem;
172 background: #3498db;
173 color: white;
174 border: none;
175 border-radius: 4px;
176 font-size: 0.8rem;
177 cursor: pointer;
178 transition: background 0.2s;
179 }
180
181 .config-btn:hover {
182 background: #2980b9;
183 }
184
185 .config-btn.secondary {
186 background: #95a5a6;
187 }
188
189 .config-btn.secondary:hover {
190 background: #7f8c8d;
191 }
192
193 .modal {
194 display: none;
195 position: fixed;
196 z-index: 1000;
197 left: 0;
198 top: 0;
199 width: 100%;
200 height: 100%;
201 overflow: auto;
202 background-color: rgba(0,0,0,0.6);
203 }
204
205 .modal.active {
206 display: block;
207 }
208
209 .modal-content {
210 background-color: #fefefe;
211 margin: 5% auto;
212 padding: 0;
213 border-radius: 8px;
214 width: 90%;
215 max-width: 900px;
216 max-height: 80vh;
217 display: flex;
218 flex-direction: column;
219 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
220 }
221
222 .modal-header {
223 padding: 1.5rem;
224 border-bottom: 1px solid #ecf0f1;
225 display: flex;
226 justify-content: space-between;
227 align-items: center;
228 }
229
230 .modal-header h3 {
231 margin: 0;
232 color: #2c3e50;
233 }
234
235 .modal-path {
236 font-family: 'Monaco', 'Courier New', monospace;
237 font-size: 0.85rem;
238 color: #7f8c8d;
239 margin-top: 0.25rem;
240 }
241
242 .close {
243 color: #aaa;
244 font-size: 28px;
245 font-weight: bold;
246 cursor: pointer;
247 line-height: 1;
248 }
249
250 .close:hover {
251 color: #000;
252 }
253
254 .modal-body {
255 padding: 1.5rem;
256 overflow: auto;
257 flex: 1;
258 }
259
260 .config-content {
261 background: #282c34;
262 color: #abb2bf;
263 padding: 1rem;
264 border-radius: 4px;
265 font-family: 'Monaco', 'Courier New', monospace;
266 font-size: 0.85rem;
267 line-height: 1.5;
268 white-space: pre-wrap;
269 word-wrap: break-word;
270 overflow-x: auto;
271 text-align: left;
272 }
273
274 .loading {
275 text-align: center;
276 padding: 2rem;
277 color: #7f8c8d;
278 }
279
280 .refresh-info {
281 text-align: center;
282 color: #7f8c8d;
283 font-size: 0.9rem;
284 margin-top: 2rem;
285 padding: 1rem;
286 }
287 </style>
288</head>
289<body>
290 <header>
291 <h1>Deploy Web UI</h1>
292 <p>Manage your VPS deployments</p>
293 </header>
294
295 <div class="container">
296 {{if not .Hosts}}
297 <div class="empty-state">
298 <h2>No deployments found</h2>
299 <p>Use the CLI to deploy your first app or static site</p>
300 </div>
301 {{else}}
302 {{range .Hosts}}
303 <div class="host-section">
304 <div class="host-header">
305 <h2>{{.Host}}</h2>
306 </div>
307 <div class="apps-grid">
308 {{range .Apps}}
309 <div class="app-card">
310 <div class="app-header">
311 <div class="app-name">{{.Name}}</div>
312 <div class="app-type {{.Type}}">{{.Type}}</div>
313 </div>
314
315 <div class="app-info">
316 <div class="app-info-label">Domain</div>
317 <div class="app-info-value">
318 <a href="https://{{.Domain}}" target="_blank">{{.Domain}}</a>
319 </div>
320 </div>
321
322 {{if eq .Type "app"}}
323 <div class="app-info">
324 <div class="app-info-label">Port</div>
325 <div class="app-info-value">{{.Port}}</div>
326 </div>
327 {{end}}
328
329 <div class="config-buttons">
330 {{if eq .Type "app"}}
331 <button class="config-btn" onclick="showConfig('{{.Host}}', '{{.Name}}', 'systemd')">Systemd Unit</button>
332 {{end}}
333 <button class="config-btn" onclick="showConfig('{{.Host}}', '{{.Name}}', 'caddy')">Caddy Config</button>
334 {{if .Env}}
335 <button class="config-btn secondary" onclick="showConfig('{{.Host}}', '{{.Name}}', 'env')">Environment</button>
336 {{end}}
337 </div>
338 </div>
339 {{end}}
340 </div>
341 </div>
342 {{end}}
343 {{end}}
344
345 <div class="refresh-info">
346 Refresh the page to see latest changes
347 </div>
348 </div>
349
350 <!-- Modal -->
351 <div id="configModal" class="modal">
352 <div class="modal-content">
353 <div class="modal-header">
354 <div>
355 <h3 id="modalTitle">Configuration</h3>
356 <div class="modal-path" id="modalPath"></div>
357 </div>
358 <span class="close" onclick="closeModal()">&times;</span>
359 </div>
360 <div class="modal-body">
361 <div id="modalContent" class="loading">Loading...</div>
362 </div>
363 </div>
364 </div>
365
366 <script>
367 const modal = document.getElementById('configModal');
368 const modalTitle = document.getElementById('modalTitle');
369 const modalPath = document.getElementById('modalPath');
370 const modalContent = document.getElementById('modalContent');
371
372 function closeModal() {
373 modal.classList.remove('active');
374 }
375
376 window.onclick = function(event) {
377 if (event.target == modal) {
378 closeModal();
379 }
380 }
381
382 async function showConfig(host, app, type) {
383 modal.classList.add('active');
384 modalContent.innerHTML = '<div class="loading">Loading...</div>';
385
386 const titles = {
387 'systemd': 'Systemd Service Unit',
388 'caddy': 'Caddy Configuration',
389 'env': 'Environment Variables'
390 };
391
392 modalTitle.textContent = titles[type];
393
394 try {
395 const response = await fetch(`/api/configs?host=${encodeURIComponent(host)}&app=${encodeURIComponent(app)}`);
396 if (!response.ok) {
397 throw new Error(`HTTP error! status: ${response.status}`);
398 }
399 const configs = await response.json();
400
401 let content = '';
402 let path = '';
403
404 switch(type) {
405 case 'systemd':
406 content = configs.systemd || 'No systemd config available';
407 path = configs.systemdPath || '';
408 break;
409 case 'caddy':
410 content = configs.caddy || 'No Caddy config available';
411 path = configs.caddyPath || '';
412 break;
413 case 'env':
414 content = configs.env || 'No environment variables';
415 path = configs.envPath || '';
416 break;
417 }
418
419 modalPath.textContent = path;
420 modalContent.innerHTML = `<div class="config-content">${escapeHtml(content)}</div>`;
421 } catch (error) {
422 modalContent.innerHTML = `<div class="loading">Error loading config: ${error.message}</div>`;
423 }
424 }
425
426 function escapeHtml(text) {
427 const div = document.createElement('div');
428 div.textContent = text;
429 return div.innerHTML;
430 }
431
432 // Close modal with Escape key
433 document.addEventListener('keydown', function(event) {
434 if (event.key === 'Escape') {
435 closeModal();
436 }
437 });
438 </script>
439</body>
440</html>