go-duck-cli 1.4.10 → 1.4.13
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/generators/kratos.js
CHANGED
|
@@ -37,12 +37,14 @@ export const generateKratosCode = async (entities, projectRootDir, projectName,
|
|
|
37
37
|
'integer': 'int32',
|
|
38
38
|
'long': 'int64',
|
|
39
39
|
'float': 'float',
|
|
40
|
+
'double': 'double',
|
|
40
41
|
'bigdecimal': 'double',
|
|
41
42
|
'bool': 'bool',
|
|
42
43
|
'boolean': 'bool',
|
|
43
44
|
'localdate': 'string',
|
|
44
45
|
'instant': 'string',
|
|
45
46
|
'datetime': 'string',
|
|
47
|
+
'time': 'string',
|
|
46
48
|
'text': 'string',
|
|
47
49
|
'json': 'string',
|
|
48
50
|
'jsonb': 'string'
|
|
@@ -62,7 +64,8 @@ export const generateKratosCode = async (entities, projectRootDir, projectName,
|
|
|
62
64
|
'int': 'int',
|
|
63
65
|
'integer': 'int',
|
|
64
66
|
'long': 'int64',
|
|
65
|
-
'float': '
|
|
67
|
+
'float': 'float32',
|
|
68
|
+
'double': 'float64',
|
|
66
69
|
'bigdecimal': 'float64',
|
|
67
70
|
'bool': 'bool',
|
|
68
71
|
'boolean': 'bool',
|
|
@@ -82,6 +85,7 @@ export const generateKratosCode = async (entities, projectRootDir, projectName,
|
|
|
82
85
|
'integer': 'int32',
|
|
83
86
|
'long': 'int64',
|
|
84
87
|
'float': 'float32',
|
|
88
|
+
'double': 'float64',
|
|
85
89
|
'bigdecimal': 'float64',
|
|
86
90
|
'json': 'string',
|
|
87
91
|
'jsonb': 'string'
|
package/generators/telemetry.js
CHANGED
|
@@ -262,7 +262,7 @@ func getContainerMemoryLimit() uint64 {
|
|
|
262
262
|
func getCgroupCPUUsageNs() (uint64, error) {
|
|
263
263
|
// try v2
|
|
264
264
|
if data, err := os.ReadFile("/sys/fs/cgroup/cpu.stat"); err == nil {
|
|
265
|
-
lines := strings.Split(string(data), "
|
|
265
|
+
lines := strings.Split(string(data), "\\n")
|
|
266
266
|
for _, line := range lines {
|
|
267
267
|
if strings.HasPrefix(line, "usage_usec") {
|
|
268
268
|
parts := strings.Fields(line)
|
package/package.json
CHANGED
|
@@ -40,6 +40,227 @@ import (
|
|
|
40
40
|
"{{app_name}}/internal/search"
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
+
const compactWidgetHTML = `<!DOCTYPE html>
|
|
44
|
+
<html lang="en">
|
|
45
|
+
<head>
|
|
46
|
+
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
47
|
+
<title>Live Telemetry</title>
|
|
48
|
+
<style>
|
|
49
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: #121212; color: #fff; font-family: system-ui, sans-serif; overflow: hidden; }
|
|
50
|
+
.bento-grid { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr; gap: 8px; width: 100%; height: 100%; padding: 8px; box-sizing: border-box; }
|
|
51
|
+
.bento-card { background: #1e1e1e; border-radius: 12px; display: flex; flex-direction: column; justify-content: center; align-items: center; position: relative; border: 1px solid #333; transition: all 0.2s; }
|
|
52
|
+
.bento-title { position: absolute; top: 10px; left: 10px; font-size: 0.75rem; color: #888; text-transform: uppercase; letter-spacing: 1px; }
|
|
53
|
+
.bento-badge { position: absolute; top: 10px; right: 10px; font-size: 0.6rem; padding: 2px 6px; border-radius: 4px; background: #e74c3c; color: #fff; display: none; }
|
|
54
|
+
.bento-value { font-size: 2.5rem; font-weight: 700; color: #fff; margin-top: 15px; display: flex; align-items: baseline; gap: 4px; }
|
|
55
|
+
.bento-unit { font-size: 1rem; color: #888; font-weight: normal; }
|
|
56
|
+
.bento-sub { font-size: 0.75rem; color: #666; margin-top: 5px; }
|
|
57
|
+
|
|
58
|
+
@keyframes alarmFlash {
|
|
59
|
+
0% { background: #1e1e1e; border-color: #333; }
|
|
60
|
+
50% { background: rgba(231, 76, 60, 0.2); border-color: #e74c3c; box-shadow: 0 0 15px rgba(231, 76, 60, 0.4); }
|
|
61
|
+
100% { background: #1e1e1e; border-color: #333; }
|
|
62
|
+
}
|
|
63
|
+
.alarm-active { animation: alarmFlash 1s infinite; }
|
|
64
|
+
</style>
|
|
65
|
+
</head>
|
|
66
|
+
<body>
|
|
67
|
+
<div class="bento-grid">
|
|
68
|
+
<div class="bento-card" id="card-cpu">
|
|
69
|
+
<div class="bento-title" id="cpu-title">CPU</div>
|
|
70
|
+
<div class="bento-badge" id="cpu-badge">QUOTA</div>
|
|
71
|
+
<div class="bento-value"><span id="cpu-val">0</span><span class="bento-unit">%</span></div>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="bento-card" id="card-mem">
|
|
74
|
+
<div class="bento-title">RAM</div>
|
|
75
|
+
<div class="bento-value"><span id="mem-val">0</span><span class="bento-unit">%</span></div>
|
|
76
|
+
<div class="bento-sub"><span id="mem-raw">0 / 0 MB</span></div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<script>
|
|
80
|
+
// Apply custom background color if provided by Mission Control grid
|
|
81
|
+
const bg = new URLSearchParams(window.location.search).get('bg');
|
|
82
|
+
if (bg) {
|
|
83
|
+
document.body.style.background = bg;
|
|
84
|
+
document.querySelectorAll('.bento-card').forEach(c => {
|
|
85
|
+
c.style.background = 'transparent';
|
|
86
|
+
c.style.borderColor = 'rgba(255,255,255,0.1)';
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const evtSource = new EventSource('/api/system/stream');
|
|
91
|
+
evtSource.addEventListener('metrics', function(e) {
|
|
92
|
+
const data = JSON.parse(e.data);
|
|
93
|
+
const sys = data.system;
|
|
94
|
+
|
|
95
|
+
// CPU
|
|
96
|
+
let cpuPct = 0;
|
|
97
|
+
if (sys.is_pod_cpu_limited) {
|
|
98
|
+
document.getElementById('cpu-title').innerText = 'CPU';
|
|
99
|
+
document.getElementById('cpu-badge').style.display = 'block';
|
|
100
|
+
cpuPct = sys.pod_cpu_limit_pct;
|
|
101
|
+
} else {
|
|
102
|
+
document.getElementById('cpu-title').innerText = 'PROCESS CPU';
|
|
103
|
+
document.getElementById('cpu-badge').style.display = 'none';
|
|
104
|
+
cpuPct = sys.process_cpu_usage;
|
|
105
|
+
}
|
|
106
|
+
document.getElementById('cpu-val').innerText = cpuPct.toFixed(1);
|
|
107
|
+
|
|
108
|
+
if (cpuPct > 90) document.getElementById('card-cpu').classList.add('alarm-active');
|
|
109
|
+
else document.getElementById('card-cpu').classList.remove('alarm-active');
|
|
110
|
+
|
|
111
|
+
// Memory
|
|
112
|
+
let memPct = 0;
|
|
113
|
+
if (sys.total_mem_mb > 0) {
|
|
114
|
+
memPct = (sys.heap_alloc_mb / sys.total_mem_mb) * 100;
|
|
115
|
+
}
|
|
116
|
+
document.getElementById('mem-val').innerText = memPct.toFixed(1);
|
|
117
|
+
document.getElementById('mem-raw').innerText = sys.heap_alloc_mb + " / " + sys.total_mem_mb + " MB";
|
|
118
|
+
|
|
119
|
+
if (memPct > 90) document.getElementById('card-mem').classList.add('alarm-active');
|
|
120
|
+
else document.getElementById('card-mem').classList.remove('alarm-active');
|
|
121
|
+
});
|
|
122
|
+
evtSource.onerror = function() {
|
|
123
|
+
document.getElementById('cpu-val').innerText = "OFF";
|
|
124
|
+
document.getElementById('mem-val').innerText = "OFF";
|
|
125
|
+
document.getElementById('mem-raw').innerText = "Disconnected";
|
|
126
|
+
};
|
|
127
|
+
</script>
|
|
128
|
+
</body>
|
|
129
|
+
</html>`
|
|
130
|
+
|
|
131
|
+
const gridWidgetHTML = `<!DOCTYPE html>
|
|
132
|
+
<html lang="en">
|
|
133
|
+
<head>
|
|
134
|
+
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
135
|
+
<title>Mission Control Grid</title>
|
|
136
|
+
<style>
|
|
137
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: #000; font-family: system-ui, sans-serif; overflow: auto; }
|
|
138
|
+
.dashboard { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); grid-auto-rows: minmax(30vh, 1fr); gap: 15px; padding: 15px; box-sizing: border-box; }
|
|
139
|
+
.grid-item { position: relative; width: 100%; height: 100%; background: #1a1a1a; border-radius: 12px; border: 1px solid #333; overflow: hidden; display: flex; flex-direction: column; transition: background 0.3s; }
|
|
140
|
+
.grid-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: rgba(0,0,0,0.4); border-bottom: 1px solid rgba(255,255,255,0.1); }
|
|
141
|
+
.grid-title { font-size: 0.85rem; font-weight: bold; color: #fff; text-transform: uppercase; letter-spacing: 1px; }
|
|
142
|
+
.grid-actions { display: flex; gap: 8px; align-items: center; }
|
|
143
|
+
.color-picker { width: 20px; height: 20px; padding: 0; border: none; border-radius: 4px; cursor: pointer; background: transparent; }
|
|
144
|
+
.close-btn { background: transparent; color: #888; border: none; font-size: 1.2rem; cursor: pointer; line-height: 1; transition: color 0.2s; }
|
|
145
|
+
.close-btn:hover { color: #e74c3c; }
|
|
146
|
+
.grid-item iframe { width: 100%; flex: 1; border: none; }
|
|
147
|
+
|
|
148
|
+
.add-item { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; transition: background 0.2s; color: #888; border: 2px dashed #444; border-radius: 12px; }
|
|
149
|
+
.add-item:hover { background: #222; color: #fff; border-color: #666; }
|
|
150
|
+
.add-icon { font-size: 3rem; font-weight: 300; margin-bottom: 10px; }
|
|
151
|
+
</style>
|
|
152
|
+
</head>
|
|
153
|
+
<body>
|
|
154
|
+
<div class="dashboard" id="dashboard">
|
|
155
|
+
<div class="add-item" id="add-btn" onclick="promptAddService()">
|
|
156
|
+
<div class="add-icon">+</div>
|
|
157
|
+
<div>Add Service</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
<script>
|
|
161
|
+
let gridState = JSON.parse(localStorage.getItem('goduck_mission_control')) || [];
|
|
162
|
+
|
|
163
|
+
function saveState() {
|
|
164
|
+
localStorage.setItem('goduck_mission_control', JSON.stringify(gridState));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function renderGrid() {
|
|
168
|
+
// Clear all grid items except add button
|
|
169
|
+
document.querySelectorAll('.grid-item').forEach(el => el.remove());
|
|
170
|
+
|
|
171
|
+
// Add default widget if state is completely empty
|
|
172
|
+
if (gridState.length === 0) {
|
|
173
|
+
gridState.push({ id: 'default', name: '{{app_name}} (Host)', url: '/api/system/widget', color: '#1a1a1a' });
|
|
174
|
+
saveState();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const dash = document.getElementById('dashboard');
|
|
178
|
+
const addBtn = document.getElementById('add-btn');
|
|
179
|
+
|
|
180
|
+
gridState.forEach(widget => {
|
|
181
|
+
const wrapper = document.createElement('div');
|
|
182
|
+
wrapper.className = 'grid-item';
|
|
183
|
+
wrapper.style.backgroundColor = widget.color;
|
|
184
|
+
|
|
185
|
+
const header = document.createElement('div');
|
|
186
|
+
header.className = 'grid-header';
|
|
187
|
+
|
|
188
|
+
const title = document.createElement('div');
|
|
189
|
+
title.className = 'grid-title';
|
|
190
|
+
title.innerText = widget.name;
|
|
191
|
+
|
|
192
|
+
const actions = document.createElement('div');
|
|
193
|
+
actions.className = 'grid-actions';
|
|
194
|
+
|
|
195
|
+
const colorPicker = document.createElement('input');
|
|
196
|
+
colorPicker.type = 'color';
|
|
197
|
+
colorPicker.className = 'color-picker';
|
|
198
|
+
colorPicker.value = widget.color || '#1a1a1a';
|
|
199
|
+
colorPicker.title = 'Change Widget Color';
|
|
200
|
+
colorPicker.onchange = (e) => {
|
|
201
|
+
widget.color = e.target.value;
|
|
202
|
+
saveState();
|
|
203
|
+
renderGrid(); // Re-render to pass new color to iframe
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const closeBtn = document.createElement('button');
|
|
207
|
+
closeBtn.className = 'close-btn';
|
|
208
|
+
closeBtn.innerHTML = '×';
|
|
209
|
+
closeBtn.title = 'Remove Widget';
|
|
210
|
+
closeBtn.onclick = () => {
|
|
211
|
+
if(confirm("Remove " + widget.name + " from Mission Control?")) {
|
|
212
|
+
gridState = gridState.filter(w => w.id !== widget.id);
|
|
213
|
+
saveState();
|
|
214
|
+
renderGrid();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
actions.appendChild(colorPicker);
|
|
219
|
+
actions.appendChild(closeBtn);
|
|
220
|
+
header.appendChild(title);
|
|
221
|
+
header.appendChild(actions);
|
|
222
|
+
|
|
223
|
+
const iframe = document.createElement('iframe');
|
|
224
|
+
// Append color parameter to URL to theme the iframe natively
|
|
225
|
+
try {
|
|
226
|
+
const urlObj = new URL(widget.url, window.location.origin);
|
|
227
|
+
urlObj.searchParams.set('bg', widget.color);
|
|
228
|
+
iframe.src = urlObj.toString();
|
|
229
|
+
} catch (e) {
|
|
230
|
+
iframe.src = widget.url; // Fallback if URL is invalid
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
wrapper.appendChild(header);
|
|
234
|
+
wrapper.appendChild(iframe);
|
|
235
|
+
|
|
236
|
+
dash.insertBefore(wrapper, addBtn);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function promptAddService() {
|
|
241
|
+
const name = prompt("Enter a Name for this Service (e.g., 'Auth Service'):");
|
|
242
|
+
if (!name) return;
|
|
243
|
+
|
|
244
|
+
const url = prompt("Enter the remote widget URL:\\n(e.g., http://localhost:8081/api/system/widget)");
|
|
245
|
+
if (!url) return;
|
|
246
|
+
|
|
247
|
+
gridState.push({
|
|
248
|
+
id: Date.now().toString(),
|
|
249
|
+
name: name,
|
|
250
|
+
url: url,
|
|
251
|
+
color: '#1a1a1a'
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
saveState();
|
|
255
|
+
renderGrid();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Initial render
|
|
259
|
+
renderGrid();
|
|
260
|
+
</script>
|
|
261
|
+
</body>
|
|
262
|
+
</html>`
|
|
263
|
+
|
|
43
264
|
func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
44
265
|
// 1. Initialize Logging & Observability
|
|
45
266
|
logger.InitLogger(appConfig)
|
|
@@ -232,45 +453,39 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
232
453
|
c.Header("Connection", "keep-alive")
|
|
233
454
|
c.Header("Access-Control-Allow-Origin", "*")
|
|
234
455
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
interval = time.Second
|
|
238
|
-
}
|
|
456
|
+
clientChan := make(chan telemetry.AppMetrics, 100)
|
|
457
|
+
telemetry.GlobalMetricsBroker.AddClient(clientChan)
|
|
239
458
|
|
|
240
|
-
|
|
241
|
-
|
|
459
|
+
defer func() {
|
|
460
|
+
telemetry.GlobalMetricsBroker.RemoveClient(clientChan)
|
|
461
|
+
}()
|
|
242
462
|
|
|
243
463
|
c.Stream(func(w io.Writer) bool {
|
|
244
464
|
select {
|
|
245
465
|
case <-c.Request.Context().Done():
|
|
246
466
|
return false
|
|
247
|
-
case <-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Simple load calculation
|
|
257
|
-
loadPct := (cpuUsage + memStats.UsedPercent) / 2.0
|
|
258
|
-
|
|
259
|
-
payload := map[string]interface{}{
|
|
260
|
-
"timestamp": time.Now().Format(time.RFC3339),
|
|
261
|
-
"cpu_percent": cpuUsage,
|
|
262
|
-
"mem_percent": memStats.UsedPercent,
|
|
263
|
-
"mem_used_mb": memStats.Used / 1024 / 1024,
|
|
264
|
-
"mem_total_mb": memStats.Total / 1024 / 1024,
|
|
265
|
-
"load_percentage": loadPct,
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
data, _ := json.Marshal(payload)
|
|
269
|
-
c.SSEvent("message", string(data))
|
|
467
|
+
case appMetrics := <-clientChan:
|
|
468
|
+
sysMetrics := telemetry.CollectSystemMetrics()
|
|
469
|
+
c.SSEvent("metrics", gin.H{
|
|
470
|
+
"system": sysMetrics,
|
|
471
|
+
"endpoints": appMetrics.Endpoints,
|
|
472
|
+
"status_codes": appMetrics.StatusCodes,
|
|
473
|
+
"failed_calls": appMetrics.FailedCalls,
|
|
474
|
+
})
|
|
270
475
|
return true
|
|
271
476
|
}
|
|
272
477
|
})
|
|
273
478
|
})
|
|
479
|
+
|
|
480
|
+
// Compact Bento Telemetry Widget
|
|
481
|
+
r.GET("/api/system/widget", func(c *gin.Context) {
|
|
482
|
+
c.Data(200, "text/html; charset=utf-8", []byte(compactWidgetHTML))
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
// Mission Control Iframe Grid
|
|
486
|
+
r.GET("/api/system/grid", func(c *gin.Context) {
|
|
487
|
+
c.Data(200, "text/html; charset=utf-8", []byte(gridWidgetHTML))
|
|
488
|
+
})
|
|
274
489
|
}
|
|
275
490
|
|
|
276
491
|
// Swagger Docs & UI
|
|
@@ -293,6 +508,15 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
293
508
|
*, *:before, *:after { box-sizing: inherit; }
|
|
294
509
|
body { margin:0; background: #fafafa; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
|
295
510
|
|
|
511
|
+
.nav-trigger-zone {
|
|
512
|
+
position: fixed;
|
|
513
|
+
top: 0;
|
|
514
|
+
left: 0;
|
|
515
|
+
right: 0;
|
|
516
|
+
height: 30px;
|
|
517
|
+
z-index: 999;
|
|
518
|
+
}
|
|
519
|
+
|
|
296
520
|
.top-nav {
|
|
297
521
|
background: rgba(255, 255, 255, 0.8);
|
|
298
522
|
backdrop-filter: blur(10px);
|
|
@@ -302,11 +526,20 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
302
526
|
display: flex;
|
|
303
527
|
justify-content: space-between;
|
|
304
528
|
align-items: center;
|
|
305
|
-
position:
|
|
306
|
-
|
|
529
|
+
position: fixed;
|
|
530
|
+
width: 100%;
|
|
531
|
+
top: -100px;
|
|
532
|
+
opacity: 0;
|
|
533
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
307
534
|
z-index: 1000;
|
|
308
535
|
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
|
|
309
536
|
}
|
|
537
|
+
|
|
538
|
+
.nav-trigger-zone:hover + .top-nav,
|
|
539
|
+
.top-nav:hover {
|
|
540
|
+
top: 0;
|
|
541
|
+
opacity: 1;
|
|
542
|
+
}
|
|
310
543
|
|
|
311
544
|
.nav-brand {
|
|
312
545
|
font-weight: 700;
|
|
@@ -579,7 +812,8 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
579
812
|
.log-line.hidden { display: none; }
|
|
580
813
|
</style>
|
|
581
814
|
</head>
|
|
582
|
-
<body>
|
|
815
|
+
<body id="swagger-body">
|
|
816
|
+
<div class="nav-trigger-zone"></div>
|
|
583
817
|
<div class="top-nav">
|
|
584
818
|
<div class="nav-brand">
|
|
585
819
|
<img src="/logo.png" alt="GO-DUCK Logo" style="height: 32px; width: auto;" />
|
|
@@ -600,6 +834,7 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
600
834
|
<button id="logout-btn" class="btn btn-logout">Logout</button>
|
|
601
835
|
<button id="mqtt-btn" class="btn" style="background: #2c3e50; color: white;">MQTT Topics</button>
|
|
602
836
|
<button id="sys-metrics-btn" class="btn" style="background: #8e44ad; color: white;">System Metrics</button>
|
|
837
|
+
<button id="compact-widget-btn" class="btn" style="background: #27ae60; color: white; margin-right: 5px;" title="Open Mission Control Grid">Mission Control</button>
|
|
603
838
|
<button id="live-logs-btn" class="btn" style="background: #d35400; color: white;" title="Stream Server Console Logs">Live Logs</button>
|
|
604
839
|
</div>
|
|
605
840
|
</div>
|
|
@@ -1305,7 +1540,7 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
1305
1540
|
}
|
|
1306
1541
|
|
|
1307
1542
|
document.getElementById('live-logs-btn').onclick = () => {
|
|
1308
|
-
if (!keycloak.authenticated) {
|
|
1543
|
+
if (!keycloak.authenticated && {{appConfig.GoDuck.Security.OIDC.Enabled}}) {
|
|
1309
1544
|
alert('Please login to view Server Logs');
|
|
1310
1545
|
return;
|
|
1311
1546
|
}
|
|
@@ -1317,13 +1552,13 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
1317
1552
|
// Connect SSE
|
|
1318
1553
|
logsEventSource = new EventSource('/api/system/logs/stream');
|
|
1319
1554
|
|
|
1320
|
-
logsEventSource.
|
|
1555
|
+
logsEventSource.addEventListener('log', function(event) {
|
|
1321
1556
|
// Remove connecting message on first log
|
|
1322
1557
|
if (logBody.children.length === 1 && logBody.firstChild.innerText.includes('Connecting')) {
|
|
1323
1558
|
logBody.innerHTML = '';
|
|
1324
1559
|
}
|
|
1325
1560
|
appendLogLine(event.data);
|
|
1326
|
-
};
|
|
1561
|
+
});
|
|
1327
1562
|
|
|
1328
1563
|
logsEventSource.onerror = function() {
|
|
1329
1564
|
appendLogLine('[SSE Error] Disconnected from log stream.');
|
|
@@ -1331,6 +1566,14 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
|
|
|
1331
1566
|
};
|
|
1332
1567
|
};
|
|
1333
1568
|
|
|
1569
|
+
document.getElementById('compact-widget-btn').onclick = () => {
|
|
1570
|
+
if (!keycloak.authenticated && {{appConfig.GoDuck.Security.OIDC.Enabled}}) {
|
|
1571
|
+
alert('Please login to view system telemetry.');
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
window.open('/api/system/grid', 'MissionControlGrid', 'width=800,height=600,menubar=no,toolbar=no,location=no,status=no');
|
|
1575
|
+
};
|
|
1576
|
+
|
|
1334
1577
|
document.getElementById('live-logs-close').onclick = () => {
|
|
1335
1578
|
liveLogsModal.style.display = 'none';
|
|
1336
1579
|
if (logsEventSource) {
|
|
@@ -65,10 +65,14 @@ func (s *{{capitalize name}}Service) Create{{capitalize name}}(ctx context.Conte
|
|
|
65
65
|
{{capitalize name}}: parseInstant(req.{{toProtoFieldName name}}),
|
|
66
66
|
{{else if (eq (toLowerCase type) "datetime")}}
|
|
67
67
|
{{capitalize name}}: parseInstant(req.{{toProtoFieldName name}}),
|
|
68
|
+
{{else if (eq (toLowerCase type) "time")}}
|
|
69
|
+
{{capitalize name}}: parseInstant(req.{{toProtoFieldName name}}),
|
|
68
70
|
{{else if (isJson type)}}
|
|
69
71
|
{{capitalize name}}: datatypes.JSON(req.{{toProtoFieldName name}}),
|
|
70
72
|
{{else if isNested}}
|
|
71
73
|
// Nested fields are mapped via JSON for simplicity in gRPC
|
|
74
|
+
{{else if (eq (toLowerCase type) "bigdecimal")}}
|
|
75
|
+
{{capitalize name}}: decimal.NewFromFloat(req.{{toProtoFieldName name}}),
|
|
72
76
|
{{else}}
|
|
73
77
|
{{capitalize name}}: {{#if (toGoCast type)}}{{toGoCast type}}(req.{{toProtoFieldName name}}){{else}}req.{{toProtoFieldName name}}{{/if}},
|
|
74
78
|
{{/if}}
|
|
@@ -179,10 +183,14 @@ func (s *{{capitalize name}}Service) Update{{capitalize name}}(ctx context.Conte
|
|
|
179
183
|
entity.{{capitalize name}} = parseInstant(req.{{toProtoFieldName name}})
|
|
180
184
|
{{else if (eq (toLowerCase type) "datetime")}}
|
|
181
185
|
entity.{{capitalize name}} = parseInstant(req.{{toProtoFieldName name}})
|
|
186
|
+
{{else if (eq (toLowerCase type) "time")}}
|
|
187
|
+
entity.{{capitalize name}} = parseInstant(req.{{toProtoFieldName name}})
|
|
182
188
|
{{else if (isJson type)}}
|
|
183
189
|
entity.{{capitalize name}} = datatypes.JSON(req.{{toProtoFieldName name}})
|
|
184
190
|
{{else if isNested}}
|
|
185
191
|
json.Unmarshal([]byte(req.{{toProtoFieldName name}}), &entity.{{capitalize name}})
|
|
192
|
+
{{else if (eq (toLowerCase type) "bigdecimal")}}
|
|
193
|
+
entity.{{capitalize name}} = decimal.NewFromFloat(req.{{toProtoFieldName name}})
|
|
186
194
|
{{else}}
|
|
187
195
|
entity.{{capitalize name}} = {{#if (toGoCast type)}}{{toGoCast type}}(req.{{toProtoFieldName name}}){{else}}req.{{toProtoFieldName name}}{{/if}}
|
|
188
196
|
{{/if}}
|
|
@@ -394,10 +402,14 @@ func map{{capitalize name}}ToPb(m *models.{{capitalize name}}) *pb.{{capitalize
|
|
|
394
402
|
{{toProtoFieldName name}}: m.{{capitalize name}}.Format(time.RFC3339),
|
|
395
403
|
{{else if (eq (toLowerCase type) "datetime")}}
|
|
396
404
|
{{toProtoFieldName name}}: m.{{capitalize name}}.Format(time.RFC3339),
|
|
405
|
+
{{else if (eq (toLowerCase type) "time")}}
|
|
406
|
+
{{toProtoFieldName name}}: m.{{capitalize name}}.Format(time.RFC3339),
|
|
397
407
|
{{else if (isJson type)}}
|
|
398
408
|
{{toProtoFieldName name}}: string(m.{{capitalize name}}),
|
|
399
409
|
{{else if isNested}}
|
|
400
410
|
{{toProtoFieldName name}}: (func() string { b, _ := json.Marshal(m.{{capitalize name}}); return string(b) })(),
|
|
411
|
+
{{else if (eq (toLowerCase type) "bigdecimal")}}
|
|
412
|
+
{{toProtoFieldName name}}: m.{{capitalize name}}.InexactFloat64(),
|
|
401
413
|
{{else}}
|
|
402
414
|
{{toProtoFieldName name}}: {{#if (toProtoCast type)}}{{toProtoCast type}}(m.{{capitalize name}}){{else}}m.{{capitalize name}}{{/if}},
|
|
403
415
|
{{/if}}
|
|
@@ -65,8 +65,7 @@ func getContainerMemoryLimit() uint64 {
|
|
|
65
65
|
func getCgroupCPUUsageNs() (uint64, error) {
|
|
66
66
|
// try v2
|
|
67
67
|
if data, err := os.ReadFile("/sys/fs/cgroup/cpu.stat"); err == nil {
|
|
68
|
-
lines := strings.Split(string(data), "
|
|
69
|
-
")
|
|
68
|
+
lines := strings.Split(string(data), "\n")
|
|
70
69
|
for _, line := range lines {
|
|
71
70
|
if strings.HasPrefix(line, "usage_usec") {
|
|
72
71
|
parts := strings.Fields(line)
|