4runr-os 2.10.0 → 2.10.2
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/apps/gateway/dist/apps/gateway/src/db/init.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/init.js +8 -1
- package/apps/gateway/dist/apps/gateway/src/db/init.js.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/redis.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/db/redis.js +3 -2
- package/apps/gateway/dist/apps/gateway/src/db/redis.js.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/health/index.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/health/index.js +9 -8
- package/apps/gateway/dist/apps/gateway/src/health/index.js.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/index.js +30 -0
- package/apps/gateway/dist/apps/gateway/src/index.js.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/metrics/index.d.ts +9 -0
- package/apps/gateway/dist/apps/gateway/src/metrics/index.d.ts.map +1 -1
- package/apps/gateway/dist/apps/gateway/src/metrics/index.js +94 -0
- package/apps/gateway/dist/apps/gateway/src/metrics/index.js.map +1 -1
- package/apps/gateway/package-lock.json +220 -207
- package/apps/gateway/src/db/init.ts +14 -2
- package/apps/gateway/src/db/redis.ts +4 -2
- package/apps/gateway/src/health/index.ts +16 -14
- package/apps/gateway/src/index.ts +39 -0
- package/apps/gateway/src/metrics/index.ts +121 -0
- package/dist/gateway-client.d.ts +9 -0
- package/dist/gateway-client.d.ts.map +1 -1
- package/dist/gateway-client.js +32 -1
- package/dist/gateway-client.js.map +1 -1
- package/dist/gateway-observability.d.ts +4 -0
- package/dist/gateway-observability.d.ts.map +1 -1
- package/dist/gateway-observability.js +13 -1
- package/dist/gateway-observability.js.map +1 -1
- package/dist/tui-handlers.js +50 -5
- package/dist/tui-handlers.js.map +1 -1
- package/mk3-tui/src/app.rs +55 -1
- package/mk3-tui/src/main.rs +52 -6
- package/mk3-tui/src/ui/connection_portal.rs +12 -1
- package/mk3-tui/src/ui/portal_monitoring.rs +170 -36
- package/package.json +2 -2
|
@@ -12,7 +12,9 @@ const NEON_GREEN: Color = Color::Rgb(57, 255, 20);
|
|
|
12
12
|
const TEXT_DIM: Color = Color::Rgb(100, 100, 120);
|
|
13
13
|
const TEXT_PRIMARY: Color = Color::Rgb(230, 230, 240);
|
|
14
14
|
const TEXT_WARN: Color = Color::Rgb(255, 180, 80);
|
|
15
|
+
const TEXT_HEADER: Color = Color::Rgb(0, 200, 255);
|
|
15
16
|
const BG_PANEL: Color = Color::Rgb(18, 18, 25);
|
|
17
|
+
const SEPARATOR_COLOR: Color = Color::Rgb(60, 60, 80);
|
|
16
18
|
|
|
17
19
|
fn gateway_display_url(state: &AppState) -> String {
|
|
18
20
|
state
|
|
@@ -55,66 +57,198 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
|
|
|
55
57
|
f.render_widget(title, chunks[0]);
|
|
56
58
|
|
|
57
59
|
let url = gateway_display_url(state);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
|
|
61
|
+
// Build formatted content lines with styling
|
|
62
|
+
let mut body_lines: Vec<Line> = vec![];
|
|
63
|
+
|
|
64
|
+
// Gateway URL header
|
|
65
|
+
body_lines.push(Line::from(vec![
|
|
66
|
+
Span::styled("Linked Gateway: ", Style::default().fg(TEXT_DIM)),
|
|
67
|
+
Span::styled(url, Style::default().fg(CYBER_CYAN).bold()),
|
|
68
|
+
]));
|
|
69
|
+
body_lines.push(Line::from(""));
|
|
62
70
|
|
|
63
71
|
if state.portal_monitoring.loading && state.portal_monitoring.content_lines.is_empty() {
|
|
64
|
-
body_lines.push(
|
|
72
|
+
body_lines.push(Line::from(Span::styled(
|
|
73
|
+
"⏳ Fetching Prometheus /metrics from Gateway…",
|
|
74
|
+
Style::default().fg(TEXT_WARN),
|
|
75
|
+
)));
|
|
65
76
|
} else if let Some(ref err) = state.portal_monitoring.error {
|
|
66
|
-
body_lines.push(
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
body_lines.push(Line::from(vec![
|
|
78
|
+
Span::styled("✗ Error: ", Style::default().fg(Color::Red).bold()),
|
|
79
|
+
Span::styled(err, Style::default().fg(TEXT_PRIMARY)),
|
|
80
|
+
]));
|
|
81
|
+
body_lines.push(Line::from(""));
|
|
82
|
+
body_lines.push(Line::from(Span::styled(
|
|
83
|
+
"Press R to retry",
|
|
84
|
+
Style::default().fg(TEXT_DIM),
|
|
85
|
+
)));
|
|
69
86
|
} else if state.portal_monitoring.content_lines.is_empty() {
|
|
70
|
-
body_lines.push(
|
|
87
|
+
body_lines.push(Line::from(Span::styled(
|
|
88
|
+
"No snapshot yet. Press R to fetch /metrics.",
|
|
89
|
+
Style::default().fg(TEXT_DIM),
|
|
90
|
+
)));
|
|
71
91
|
} else {
|
|
72
|
-
|
|
92
|
+
// Parse and format content lines with enhanced readability
|
|
93
|
+
let mut in_section = false;
|
|
94
|
+
let mut section_name = "";
|
|
95
|
+
|
|
96
|
+
for raw_line in &state.portal_monitoring.content_lines {
|
|
97
|
+
let line_text = raw_line.trim();
|
|
98
|
+
|
|
99
|
+
// Detect section headers
|
|
100
|
+
if line_text.starts_with("Live link check")
|
|
101
|
+
|| line_text.starts_with("Dependency checks")
|
|
102
|
+
|| line_text.starts_with("Prometheus /metrics")
|
|
103
|
+
|| line_text.starts_with("Top HTTP routes") {
|
|
104
|
+
in_section = true;
|
|
105
|
+
section_name = line_text;
|
|
106
|
+
body_lines.push(Line::from(Span::styled(
|
|
107
|
+
format!("━━ {} ━━", line_text),
|
|
108
|
+
Style::default().fg(TEXT_HEADER).bold(),
|
|
109
|
+
)));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Empty lines
|
|
114
|
+
if line_text.is_empty() {
|
|
115
|
+
body_lines.push(Line::from(""));
|
|
116
|
+
in_section = false;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Special formatting for different line types
|
|
121
|
+
if line_text.starts_with("✓") || line_text.starts_with("/health OK") {
|
|
122
|
+
body_lines.push(Line::from(Span::styled(
|
|
123
|
+
line_text,
|
|
124
|
+
Style::default().fg(NEON_GREEN),
|
|
125
|
+
)));
|
|
126
|
+
} else if line_text.starts_with("⚠") || line_text.contains("degraded") || line_text.contains("not ready") {
|
|
127
|
+
body_lines.push(Line::from(Span::styled(
|
|
128
|
+
line_text,
|
|
129
|
+
Style::default().fg(TEXT_WARN),
|
|
130
|
+
)));
|
|
131
|
+
} else if line_text.starts_with(" •") {
|
|
132
|
+
// Dependency check lines - color based on status
|
|
133
|
+
let color = if line_text.contains(": up") {
|
|
134
|
+
NEON_GREEN
|
|
135
|
+
} else if line_text.contains(": down") {
|
|
136
|
+
Color::Red
|
|
137
|
+
} else {
|
|
138
|
+
TEXT_WARN
|
|
139
|
+
};
|
|
140
|
+
body_lines.push(Line::from(Span::styled(
|
|
141
|
+
line_text,
|
|
142
|
+
Style::default().fg(color),
|
|
143
|
+
)));
|
|
144
|
+
} else if line_text.starts_with(" ") {
|
|
145
|
+
// Indented content (route metrics, etc)
|
|
146
|
+
body_lines.push(Line::from(Span::styled(
|
|
147
|
+
line_text,
|
|
148
|
+
Style::default().fg(TEXT_PRIMARY),
|
|
149
|
+
)));
|
|
150
|
+
} else if line_text.contains("total") || line_text.contains("latency") || line_text.contains("created") {
|
|
151
|
+
// Metrics lines - highlight numbers
|
|
152
|
+
let parts: Vec<&str> = line_text.split_whitespace().collect();
|
|
153
|
+
if parts.len() >= 2 {
|
|
154
|
+
let (label, value) = line_text.split_at(line_text.rfind(char::is_whitespace).unwrap_or(line_text.len()));
|
|
155
|
+
body_lines.push(Line::from(vec![
|
|
156
|
+
Span::styled(format!("{} ", label.trim()), Style::default().fg(TEXT_DIM)),
|
|
157
|
+
Span::styled(value.trim(), Style::default().fg(CYBER_CYAN).bold()),
|
|
158
|
+
]));
|
|
159
|
+
} else {
|
|
160
|
+
body_lines.push(Line::from(Span::styled(
|
|
161
|
+
line_text,
|
|
162
|
+
Style::default().fg(TEXT_PRIMARY),
|
|
163
|
+
)));
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// Default text
|
|
167
|
+
body_lines.push(Line::from(Span::styled(
|
|
168
|
+
line_text,
|
|
169
|
+
Style::default().fg(TEXT_PRIMARY),
|
|
170
|
+
)));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
73
173
|
}
|
|
74
174
|
|
|
175
|
+
// Calculate scrolling
|
|
75
176
|
let total = body_lines.len();
|
|
76
177
|
let max_scroll = total.saturating_sub(body_inner_h.min(total.max(1)));
|
|
77
178
|
let start = state.portal_monitoring.scroll_offset.min(max_scroll);
|
|
78
179
|
let end = (start + body_inner_h).min(total);
|
|
79
|
-
let window: Vec<
|
|
80
|
-
let body_text = window.join("\n");
|
|
180
|
+
let window: Vec<Line> = body_lines[start..end].to_vec();
|
|
81
181
|
|
|
82
|
-
let body = Paragraph::new(
|
|
182
|
+
let body = Paragraph::new(window)
|
|
83
183
|
.wrap(Wrap { trim: true })
|
|
84
|
-
.style(Style::default().
|
|
184
|
+
.style(Style::default().bg(BG_PANEL))
|
|
85
185
|
.block(
|
|
86
186
|
Block::default()
|
|
87
|
-
.title(
|
|
88
|
-
|
|
187
|
+
.title(vec![
|
|
188
|
+
Span::styled(" ", Style::default()),
|
|
189
|
+
Span::styled("Traffic & Observability", Style::default().fg(NEON_GREEN).bold()),
|
|
190
|
+
Span::styled(" ", Style::default()),
|
|
191
|
+
])
|
|
89
192
|
.borders(Borders::ALL)
|
|
90
|
-
.border_style(Style::default().fg(
|
|
193
|
+
.border_style(Style::default().fg(SEPARATOR_COLOR))
|
|
91
194
|
.style(Style::default().bg(BG_PANEL)),
|
|
92
195
|
);
|
|
93
196
|
f.render_widget(body, chunks[1]);
|
|
94
197
|
|
|
95
|
-
// Footer
|
|
198
|
+
// Footer with better visual hierarchy
|
|
96
199
|
let status = if state.portal_monitoring.loading {
|
|
97
|
-
|
|
200
|
+
vec![
|
|
201
|
+
Span::styled("⏳ ", Style::default().fg(TEXT_WARN)),
|
|
202
|
+
Span::styled("loading", Style::default().fg(TEXT_WARN)),
|
|
203
|
+
]
|
|
204
|
+
} else {
|
|
205
|
+
vec![
|
|
206
|
+
Span::styled("● ", Style::default().fg(NEON_GREEN)),
|
|
207
|
+
Span::styled("live", Style::default().fg(NEON_GREEN)),
|
|
208
|
+
]
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Auto-refresh status
|
|
212
|
+
let auto_status = if state.portal_monitoring.auto_refresh_enabled {
|
|
213
|
+
Span::styled("auto ✓", Style::default().fg(NEON_GREEN))
|
|
98
214
|
} else {
|
|
99
|
-
Span::styled("
|
|
215
|
+
Span::styled("auto ✗", Style::default().fg(TEXT_DIM))
|
|
100
216
|
};
|
|
101
|
-
|
|
217
|
+
|
|
218
|
+
// Last refresh timer
|
|
219
|
+
let refresh_info = if let Some(last) = state.portal_monitoring.last_refresh {
|
|
220
|
+
let elapsed = last.elapsed().as_secs();
|
|
221
|
+
format!(" ~{}s ago", elapsed)
|
|
222
|
+
} else {
|
|
223
|
+
" never".to_string()
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
let mut footer_spans = vec![
|
|
102
227
|
Span::styled("ESC ", Style::default().fg(BRAND_PURPLE).bold()),
|
|
103
228
|
Span::styled("Main ", Style::default().fg(TEXT_DIM)),
|
|
104
|
-
Span::styled("│ ", Style::default().fg(
|
|
105
|
-
Span::styled("R", Style::default().fg(CYBER_CYAN).bold()),
|
|
106
|
-
Span::styled("
|
|
107
|
-
Span::styled("
|
|
108
|
-
Span::styled("
|
|
109
|
-
|
|
110
|
-
Span::styled("
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
);
|
|
229
|
+
Span::styled("│ ", Style::default().fg(SEPARATOR_COLOR)),
|
|
230
|
+
Span::styled("R ", Style::default().fg(CYBER_CYAN).bold()),
|
|
231
|
+
Span::styled("refresh ", Style::default().fg(TEXT_DIM)),
|
|
232
|
+
Span::styled("│ ", Style::default().fg(SEPARATOR_COLOR)),
|
|
233
|
+
Span::styled("A ", Style::default().fg(CYBER_CYAN).bold()),
|
|
234
|
+
Span::styled("toggle auto ", Style::default().fg(TEXT_DIM)),
|
|
235
|
+
Span::styled("│ ", Style::default().fg(SEPARATOR_COLOR)),
|
|
236
|
+
Span::styled("↑↓ ", Style::default().fg(CYBER_CYAN).bold()),
|
|
237
|
+
Span::styled("scroll ", Style::default().fg(TEXT_DIM)),
|
|
238
|
+
Span::styled("│ ", Style::default().fg(SEPARATOR_COLOR)),
|
|
239
|
+
];
|
|
240
|
+
footer_spans.extend(status);
|
|
241
|
+
footer_spans.push(Span::styled(refresh_info.as_str(), Style::default().fg(TEXT_DIM)));
|
|
242
|
+
footer_spans.push(Span::styled(" ", Style::default()));
|
|
243
|
+
footer_spans.push(auto_status);
|
|
244
|
+
|
|
245
|
+
let footer = Paragraph::new(Line::from(footer_spans))
|
|
246
|
+
.alignment(Alignment::Center)
|
|
247
|
+
.block(
|
|
248
|
+
Block::default()
|
|
249
|
+
.borders(Borders::ALL)
|
|
250
|
+
.border_style(Style::default().fg(SEPARATOR_COLOR))
|
|
251
|
+
.style(Style::default().bg(BG_PANEL)),
|
|
252
|
+
);
|
|
119
253
|
f.render_widget(footer, chunks[2]);
|
|
120
254
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4runr-os",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.10.
|
|
5
|
+
"description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.10.2: Portal Monitoring — auto-refresh, uptime/queue/memory metrics, /api/monitoring/summary; /health includes uptime+memory; TUI text-only (no decorative emoji). v2.10.1+ Docker Postgres+Redis in auto mode. See docs/4RUNR-DB-IMPLEMENTATION-PLAN.md, docs/MONITORING-IMPROVEMENTS-V1.md",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"4runr": "dist/index.js",
|