@code-rag/api-server 0.1.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/dashboard/data-collector.d.ts +61 -0
  4. package/dist/dashboard/data-collector.js +244 -0
  5. package/dist/dashboard/data-collector.js.map +1 -0
  6. package/dist/dashboard/data-collector.test.d.ts +1 -0
  7. package/dist/dashboard/data-collector.test.js +334 -0
  8. package/dist/dashboard/data-collector.test.js.map +1 -0
  9. package/dist/dashboard/index.d.ts +10 -0
  10. package/dist/dashboard/index.js +6 -0
  11. package/dist/dashboard/index.js.map +1 -0
  12. package/dist/dashboard/routes.d.ts +18 -0
  13. package/dist/dashboard/routes.js +150 -0
  14. package/dist/dashboard/routes.js.map +1 -0
  15. package/dist/dashboard/templates.d.ts +47 -0
  16. package/dist/dashboard/templates.js +591 -0
  17. package/dist/dashboard/templates.js.map +1 -0
  18. package/dist/dashboard/templates.test.d.ts +1 -0
  19. package/dist/dashboard/templates.test.js +408 -0
  20. package/dist/dashboard/templates.test.js.map +1 -0
  21. package/dist/dashboard/types.d.ts +119 -0
  22. package/dist/dashboard/types.js +4 -0
  23. package/dist/dashboard/types.js.map +1 -0
  24. package/dist/index.d.ts +21 -0
  25. package/dist/index.js +36 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/middleware/auth.d.ts +37 -0
  28. package/dist/middleware/auth.js +92 -0
  29. package/dist/middleware/auth.js.map +1 -0
  30. package/dist/middleware/rate-limit.d.ts +26 -0
  31. package/dist/middleware/rate-limit.js +79 -0
  32. package/dist/middleware/rate-limit.js.map +1 -0
  33. package/dist/openapi.d.ts +14 -0
  34. package/dist/openapi.js +363 -0
  35. package/dist/openapi.js.map +1 -0
  36. package/dist/routes/context.d.ts +15 -0
  37. package/dist/routes/context.js +84 -0
  38. package/dist/routes/context.js.map +1 -0
  39. package/dist/routes/history.d.ts +56 -0
  40. package/dist/routes/history.js +308 -0
  41. package/dist/routes/history.js.map +1 -0
  42. package/dist/routes/index-trigger.d.ts +21 -0
  43. package/dist/routes/index-trigger.js +47 -0
  44. package/dist/routes/index-trigger.js.map +1 -0
  45. package/dist/routes/search.d.ts +24 -0
  46. package/dist/routes/search.js +85 -0
  47. package/dist/routes/search.js.map +1 -0
  48. package/dist/routes/status.d.ts +14 -0
  49. package/dist/routes/status.js +37 -0
  50. package/dist/routes/status.js.map +1 -0
  51. package/dist/routes/team.d.ts +62 -0
  52. package/dist/routes/team.js +129 -0
  53. package/dist/routes/team.js.map +1 -0
  54. package/dist/routes/viewer.d.ts +78 -0
  55. package/dist/routes/viewer.js +387 -0
  56. package/dist/routes/viewer.js.map +1 -0
  57. package/dist/routes/viewer.test.d.ts +1 -0
  58. package/dist/routes/viewer.test.js +509 -0
  59. package/dist/routes/viewer.test.js.map +1 -0
  60. package/dist/server.d.ts +45 -0
  61. package/dist/server.js +227 -0
  62. package/dist/server.js.map +1 -0
  63. package/dist/server.test.d.ts +1 -0
  64. package/dist/server.test.js +620 -0
  65. package/dist/server.test.js.map +1 -0
  66. package/package.json +62 -0
@@ -0,0 +1,334 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { ok, err } from 'neverthrow';
3
+ import { StoreError } from '@coderag/core';
4
+ import { DashboardDataCollector } from './data-collector.js';
5
+ // --- Helpers ---
6
+ function mockStore(overrides = {}) {
7
+ return {
8
+ count: vi.fn().mockResolvedValue(ok(42)),
9
+ ...overrides,
10
+ };
11
+ }
12
+ function mockConfig(overrides = {}) {
13
+ return {
14
+ version: '1',
15
+ project: { name: 'test-project', languages: ['typescript', 'python'] },
16
+ ingestion: { maxTokensPerChunk: 512, exclude: [] },
17
+ embedding: { provider: 'ollama', model: 'nomic-embed-text', dimensions: 768, autoStart: true, autoStop: false, docker: { image: 'ollama/ollama', gpu: 'auto' } },
18
+ llm: { provider: 'ollama', model: 'qwen2.5-coder:7b' },
19
+ search: { topK: 10, vectorWeight: 0.7, bm25Weight: 0.3 },
20
+ storage: { path: '.coderag' },
21
+ ...overrides,
22
+ };
23
+ }
24
+ function createDeps(overrides = {}) {
25
+ const store = mockStore();
26
+ const config = mockConfig();
27
+ return {
28
+ getStore: () => store,
29
+ getConfig: () => config,
30
+ apiKeys: [],
31
+ ...overrides,
32
+ };
33
+ }
34
+ // --- getIndexOverview Tests ---
35
+ describe('DashboardDataCollector', () => {
36
+ describe('getIndexOverview', () => {
37
+ it('should return index overview with chunk count from store', async () => {
38
+ const deps = createDeps();
39
+ const collector = new DashboardDataCollector(deps);
40
+ const overview = await collector.getIndexOverview();
41
+ expect(overview.chunkCount).toBe(42);
42
+ expect(overview.health).toBe('healthy');
43
+ });
44
+ it('should estimate file count from chunk count', async () => {
45
+ const deps = createDeps();
46
+ const collector = new DashboardDataCollector(deps);
47
+ const overview = await collector.getIndexOverview();
48
+ // 42 chunks / 5 ≈ 9 files
49
+ expect(overview.fileCount).toBe(9);
50
+ });
51
+ it('should return languages from config', async () => {
52
+ const deps = createDeps();
53
+ const collector = new DashboardDataCollector(deps);
54
+ const overview = await collector.getIndexOverview();
55
+ expect(overview.languages).toEqual(['typescript', 'python']);
56
+ });
57
+ it('should return not_initialized when store is null', async () => {
58
+ const deps = createDeps({ getStore: () => null });
59
+ const collector = new DashboardDataCollector(deps);
60
+ const overview = await collector.getIndexOverview();
61
+ expect(overview.health).toBe('not_initialized');
62
+ expect(overview.chunkCount).toBe(0);
63
+ expect(overview.fileCount).toBe(0);
64
+ });
65
+ it('should return degraded when store count fails', async () => {
66
+ const store = mockStore({
67
+ count: vi.fn().mockResolvedValue(err(new StoreError('Connection lost'))),
68
+ });
69
+ const deps = createDeps({ getStore: () => store });
70
+ const collector = new DashboardDataCollector(deps);
71
+ const overview = await collector.getIndexOverview();
72
+ expect(overview.health).toBe('degraded');
73
+ expect(overview.chunkCount).toBe(0);
74
+ });
75
+ it('should return degraded when store is empty', async () => {
76
+ const store = mockStore({
77
+ count: vi.fn().mockResolvedValue(ok(0)),
78
+ });
79
+ const deps = createDeps({ getStore: () => store });
80
+ const collector = new DashboardDataCollector(deps);
81
+ const overview = await collector.getIndexOverview();
82
+ expect(overview.health).toBe('degraded');
83
+ });
84
+ it('should estimate storage bytes from chunk count', async () => {
85
+ const deps = createDeps();
86
+ const collector = new DashboardDataCollector(deps);
87
+ const overview = await collector.getIndexOverview();
88
+ // 42 chunks * 4096 bytes
89
+ expect(overview.storageBytes).toBe(42 * 4096);
90
+ });
91
+ it('should return lastIndexed timestamp when set', async () => {
92
+ const deps = createDeps();
93
+ const collector = new DashboardDataCollector(deps);
94
+ collector.setLastIndexed('2026-02-22T10:00:00Z');
95
+ const overview = await collector.getIndexOverview();
96
+ expect(overview.lastIndexed).toBe('2026-02-22T10:00:00Z');
97
+ });
98
+ it('should return null lastIndexed when never indexed', async () => {
99
+ const deps = createDeps();
100
+ const collector = new DashboardDataCollector(deps);
101
+ const overview = await collector.getIndexOverview();
102
+ expect(overview.lastIndexed).toBeNull();
103
+ });
104
+ it('should return empty languages when config has none', async () => {
105
+ const config = mockConfig({ project: { name: 'test', languages: [] } });
106
+ const deps = createDeps({ getConfig: () => config });
107
+ const collector = new DashboardDataCollector(deps);
108
+ const overview = await collector.getIndexOverview();
109
+ expect(overview.languages).toEqual([]);
110
+ });
111
+ });
112
+ // --- recordSearch & getSearchAnalytics Tests ---
113
+ describe('recordSearch / getSearchAnalytics', () => {
114
+ it('should record searches and return total count', () => {
115
+ const collector = new DashboardDataCollector(createDeps());
116
+ collector.recordSearch('hello world', 50, true);
117
+ collector.recordSearch('function parser', 30, true);
118
+ const analytics = collector.getSearchAnalytics(30);
119
+ expect(analytics.totalQueries).toBe(2);
120
+ });
121
+ it('should calculate average response time', () => {
122
+ const collector = new DashboardDataCollector(createDeps());
123
+ collector.recordSearch('query1', 100, true);
124
+ collector.recordSearch('query2', 200, true);
125
+ collector.recordSearch('query3', 300, true);
126
+ const analytics = collector.getSearchAnalytics(30);
127
+ expect(analytics.avgResponseTimeMs).toBe(200);
128
+ });
129
+ it('should calculate error rate', () => {
130
+ const collector = new DashboardDataCollector(createDeps());
131
+ collector.recordSearch('good1', 50, true);
132
+ collector.recordSearch('good2', 50, true);
133
+ collector.recordSearch('bad1', 50, false);
134
+ collector.recordSearch('bad2', 50, false);
135
+ const analytics = collector.getSearchAnalytics(30);
136
+ expect(analytics.errorRate).toBe(0.5);
137
+ });
138
+ it('should return top queries sorted by count', () => {
139
+ const collector = new DashboardDataCollector(createDeps());
140
+ collector.recordSearch('common query', 50, true);
141
+ collector.recordSearch('common query', 50, true);
142
+ collector.recordSearch('common query', 50, true);
143
+ collector.recordSearch('rare query', 50, true);
144
+ const analytics = collector.getSearchAnalytics(30);
145
+ expect(analytics.topQueries[0].query).toBe('common query');
146
+ expect(analytics.topQueries[0].count).toBe(3);
147
+ expect(analytics.topQueries[1].query).toBe('rare query');
148
+ expect(analytics.topQueries[1].count).toBe(1);
149
+ });
150
+ it('should normalize top queries to lowercase', () => {
151
+ const collector = new DashboardDataCollector(createDeps());
152
+ collector.recordSearch('Hello World', 50, true);
153
+ collector.recordSearch('hello world', 50, true);
154
+ const analytics = collector.getSearchAnalytics(30);
155
+ expect(analytics.topQueries).toHaveLength(1);
156
+ expect(analytics.topQueries[0].count).toBe(2);
157
+ });
158
+ it('should group queries by day', () => {
159
+ const collector = new DashboardDataCollector(createDeps());
160
+ collector.recordSearch('q1', 50, true);
161
+ collector.recordSearch('q2', 50, true);
162
+ const analytics = collector.getSearchAnalytics(30);
163
+ expect(analytics.queriesPerDay.length).toBeGreaterThanOrEqual(1);
164
+ const today = new Date().toISOString().slice(0, 10);
165
+ const todayEntry = analytics.queriesPerDay.find((d) => d.date === today);
166
+ expect(todayEntry?.count).toBe(2);
167
+ });
168
+ it('should return empty analytics when no searches recorded', () => {
169
+ const collector = new DashboardDataCollector(createDeps());
170
+ const analytics = collector.getSearchAnalytics(30);
171
+ expect(analytics.totalQueries).toBe(0);
172
+ expect(analytics.queriesPerDay).toEqual([]);
173
+ expect(analytics.topQueries).toEqual([]);
174
+ expect(analytics.avgResponseTimeMs).toBe(0);
175
+ expect(analytics.errorRate).toBe(0);
176
+ });
177
+ it('should filter searches by date range', () => {
178
+ const collector = new DashboardDataCollector(createDeps());
179
+ collector.recordSearch('recent', 50, true);
180
+ // Requesting 0 days means cutoff = now, so entries at now are included
181
+ const analytics = collector.getSearchAnalytics(0);
182
+ expect(analytics.totalQueries).toBe(1);
183
+ // All queries within 30 days should be returned
184
+ const analytics30 = collector.getSearchAnalytics(30);
185
+ expect(analytics30.totalQueries).toBe(1);
186
+ });
187
+ it('should limit top queries to configured limit', () => {
188
+ const collector = new DashboardDataCollector(createDeps(), { topQueriesLimit: 2 });
189
+ collector.recordSearch('a', 50, true);
190
+ collector.recordSearch('b', 50, true);
191
+ collector.recordSearch('c', 50, true);
192
+ const analytics = collector.getSearchAnalytics(30);
193
+ expect(analytics.topQueries.length).toBeLessThanOrEqual(2);
194
+ });
195
+ it('should evict old search records when exceeding max limit', () => {
196
+ const collector = new DashboardDataCollector(createDeps(), { maxSearchRecords: 3 });
197
+ collector.recordSearch('q1', 50, true);
198
+ collector.recordSearch('q2', 50, true);
199
+ collector.recordSearch('q3', 50, true);
200
+ collector.recordSearch('q4', 50, true);
201
+ expect(collector.getSearchRecordCount()).toBe(3);
202
+ });
203
+ });
204
+ // --- recordRequest & getUsageStats Tests ---
205
+ describe('recordRequest / getUsageStats', () => {
206
+ it('should count API calls today', () => {
207
+ const collector = new DashboardDataCollector(createDeps());
208
+ collector.recordRequest('GET', '/api/v1/status', null);
209
+ collector.recordRequest('POST', '/api/v1/search', null);
210
+ const stats = collector.getUsageStats();
211
+ expect(stats.apiCallsToday).toBe(2);
212
+ });
213
+ it('should count API calls for week and month', () => {
214
+ const collector = new DashboardDataCollector(createDeps());
215
+ collector.recordRequest('GET', '/health', null);
216
+ const stats = collector.getUsageStats();
217
+ expect(stats.apiCallsWeek).toBeGreaterThanOrEqual(1);
218
+ expect(stats.apiCallsMonth).toBeGreaterThanOrEqual(1);
219
+ });
220
+ it('should return zero stats when no requests recorded', () => {
221
+ const collector = new DashboardDataCollector(createDeps());
222
+ const stats = collector.getUsageStats();
223
+ expect(stats.apiCallsToday).toBe(0);
224
+ expect(stats.apiCallsWeek).toBe(0);
225
+ expect(stats.apiCallsMonth).toBe(0);
226
+ });
227
+ it('should evict old request records when exceeding max limit', () => {
228
+ const collector = new DashboardDataCollector(createDeps(), { maxRequestRecords: 3 });
229
+ collector.recordRequest('GET', '/a', null);
230
+ collector.recordRequest('GET', '/b', null);
231
+ collector.recordRequest('GET', '/c', null);
232
+ collector.recordRequest('GET', '/d', null);
233
+ expect(collector.getRequestRecordCount()).toBe(3);
234
+ });
235
+ it('should include cost estimate string', () => {
236
+ const collector = new DashboardDataCollector(createDeps());
237
+ collector.recordRequest('GET', '/api/v1/status', null);
238
+ const stats = collector.getUsageStats();
239
+ expect(typeof stats.costEstimate).toBe('string');
240
+ expect(stats.costEstimate.length).toBeGreaterThan(0);
241
+ });
242
+ });
243
+ // --- getUsers Tests ---
244
+ describe('getUsers', () => {
245
+ it('should return users from API keys', () => {
246
+ const apiKeys = [
247
+ { key: 'admin-key-12345', admin: true },
248
+ { key: 'user-key-67890', admin: false },
249
+ ];
250
+ const collector = new DashboardDataCollector(createDeps({ apiKeys }));
251
+ const users = collector.getUsers();
252
+ expect(users).toHaveLength(2);
253
+ expect(users[0].role).toBe('admin');
254
+ expect(users[1].role).toBe('user');
255
+ });
256
+ it('should truncate user IDs to first 8 characters', () => {
257
+ const apiKeys = [
258
+ { key: 'very-long-api-key-string', admin: false },
259
+ ];
260
+ const collector = new DashboardDataCollector(createDeps({ apiKeys }));
261
+ const users = collector.getUsers();
262
+ expect(users[0].userId).toBe('very-lon...');
263
+ });
264
+ it('should not truncate short keys', () => {
265
+ const apiKeys = [
266
+ { key: 'short', admin: false },
267
+ ];
268
+ const collector = new DashboardDataCollector(createDeps({ apiKeys }));
269
+ const users = collector.getUsers();
270
+ expect(users[0].userId).toBe('short');
271
+ });
272
+ it('should track user activity from searches', () => {
273
+ const apiKeys = [
274
+ { key: 'test-key', admin: false },
275
+ ];
276
+ const collector = new DashboardDataCollector(createDeps({ apiKeys }));
277
+ collector.recordSearch('hello', 50, true, 'test-key');
278
+ collector.recordSearch('world', 30, true, 'test-key');
279
+ const users = collector.getUsers();
280
+ expect(users[0].queryCount).toBe(2);
281
+ expect(users[0].lastActive).not.toBeNull();
282
+ });
283
+ it('should return zero query count for inactive users', () => {
284
+ const apiKeys = [
285
+ { key: 'inactive-user-key', admin: false },
286
+ ];
287
+ const collector = new DashboardDataCollector(createDeps({ apiKeys }));
288
+ const users = collector.getUsers();
289
+ expect(users[0].queryCount).toBe(0);
290
+ expect(users[0].lastActive).toBeNull();
291
+ });
292
+ it('should return empty array when no API keys configured', () => {
293
+ const collector = new DashboardDataCollector(createDeps({ apiKeys: [] }));
294
+ const users = collector.getUsers();
295
+ expect(users).toEqual([]);
296
+ });
297
+ });
298
+ // --- getConfig Tests ---
299
+ describe('getConfig', () => {
300
+ it('should return default configuration', () => {
301
+ const collector = new DashboardDataCollector(createDeps());
302
+ const config = collector.getConfig();
303
+ expect(config.maxSearchRecords).toBe(10_000);
304
+ expect(config.maxRequestRecords).toBe(50_000);
305
+ expect(config.topQueriesLimit).toBe(20);
306
+ });
307
+ it('should return custom configuration when provided', () => {
308
+ const collector = new DashboardDataCollector(createDeps(), {
309
+ maxSearchRecords: 500,
310
+ topQueriesLimit: 5,
311
+ });
312
+ const config = collector.getConfig();
313
+ expect(config.maxSearchRecords).toBe(500);
314
+ expect(config.topQueriesLimit).toBe(5);
315
+ });
316
+ });
317
+ // --- setLastIndexed Tests ---
318
+ describe('setLastIndexed', () => {
319
+ it('should update lastIndexed timestamp', async () => {
320
+ const collector = new DashboardDataCollector(createDeps());
321
+ collector.setLastIndexed('2026-01-15T12:00:00Z');
322
+ const overview = await collector.getIndexOverview();
323
+ expect(overview.lastIndexed).toBe('2026-01-15T12:00:00Z');
324
+ });
325
+ it('should overwrite previous lastIndexed', async () => {
326
+ const collector = new DashboardDataCollector(createDeps());
327
+ collector.setLastIndexed('2026-01-01T00:00:00Z');
328
+ collector.setLastIndexed('2026-02-01T00:00:00Z');
329
+ const overview = await collector.getIndexOverview();
330
+ expect(overview.lastIndexed).toBe('2026-02-01T00:00:00Z');
331
+ });
332
+ });
333
+ });
334
+ //# sourceMappingURL=data-collector.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-collector.test.js","sourceRoot":"","sources":["../../src/dashboard/data-collector.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dashboard module barrel exports.
3
+ */
4
+ export { DashboardDataCollector } from './data-collector.js';
5
+ export type { DashboardDataCollectorDeps } from './data-collector.js';
6
+ export { createDashboardRouter } from './routes.js';
7
+ export type { DashboardRouteDeps } from './routes.js';
8
+ export { renderDashboardPage, esc } from './templates.js';
9
+ export type { OverviewPageData, AnalyticsPageData, UsersPageData, SettingsPageData, PageData, } from './templates.js';
10
+ export type { IndexOverview, SearchAnalytics, UserInfo, UsageStats, DashboardConfig, DashboardPage, FlashMessage, DailyQueryCount, TopQuery, SearchRecord, RequestRecord, } from './types.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Dashboard module barrel exports.
3
+ */
4
+ export { DashboardDataCollector } from './data-collector.js';
5
+ export { createDashboardRouter } from './routes.js';
6
+ export { renderDashboardPage, esc } from './templates.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAG7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Dashboard Express routes — admin-only, server-rendered HTML.
3
+ */
4
+ import { Router } from 'express';
5
+ import { type ApiKeyEntry } from '../middleware/auth.js';
6
+ import type { DashboardDataCollector } from './data-collector.js';
7
+ import type { IndexTriggerCallback } from '../routes/index-trigger.js';
8
+ import type { CodeRAGConfig } from '@code-rag/core';
9
+ export interface DashboardRouteDeps {
10
+ readonly dataCollector: DashboardDataCollector;
11
+ readonly onIndex: IndexTriggerCallback | null;
12
+ readonly getConfig: () => CodeRAGConfig | null;
13
+ readonly apiKeys: ReadonlyArray<ApiKeyEntry>;
14
+ }
15
+ /**
16
+ * Create the dashboard router with all admin pages and actions.
17
+ */
18
+ export declare function createDashboardRouter(deps: DashboardRouteDeps): Router;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Dashboard Express routes — admin-only, server-rendered HTML.
3
+ */
4
+ import { Router } from 'express';
5
+ import { createAuthMiddleware, requireAdmin } from '../middleware/auth.js';
6
+ import { renderDashboardPage } from './templates.js';
7
+ /**
8
+ * Create the dashboard router with all admin pages and actions.
9
+ */
10
+ export function createDashboardRouter(deps) {
11
+ const router = Router();
12
+ // Apply auth middleware and requireAdmin to all dashboard routes
13
+ router.use(createAuthMiddleware(deps.apiKeys));
14
+ router.use(requireAdmin);
15
+ // Parse URL-encoded form bodies for POST actions
16
+ router.use((req, _res, next) => {
17
+ if (req.headers['content-type']?.includes('application/x-www-form-urlencoded')) {
18
+ let body = '';
19
+ req.on('data', (chunk) => {
20
+ body += chunk.toString();
21
+ });
22
+ req.on('end', () => {
23
+ // Simple URL-encoded parsing for flash messages
24
+ const params = new URLSearchParams(body);
25
+ req.formBody = Object.fromEntries(params.entries());
26
+ next();
27
+ });
28
+ }
29
+ else {
30
+ next();
31
+ }
32
+ });
33
+ // GET /dashboard → redirect to /dashboard/overview
34
+ router.get('/', (_req, res) => {
35
+ res.redirect(302, '/dashboard/overview');
36
+ });
37
+ // GET /dashboard/overview
38
+ router.get('/overview', async (req, res) => {
39
+ try {
40
+ const overview = await deps.dataCollector.getIndexOverview();
41
+ const usageStats = deps.dataCollector.getUsageStats();
42
+ // Read flash from query string (simple approach, no sessions)
43
+ const flash = parseFlash(req);
44
+ const html = renderDashboardPage({
45
+ page: 'overview',
46
+ data: { overview, usageStats, flash },
47
+ });
48
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
49
+ res.send(html);
50
+ }
51
+ catch (error) {
52
+ const message = error instanceof Error ? error.message : 'Unknown error';
53
+ res.status(500).send(`<h1>Internal Server Error</h1><p>${escHtml(message)}</p>`);
54
+ }
55
+ });
56
+ // GET /dashboard/analytics
57
+ router.get('/analytics', (_req, res) => {
58
+ try {
59
+ const analytics = deps.dataCollector.getSearchAnalytics(30);
60
+ const html = renderDashboardPage({
61
+ page: 'analytics',
62
+ data: { analytics },
63
+ });
64
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
65
+ res.send(html);
66
+ }
67
+ catch (error) {
68
+ const message = error instanceof Error ? error.message : 'Unknown error';
69
+ res.status(500).send(`<h1>Internal Server Error</h1><p>${escHtml(message)}</p>`);
70
+ }
71
+ });
72
+ // GET /dashboard/users
73
+ router.get('/users', (_req, res) => {
74
+ try {
75
+ const users = deps.dataCollector.getUsers();
76
+ const html = renderDashboardPage({
77
+ page: 'users',
78
+ data: { users },
79
+ });
80
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
81
+ res.send(html);
82
+ }
83
+ catch (error) {
84
+ const message = error instanceof Error ? error.message : 'Unknown error';
85
+ res.status(500).send(`<h1>Internal Server Error</h1><p>${escHtml(message)}</p>`);
86
+ }
87
+ });
88
+ // GET /dashboard/settings
89
+ router.get('/settings', (_req, res) => {
90
+ try {
91
+ const config = deps.dataCollector.getConfig();
92
+ const coderagConfig = deps.getConfig();
93
+ const projectConfig = coderagConfig
94
+ ? {
95
+ name: coderagConfig.project.name,
96
+ embeddingModel: coderagConfig.embedding.model,
97
+ storagePath: coderagConfig.storage.path,
98
+ }
99
+ : null;
100
+ const html = renderDashboardPage({
101
+ page: 'settings',
102
+ data: { config, projectConfig },
103
+ });
104
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
105
+ res.send(html);
106
+ }
107
+ catch (error) {
108
+ const message = error instanceof Error ? error.message : 'Unknown error';
109
+ res.status(500).send(`<h1>Internal Server Error</h1><p>${escHtml(message)}</p>`);
110
+ }
111
+ });
112
+ // POST /dashboard/actions/reindex
113
+ router.post('/actions/reindex', async (_req, res) => {
114
+ if (!deps.onIndex) {
115
+ res.redirect(302, '/dashboard/overview?flash=error&msg=Indexing+service+not+configured');
116
+ return;
117
+ }
118
+ try {
119
+ const result = await deps.onIndex({ force: true });
120
+ deps.dataCollector.setLastIndexed(new Date().toISOString());
121
+ const msg = encodeURIComponent(`Re-indexing completed: ${result.indexed_files} files in ${result.duration_ms}ms`);
122
+ res.redirect(302, `/dashboard/overview?flash=success&msg=${msg}`);
123
+ }
124
+ catch (error) {
125
+ const message = error instanceof Error ? error.message : 'Unknown error';
126
+ const msg = encodeURIComponent(`Re-indexing failed: ${message}`);
127
+ res.redirect(302, `/dashboard/overview?flash=error&msg=${msg}`);
128
+ }
129
+ });
130
+ return router;
131
+ }
132
+ /**
133
+ * Parse a flash message from query parameters.
134
+ */
135
+ function parseFlash(req) {
136
+ const flashType = req.query['flash'];
137
+ const flashMsg = req.query['msg'];
138
+ if (typeof flashType === 'string' &&
139
+ typeof flashMsg === 'string' &&
140
+ (flashType === 'success' || flashType === 'error' || flashType === 'info')) {
141
+ return { type: flashType, text: flashMsg };
142
+ }
143
+ return undefined;
144
+ }
145
+ /**
146
+ * Simple HTML escape for error messages.
147
+ */
148
+ function escHtml(s) {
149
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
150
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Server-rendered HTML templates for the CodeRAG admin dashboard.
3
+ *
4
+ * All HTML/CSS is inline — no external assets or client-side JS frameworks.
5
+ * Works without JavaScript in the browser (progressive enhancement).
6
+ */
7
+ import type { FlashMessage, IndexOverview, SearchAnalytics, UserInfo, UsageStats, DashboardConfig } from './types.js';
8
+ export interface OverviewPageData {
9
+ readonly overview: IndexOverview;
10
+ readonly usageStats: UsageStats;
11
+ readonly flash?: FlashMessage;
12
+ }
13
+ export interface AnalyticsPageData {
14
+ readonly analytics: SearchAnalytics;
15
+ }
16
+ export interface UsersPageData {
17
+ readonly users: ReadonlyArray<UserInfo>;
18
+ }
19
+ export interface SettingsPageData {
20
+ readonly config: DashboardConfig;
21
+ readonly projectConfig: {
22
+ readonly name: string;
23
+ readonly embeddingModel: string;
24
+ readonly storagePath: string;
25
+ } | null;
26
+ }
27
+ export type PageData = {
28
+ page: 'overview';
29
+ data: OverviewPageData;
30
+ } | {
31
+ page: 'analytics';
32
+ data: AnalyticsPageData;
33
+ } | {
34
+ page: 'users';
35
+ data: UsersPageData;
36
+ } | {
37
+ page: 'settings';
38
+ data: SettingsPageData;
39
+ };
40
+ /**
41
+ * Render a complete dashboard HTML page.
42
+ */
43
+ export declare function renderDashboardPage(pageData: PageData): string;
44
+ /**
45
+ * Escape HTML special characters to prevent XSS.
46
+ */
47
+ export declare function esc(s: string): string;