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.
Files changed (36) hide show
  1. package/apps/gateway/dist/apps/gateway/src/db/init.d.ts.map +1 -1
  2. package/apps/gateway/dist/apps/gateway/src/db/init.js +8 -1
  3. package/apps/gateway/dist/apps/gateway/src/db/init.js.map +1 -1
  4. package/apps/gateway/dist/apps/gateway/src/db/redis.d.ts.map +1 -1
  5. package/apps/gateway/dist/apps/gateway/src/db/redis.js +3 -2
  6. package/apps/gateway/dist/apps/gateway/src/db/redis.js.map +1 -1
  7. package/apps/gateway/dist/apps/gateway/src/health/index.d.ts.map +1 -1
  8. package/apps/gateway/dist/apps/gateway/src/health/index.js +9 -8
  9. package/apps/gateway/dist/apps/gateway/src/health/index.js.map +1 -1
  10. package/apps/gateway/dist/apps/gateway/src/index.js +30 -0
  11. package/apps/gateway/dist/apps/gateway/src/index.js.map +1 -1
  12. package/apps/gateway/dist/apps/gateway/src/metrics/index.d.ts +9 -0
  13. package/apps/gateway/dist/apps/gateway/src/metrics/index.d.ts.map +1 -1
  14. package/apps/gateway/dist/apps/gateway/src/metrics/index.js +94 -0
  15. package/apps/gateway/dist/apps/gateway/src/metrics/index.js.map +1 -1
  16. package/apps/gateway/package-lock.json +220 -207
  17. package/apps/gateway/src/db/init.ts +14 -2
  18. package/apps/gateway/src/db/redis.ts +4 -2
  19. package/apps/gateway/src/health/index.ts +16 -14
  20. package/apps/gateway/src/index.ts +39 -0
  21. package/apps/gateway/src/metrics/index.ts +121 -0
  22. package/dist/gateway-client.d.ts +9 -0
  23. package/dist/gateway-client.d.ts.map +1 -1
  24. package/dist/gateway-client.js +32 -1
  25. package/dist/gateway-client.js.map +1 -1
  26. package/dist/gateway-observability.d.ts +4 -0
  27. package/dist/gateway-observability.d.ts.map +1 -1
  28. package/dist/gateway-observability.js +13 -1
  29. package/dist/gateway-observability.js.map +1 -1
  30. package/dist/tui-handlers.js +50 -5
  31. package/dist/tui-handlers.js.map +1 -1
  32. package/mk3-tui/src/app.rs +55 -1
  33. package/mk3-tui/src/main.rs +52 -6
  34. package/mk3-tui/src/ui/connection_portal.rs +12 -1
  35. package/mk3-tui/src/ui/portal_monitoring.rs +170 -36
  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
- let mut body_lines: Vec<String> = vec![
59
- format!("Linked Gateway: {}", url),
60
- String::new(),
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("Fetching Prometheus /metrics from the Gateway…".to_string());
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(format!("Last error: {}", err));
67
- body_lines.push(String::new());
68
- body_lines.push("Press R to retry.".to_string());
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("No snapshot yet. Press R to fetch /metrics.".to_string());
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
- body_lines.extend(state.portal_monitoring.content_lines.iter().cloned());
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<String> = body_lines[start..end].to_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(body_text)
182
+ let body = Paragraph::new(window)
83
183
  .wrap(Wrap { trim: true })
84
- .style(Style::default().fg(TEXT_PRIMARY).bg(BG_PANEL))
184
+ .style(Style::default().bg(BG_PANEL))
85
185
  .block(
86
186
  Block::default()
87
- .title(" Traffic & observability ")
88
- .title_style(Style::default().fg(NEON_GREEN))
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(TEXT_DIM))
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
- Span::styled(" loading ", Style::default().fg(TEXT_WARN))
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(" live ", Style::default().fg(NEON_GREEN))
215
+ Span::styled("auto ", Style::default().fg(TEXT_DIM))
100
216
  };
101
- let footer = Paragraph::new(Line::from(vec![
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(TEXT_DIM)),
105
- Span::styled("R", Style::default().fg(CYBER_CYAN).bold()),
106
- Span::styled(" refresh ", Style::default().fg(TEXT_DIM)),
107
- Span::styled("↑↓", Style::default().fg(CYBER_CYAN).bold()),
108
- Span::styled(" scroll ", Style::default().fg(TEXT_DIM)),
109
- status,
110
- Span::styled(" auto ~5s ", Style::default().fg(TEXT_DIM)),
111
- ]))
112
- .alignment(Alignment::Center)
113
- .block(
114
- Block::default()
115
- .borders(Borders::ALL)
116
- .border_style(Style::default().fg(TEXT_DIM))
117
- .style(Style::default().bg(BG_PANEL)),
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.0",
3
+ "version": "2.10.2",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.10.0: LOCAL PERSISTENCEDocker-based auto-managed Postgres+Redis for Gateway (data survives restarts). Auto-fallback to memory mode. See docs/4RUNR-DB-IMPLEMENTATION-PLAN.md",
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",