Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@
"log": "Logs",
"log_empty_message_title": "No logs",
"log_empty_message_text": "No logs to display.",
"inspect": "Inspect",
"run_name": "Name",
"workflow_name": "Workflow",
"configuration": "Configuration",
Expand Down Expand Up @@ -573,6 +574,7 @@
"fleet_placeholder": "Filtering by fleet",
"fleet_name": "Fleet name",
"total_instances": "Number of instances",
"inspect": "Inspect",
"empty_message_title": "No fleets",
"empty_message_text": "No fleets to display.",
"nomatch_message_title": "No matches",
Expand Down
113 changes: 113 additions & 0 deletions frontend/src/pages/Fleets/Details/Inspect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import ace from 'ace-builds';
import CodeEditor, { CodeEditorProps } from '@cloudscape-design/components/code-editor';
import { Mode } from '@cloudscape-design/global-styles';

import { Container, Header, Loader } from 'components';
import { CODE_EDITOR_I18N_STRINGS } from 'components/form/CodeEditor/constants';

import { useAppSelector } from 'hooks';
import { useGetFleetDetailsQuery } from 'services/fleet';

import { selectSystemMode } from 'App/slice';

import 'ace-builds/src-noconflict/theme-cloud_editor';
import 'ace-builds/src-noconflict/theme-cloud_editor_dark';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/ext-language_tools';

ace.config.set('useWorker', false);

interface AceEditorElement extends HTMLElement {
env?: {
editor?: {
setReadOnly: (readOnly: boolean) => void;
};
};
}

export const FleetInspect = () => {
const { t } = useTranslation();
const params = useParams();
const paramProjectName = params.projectName ?? '';
const paramFleetId = params.fleetId ?? '';

const systemMode = useAppSelector(selectSystemMode) ?? '';

const { data: fleetData, isLoading } = useGetFleetDetailsQuery(
{
projectName: paramProjectName,
fleetId: paramFleetId,
},
{
refetchOnMountOrArgChange: true,
},
);

const [codeEditorPreferences, setCodeEditorPreferences] = useState<CodeEditorProps['preferences']>(() => ({
theme: systemMode === Mode.Dark ? 'cloud_editor_dark' : 'cloud_editor',
}));

useEffect(() => {
if (systemMode === Mode.Dark)
setCodeEditorPreferences({
theme: 'cloud_editor_dark',
});
else
setCodeEditorPreferences({
theme: 'cloud_editor',
});
}, [systemMode]);

const onCodeEditorPreferencesChange: CodeEditorProps['onPreferencesChange'] = (e) => {
setCodeEditorPreferences(e.detail);
};

const jsonContent = useMemo(() => {
if (!fleetData) return '';
return JSON.stringify(fleetData, null, 2);
}, [fleetData]);

// Set editor to read-only after it loads
useEffect(() => {
const timer = setTimeout(() => {
// Find the ace editor instance in the DOM
const editorElements = document.querySelectorAll('.ace_editor');
editorElements.forEach((element: Element) => {
const aceEditor = (element as AceEditorElement).env?.editor;
if (aceEditor) {
aceEditor.setReadOnly(true);
}
});
}, 100);

return () => clearTimeout(timer);
}, [jsonContent]);

if (isLoading)
return (
<Container>
<Loader />
</Container>
);

return (
<Container header={<Header variant="h2">{t('fleets.inspect')}</Header>}>
<CodeEditor
value={jsonContent}
language="json"
i18nStrings={CODE_EDITOR_I18N_STRINGS}
ace={ace}
themes={{ light: [], dark: [] }}
preferences={codeEditorPreferences}
onPreferencesChange={onCodeEditorPreferencesChange}
editorContentHeight={600}
onChange={() => {
// Prevent editing - onChange is required but we ignore changes
}}
/>
</Container>
);
};
6 changes: 6 additions & 0 deletions frontend/src/pages/Fleets/Details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Button, ContentLayout, DetailsHeader, Tabs } from 'components';
enum CodeTab {
Details = 'details',
Events = 'events',
Inspect = 'inspect',
}

import { useBreadcrumbs } from 'hooks';
Expand Down Expand Up @@ -96,6 +97,11 @@ export const FleetDetails: React.FC = () => {
id: CodeTab.Events,
href: ROUTES.FLEETS.DETAILS.EVENTS.FORMAT(paramProjectName, paramFleetId),
},
{
label: 'Inspect',
id: CodeTab.Inspect,
href: ROUTES.FLEETS.DETAILS.INSPECT.FORMAT(paramProjectName, paramFleetId),
},
]}
/>

Expand Down
108 changes: 108 additions & 0 deletions frontend/src/pages/Runs/Details/Inspect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import ace from 'ace-builds';
import CodeEditor, { CodeEditorProps } from '@cloudscape-design/components/code-editor';
import { Mode } from '@cloudscape-design/global-styles';

import { Container, Header, Loader } from 'components';
import { CODE_EDITOR_I18N_STRINGS } from 'components/form/CodeEditor/constants';

import { useAppSelector } from 'hooks';
import { useGetRunQuery } from 'services/run';

import { selectSystemMode } from 'App/slice';

import 'ace-builds/src-noconflict/theme-cloud_editor';
import 'ace-builds/src-noconflict/theme-cloud_editor_dark';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/ext-language_tools';

ace.config.set('useWorker', false);

interface AceEditorElement extends HTMLElement {
env?: {
editor?: {
setReadOnly: (readOnly: boolean) => void;
};
};
}

export const RunInspect = () => {
const { t } = useTranslation();
const params = useParams();
const paramProjectName = params.projectName ?? '';
const paramRunId = params.runId ?? '';

const systemMode = useAppSelector(selectSystemMode) ?? '';

const { data: runData, isLoading } = useGetRunQuery({
project_name: paramProjectName,
id: paramRunId,
});

const [codeEditorPreferences, setCodeEditorPreferences] = useState<CodeEditorProps['preferences']>(() => ({
theme: systemMode === Mode.Dark ? 'cloud_editor_dark' : 'cloud_editor',
}));

useEffect(() => {
if (systemMode === Mode.Dark)
setCodeEditorPreferences({
theme: 'cloud_editor_dark',
});
else
setCodeEditorPreferences({
theme: 'cloud_editor',
});
}, [systemMode]);

const onCodeEditorPreferencesChange: CodeEditorProps['onPreferencesChange'] = (e) => {
setCodeEditorPreferences(e.detail);
};

const jsonContent = useMemo(() => {
if (!runData) return '';
return JSON.stringify(runData, null, 2);
}, [runData]);

// Set editor to read-only after it loads
useEffect(() => {
const timer = setTimeout(() => {
// Find the ace editor instance in the DOM
const editorElements = document.querySelectorAll('.ace_editor');
editorElements.forEach((element: Element) => {
const aceEditor = (element as AceEditorElement).env?.editor;
if (aceEditor) {
aceEditor.setReadOnly(true);
}
});
}, 100);

return () => clearTimeout(timer);
}, [jsonContent]);

if (isLoading)
return (
<Container>
<Loader />
</Container>
);

return (
<Container header={<Header variant="h2">{t('projects.run.inspect')}</Header>}>
<CodeEditor
value={jsonContent}
language="json"
i18nStrings={CODE_EDITOR_I18N_STRINGS}
ace={ace}
themes={{ light: [], dark: [] }}
preferences={codeEditorPreferences}
onPreferencesChange={onCodeEditorPreferencesChange}
editorContentHeight={600}
onChange={() => {
// Prevent editing - onChange is required but we ignore changes
}}
/>
</Container>
);
};
1 change: 1 addition & 0 deletions frontend/src/pages/Runs/Details/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum CodeTab {
Metrics = 'metrics',
Logs = 'logs',
Events = 'events',
Inspect = 'inspect',
}
5 changes: 5 additions & 0 deletions frontend/src/pages/Runs/Details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ export const RunDetailsPage: React.FC = () => {
id: CodeTab.Events,
href: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.EVENTS.FORMAT(paramProjectName, paramRunId),
},
{
label: 'Inspect',
id: CodeTab.Inspect,
href: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.INSPECT.FORMAT(paramProjectName, paramRunId),
},
]}
/>
)}
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Logout } from 'App/Logout';
import { FleetDetails, FleetList } from 'pages/Fleets';
import { EventsList as FleetEventsList } from 'pages/Fleets/Details/Events';
import { FleetDetails as FleetDetailsGeneral } from 'pages/Fleets/Details/FleetDetails';
import { FleetInspect } from 'pages/Fleets/Details/Inspect';
import { InstanceList } from 'pages/Instances';
import { ModelsList } from 'pages/Models';
import { ModelDetails } from 'pages/Models/Details';
Expand All @@ -28,6 +29,7 @@ import {
RunDetailsPage,
RunList,
} from 'pages/Runs';
import { RunInspect } from 'pages/Runs/Details/Inspect';
import { JobDetailsPage } from 'pages/Runs/Details/Jobs/Details';
import { EventsList as JobEvents } from 'pages/Runs/Details/Jobs/Events';
import { CreditsHistoryAdd, UserAdd, UserDetails, UserEdit, UserList } from 'pages/User';
Expand Down Expand Up @@ -122,6 +124,10 @@ export const router = createBrowserRouter([
path: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.EVENTS.TEMPLATE,
element: <RunEvents />,
},
{
path: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.INSPECT.TEMPLATE,
element: <RunInspect />,
},
],
},
{
Expand Down Expand Up @@ -208,6 +214,10 @@ export const router = createBrowserRouter([
path: ROUTES.FLEETS.DETAILS.EVENTS.TEMPLATE,
element: <FleetEventsList />,
},
{
path: ROUTES.FLEETS.DETAILS.INSPECT.TEMPLATE,
element: <FleetInspect />,
},
],
},

Expand Down
10 changes: 10 additions & 0 deletions frontend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export const ROUTES = {
FORMAT: (projectName: string, runId: string) =>
buildRoute(ROUTES.PROJECT.DETAILS.RUNS.DETAILS.LOGS.TEMPLATE, { projectName, runId }),
},
INSPECT: {
TEMPLATE: `/projects/:projectName/runs/:runId/inspect`,
FORMAT: (projectName: string, runId: string) =>
buildRoute(ROUTES.PROJECT.DETAILS.RUNS.DETAILS.INSPECT.TEMPLATE, { projectName, runId }),
},
JOBS: {
DETAILS: {
TEMPLATE: `/projects/:projectName/runs/:runId/jobs/:jobName`,
Expand Down Expand Up @@ -141,6 +146,11 @@ export const ROUTES = {
FORMAT: (projectName: string, fleetId: string) =>
buildRoute(ROUTES.FLEETS.DETAILS.EVENTS.TEMPLATE, { projectName, fleetId }),
},
INSPECT: {
TEMPLATE: `/projects/:projectName/fleets/:fleetId/inspect`,
FORMAT: (projectName: string, fleetId: string) =>
buildRoute(ROUTES.FLEETS.DETAILS.INSPECT.TEMPLATE, { projectName, fleetId }),
},
},
},

Expand Down
Loading