Skip to content

Commit fa13de6

Browse files
committed
feat: add Devfile Creator with AI agent, CodeMirror editor, and terminal integration
- Add Devfile Creator feature: list, create, edit, delete devfiles stored in ConfigMap - Integrate CodeMirror 6 editor with YAML syntax highlighting, schema validation, and autocompletion - Serve devfile JSON schemas (2.0.0–2.3.0) from backend API instead of bundling in frontend - Add AI agent workspace with terminal proxy (gritty) and theme support - Move terminal themes to common package for shared use between frontend and backend - Extract devfile and terminal helpers into dedicated modules - Replace Monaco editor with CodeMirror 6 (17x smaller bundle: 764KB vs 13MB) - Add WebSocket ConfigMap watch for real-time devfile updates - Add devfile project names display in list view - Fix WebSocket error banner debounce (10s), Copy Link action, Start Agent for failed workspaces Assisted-by: Claude Opus 4.6 Signed-off-by: Oleksii Orel <oorel@redhat.com>
1 parent fef45aa commit fa13de6

71 files changed

Lines changed: 14940 additions & 278 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/common/src/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
*/
1212

1313
export * from './backup';
14+
export * from './terminal-themes';
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2018-2025 Red Hat, Inc.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat, Inc. - initial API and implementation
11+
*/
12+
13+
// Generated by Claude Opus 4.6
14+
15+
export interface TerminalTheme {
16+
foreground: string;
17+
background: string;
18+
cursor: string;
19+
cursorAccent: string;
20+
selectionBackground: string;
21+
selectionForeground: string;
22+
black: string;
23+
red: string;
24+
green: string;
25+
yellow: string;
26+
blue: string;
27+
magenta: string;
28+
cyan: string;
29+
white: string;
30+
brightBlack: string;
31+
brightRed: string;
32+
brightGreen: string;
33+
brightYellow: string;
34+
brightBlue: string;
35+
brightMagenta: string;
36+
brightCyan: string;
37+
brightWhite: string;
38+
}
39+
40+
export type TerminalThemeName = 'dracula' | 'dark' | 'light';
41+
42+
export const TERMINAL_THEMES: Record<TerminalThemeName, TerminalTheme> = {
43+
dracula: {
44+
foreground: '#f8f8f2',
45+
background: '#282a36',
46+
cursor: '#f8f8f2',
47+
cursorAccent: '#282a36',
48+
selectionBackground: '#44475a',
49+
selectionForeground: '#f8f8f2',
50+
black: '#21222c',
51+
red: '#ff5555',
52+
green: '#50fa7b',
53+
yellow: '#f1fa8c',
54+
blue: '#bd93f9',
55+
magenta: '#ff79c6',
56+
cyan: '#8be9fd',
57+
white: '#f8f8f2',
58+
brightBlack: '#6272a4',
59+
brightRed: '#ff6e6e',
60+
brightGreen: '#69ff94',
61+
brightYellow: '#ffffa5',
62+
brightBlue: '#d6acff',
63+
brightMagenta: '#ff92df',
64+
brightCyan: '#a4ffff',
65+
brightWhite: '#ffffff',
66+
},
67+
dark: {
68+
foreground: '#d4d4d4',
69+
background: '#1e1e1e',
70+
cursor: '#d4d4d4',
71+
cursorAccent: '#1e1e1e',
72+
selectionBackground: '#264f78',
73+
selectionForeground: '#ffffff',
74+
black: '#000000',
75+
red: '#cd3131',
76+
green: '#0dbc79',
77+
yellow: '#e5e510',
78+
blue: '#2472c8',
79+
magenta: '#bc3fbc',
80+
cyan: '#11a8cd',
81+
white: '#e5e5e5',
82+
brightBlack: '#666666',
83+
brightRed: '#f14c4c',
84+
brightGreen: '#23d18b',
85+
brightYellow: '#f5f543',
86+
brightBlue: '#3b8eea',
87+
brightMagenta: '#d670d6',
88+
brightCyan: '#29b8db',
89+
brightWhite: '#e5e5e5',
90+
},
91+
light: {
92+
foreground: '#383a42',
93+
background: '#fafafa',
94+
cursor: '#526eff',
95+
cursorAccent: '#fafafa',
96+
selectionBackground: '#d7d4f0',
97+
selectionForeground: '#383a42',
98+
black: '#000000',
99+
red: '#e45649',
100+
green: '#50a14f',
101+
yellow: '#c18401',
102+
blue: '#4078f2',
103+
magenta: '#a626a4',
104+
cyan: '#0184bc',
105+
white: '#a0a1a7',
106+
brightBlack: '#5c6370',
107+
brightRed: '#e06c75',
108+
brightGreen: '#98c379',
109+
brightYellow: '#e5c07b',
110+
brightBlue: '#61afef',
111+
brightMagenta: '#c678dd',
112+
brightCyan: '#56b6c2',
113+
brightWhite: '#909090',
114+
},
115+
};
116+
117+
export function isTerminalThemeName(value: string): value is TerminalThemeName {
118+
return value === 'dracula' || value === 'dark' || value === 'light';
119+
}

packages/common/src/dto/api/webSocket.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@
1111
*/
1212

1313
import { V1alpha2DevWorkspace } from '@devfile/api';
14-
import { CoreV1Event, V1Pod, V1Status } from '@kubernetes/client-node';
14+
import {
15+
CoreV1Event,
16+
V1ConfigMap,
17+
V1Pod,
18+
V1Status,
19+
} from '@kubernetes/client-node';
1520

1621
export enum Channel {
1722
DEV_WORKSPACE = 'devWorkspace',
1823
EVENT = 'event',
1924
POD = 'pod',
2025
LOGS = 'logs',
26+
CONFIGMAP = 'configmap',
2127
}
2228

2329
export function isWebSocketChannel(channel: unknown): channel is Channel {
@@ -26,7 +32,8 @@ export function isWebSocketChannel(channel: unknown): channel is Channel {
2632
((channel as Channel) === Channel.DEV_WORKSPACE ||
2733
(channel as Channel) === Channel.EVENT)) ||
2834
(channel as Channel) === Channel.POD ||
29-
(channel as Channel) === Channel.LOGS
35+
(channel as Channel) === Channel.LOGS ||
36+
(channel as Channel) === Channel.CONFIGMAP
3037
);
3138
}
3239

@@ -44,7 +51,11 @@ export type SubscribeMessage = {
4451
method: 'SUBSCRIBE';
4552
} & (
4653
| {
47-
channel: Channel.DEV_WORKSPACE | Channel.EVENT | Channel.POD;
54+
channel:
55+
| Channel.DEV_WORKSPACE
56+
| Channel.EVENT
57+
| Channel.POD
58+
| Channel.CONFIGMAP;
4859
params: SubscribeParams;
4960
}
5061
| {
@@ -138,6 +149,10 @@ export type PodMessage = {
138149
eventPhase: EventPhase.ADDED | EventPhase.MODIFIED | EventPhase.DELETED;
139150
pod: V1Pod;
140151
};
152+
export type ConfigMapMessage = {
153+
eventPhase: EventPhase.ADDED | EventPhase.MODIFIED | EventPhase.DELETED;
154+
configMap: V1ConfigMap;
155+
};
141156
export type LogsMessage = {
142157
eventPhase: EventPhase.ADDED;
143158
podName: string;
@@ -153,6 +168,7 @@ export type NotificationMessage =
153168
| EventMessage
154169
| DevWorkspaceMessage
155170
| PodMessage
171+
| ConfigMapMessage
156172
| LogsMessage
157173
| StatusMessage;
158174
export type EventData = {
@@ -220,6 +236,18 @@ export function isStatusMessage(message: unknown): message is StatusMessage {
220236
);
221237
}
222238

239+
export function isConfigMapMessage(
240+
message: unknown,
241+
): message is ConfigMapMessage {
242+
return (
243+
message !== undefined &&
244+
((message as ConfigMapMessage).eventPhase === EventPhase.ADDED ||
245+
(message as ConfigMapMessage).eventPhase === EventPhase.MODIFIED ||
246+
(message as ConfigMapMessage).eventPhase === EventPhase.DELETED) &&
247+
(message as ConfigMapMessage).configMap !== undefined
248+
);
249+
}
250+
223251
export function isLogsMessage(message: unknown): message is LogsMessage {
224252
return (
225253
message !== undefined &&

packages/dashboard-backend/src/app.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { registerBackupRoutes } from '@/routes/api/backup';
2727
import { registerClusterConfigRoute } from '@/routes/api/clusterConfig';
2828
import { registerClusterInfoRoute } from '@/routes/api/clusterInfo';
2929
import { registerDataResolverRoute } from '@/routes/api/dataResolver';
30+
import { registerDevfileCreatorRoute } from '@/routes/api/devfileCreator';
31+
import { registerDevfileSchemaRoute } from '@/routes/api/devfileSchema';
3032
import { registerDevWorkspaceClusterRoutes } from '@/routes/api/devworkspaceCluster';
3133
import { registerDevworkspaceResourcesRoute } from '@/routes/api/devworkspaceResources';
3234
import { registerDevworkspacesRoutes } from '@/routes/api/devworkspaces';
@@ -143,5 +145,9 @@ export default async function buildApp(server: FastifyInstance): Promise<unknown
143145
registerAirGapSampleRoute(server),
144146

145147
registerBackupRoutes(server),
148+
149+
registerDevfileCreatorRoute(server),
150+
151+
registerDevfileSchemaRoute(server),
146152
]);
147153
}

packages/dashboard-backend/src/constants/schemas.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,17 @@ export const sshKeyParamsSchema: JSONSchema7 = {
261261
required: ['namespace', 'name'],
262262
};
263263

264+
export const devfileVersionSchema: JSONSchema7 = {
265+
type: 'object',
266+
properties: {
267+
version: {
268+
type: 'string',
269+
pattern: '^2\\.[0-3]\\.[0-2](?:-alpha)?$',
270+
},
271+
},
272+
required: ['version'],
273+
};
274+
264275
// namespaced schemas
265276

266277
export const namespacedSchema: JSONSchema7 = {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright (c) 2018-2025 Red Hat, Inc.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat, Inc. - initial API and implementation
11+
*/
12+
13+
export { TERMINAL_THEMES } from '@eclipse-che/common';

0 commit comments

Comments
 (0)