--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Resource } from "./resource";
+import { ResourceKind } from '~/models/resource';
+
+export enum LogEventType {
+ CREATE = 'create',
+ UPDATE = 'update',
+ DISPATCH = 'dispatch',
+ CRUNCH_RUN = 'crunch-run',
+ CRUNCHSTAT = 'crunchstat',
+ HOSTSTAT = 'hoststat',
+ NODE_INFO = 'node-info',
+ ARV_MOUNT = 'arv-mount',
+ STDOUT = 'stdout',
+ STDERR = 'stderr',
+}
+
+export interface LogResource extends Resource {
+ kind: ResourceKind.LOG;
+ objectUuid: string;
+ eventAt: string;
+ eventType: string;
+ summary: string;
+ properties: any;
+}
CONTAINER = "arvados#container",
CONTAINER_REQUEST = "arvados#containerRequest",
GROUP = "arvados#group",
+ LOG = "arvados#log",
PROCESS = "arvados#containerRequest",
PROJECT = "arvados#group",
USER = "arvados#user",
CONTAINER = 'dz642',
CONTAINER_REQUEST = 'xvhdp',
GROUP = 'j7d0g',
+ LOG = '57u5n',
USER = 'tpzed',
}
return ResourceKind.CONTAINER_REQUEST;
case ResourceObjectType.CONTAINER:
return ResourceKind.CONTAINER;
+ case ResourceObjectType.LOG:
+ return ResourceKind.LOG;
default:
return undefined;
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { CommonResourceService } from "~/common/api/common-resource-service";
+import { AxiosInstance } from "axios";
+import { LogResource } from '~/models/log';
+
+export class LogService extends CommonResourceService<LogResource> {
+ constructor(serverApi: AxiosInstance) {
+ super(serverApi, "logs");
+ }
+}
import { ResourceKind } from "~/models/resource";
import { ContainerRequestService } from './container-request-service/container-request-service';
import { ContainerService } from './container-service/container-service';
+import { LogService } from './log-service/log-service';
export type ServiceRepository = ReturnType<typeof createServices>;
const webdavClient = new WebDAV();
webdavClient.defaults.baseURL = config.keepWebServiceUrl;
+ const containerRequestService = new ContainerRequestService(apiClient);
+ const containerService = new ContainerService(apiClient);
const groupsService = new GroupsService(apiClient);
const keepService = new KeepService(apiClient);
const linkService = new LinkService(apiClient);
+ const logService = new LogService(apiClient);
const projectService = new ProjectService(apiClient);
const userService = new UserService(apiClient);
- const containerRequestService = new ContainerRequestService(apiClient);
- const containerService = new ContainerService(apiClient);
const ancestorsService = new AncestorService(groupsService, userService);
const authService = new AuthService(apiClient, config.rootUrl);
groupsService,
keepService,
linkService,
+ logService,
projectService,
tagService,
userService,
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { unionize, ofType, UnionOf } from "~/common/unionize";
+import { ProcessLogs } from './process-logs-panel';
+import { LogEventType } from '~/models/log';
+import { RootState } from '~/store/store';
+import { ServiceRepository } from '~/services/services';
+import { Dispatch } from 'redux';
+import { FilterBuilder } from '~/common/api/filter-builder';
+import { groupBy } from 'lodash';
+import { loadProcess } from '~/store/processes/processes-actions';
+import { OrderBuilder } from '~/common/api/order-builder';
+import { LogResource } from '~/models/log';
+import { LogService } from '~/services/log-service/log-service';
+
+export const processLogsPanelActions = unionize({
+ INIT_PROCESS_LOGS_PANEL: ofType<{ filters: string[], logs: ProcessLogs }>(),
+ SET_PROCESS_LOGS_PANEL_FILTER: ofType<string>(),
+ ADD_PROCESS_LOGS_PANEL_ITEM: ofType<{ logType: string, log: string }>(),
+});
+
+export type ProcessLogsPanelAction = UnionOf<typeof processLogsPanelActions>;
+
+export const setProcessLogsPanelFilter = (filter: string) =>
+ processLogsPanelActions.SET_PROCESS_LOGS_PANEL_FILTER(filter);
+
+export const initProcessLogsPanel = (processUuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, { logService }: ServiceRepository) => {
+ const process = await dispatch<any>(loadProcess(processUuid));
+ if (process.container) {
+ const logResources = await loadContainerLogs(process.container.uuid, logService);
+ const initialState = createInitialLogPanelState(logResources);
+ dispatch(processLogsPanelActions.INIT_PROCESS_LOGS_PANEL(initialState));
+ }
+ };
+
+const loadContainerLogs = async (containerUuid: string, logService: LogService) => {
+ const requestFilters = new FilterBuilder()
+ .addEqual('objectUuid', containerUuid)
+ .addIn('eventType', PROCESS_PANEL_LOG_EVENT_TYPES)
+ .getFilters();
+ const requestOrder = new OrderBuilder<LogResource>()
+ .addAsc('eventAt')
+ .getOrder();
+ const requestParams = {
+ limit: MAX_AMOUNT_OF_LOGS,
+ filters: requestFilters,
+ order: requestOrder,
+ };
+ const { items } = await logService.list(requestParams);
+ return items;
+};
+
+const createInitialLogPanelState = (logResources: LogResource[]) => {
+ const allLogs = logResources.map(({ properties }) => properties.text);
+ const groupedLogResources = groupBy(logResources, log => log.eventType);
+ const groupedLogs = Object
+ .keys(groupedLogResources)
+ .reduce((grouped, key) => ({
+ ...grouped,
+ [key]: groupedLogResources[key].map(({ properties }) => properties.text)
+ }), {});
+ const filters = [SUMMARIZED_FILTER_TYPE, ...Object.keys(groupedLogs)];
+ const logs = { [SUMMARIZED_FILTER_TYPE]: allLogs, ...groupedLogs };
+ return { filters, logs };
+};
+
+const MAX_AMOUNT_OF_LOGS = 10000;
+
+const SUMMARIZED_FILTER_TYPE = 'Summarized';
+
+const PROCESS_PANEL_LOG_EVENT_TYPES = [
+ LogEventType.ARV_MOUNT,
+ LogEventType.CRUNCH_RUN,
+ LogEventType.CRUNCHSTAT,
+ LogEventType.DISPATCH,
+ LogEventType.HOSTSTAT,
+ LogEventType.NODE_INFO,
+ LogEventType.STDERR,
+ LogEventType.STDOUT,
+];
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ProcessLogsPanel } from './process-logs-panel';
+import { RootState } from '~/store/store';
+import { ProcessLogsPanelAction, processLogsPanelActions } from './process-logs-panel-actions';
+
+const initialState: ProcessLogsPanel = {
+ filters: [],
+ selectedFilter: '',
+ logs: { '': [] },
+};
+
+export const processLogsPanelReducer = (state = initialState, action: ProcessLogsPanelAction): ProcessLogsPanel =>
+ processLogsPanelActions.match(action, {
+ INIT_PROCESS_LOGS_PANEL: ({ filters, logs }) => ({
+ filters,
+ logs,
+ selectedFilter: filters[0] || '',
+ }),
+ SET_PROCESS_LOGS_PANEL_FILTER: selectedFilter => ({
+ ...state,
+ selectedFilter
+ }),
+ ADD_PROCESS_LOGS_PANEL_ITEM: ({ logType, log }) => {
+ const logsOfType = [...state.logs[logType], log];
+ const logs = { ...state.logs, [logType]: logsOfType };
+ return { ...state, logs };
+ },
+ default: () => state,
+ });
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface ProcessLogsPanel {
+ filters: string[];
+ selectedFilter: string;
+ logs: ProcessLogs;
+}
+
+export interface ProcessLogs {
+ [logType: string]: string[];
+}
+
+export const getProcessPanelLogs = ({ selectedFilter, logs }: ProcessLogsPanel) => {
+ return logs[selectedFilter];
+};
import { updateResources } from '~/store/resources/resources-actions';
import { FilterBuilder } from '~/common/api/filter-builder';
import { ContainerRequestResource } from '../../models/container-request';
+import { Process } from './process';
export const loadProcess = (containerRequestUuid: string) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process> => {
const containerRequest = await services.containerRequestService.get(containerRequestUuid);
dispatch<any>(updateResources([containerRequest]));
if (containerRequest.containerUuid) {
const container = await services.containerService.get(containerRequest.containerUuid);
dispatch<any>(updateResources([container]));
await dispatch<any>(loadSubprocesses(containerRequest.containerUuid));
+ return { containerRequest, container };
}
+ return { containerRequest };
};
export const loadSubprocesses = (containerUuid: string) =>
import { propertiesReducer } from './properties/properties-reducer';
import { RootState } from './store';
import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
+import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
dialog: dialogReducer,
favorites: favoritesReducer,
form: formReducer,
+ processLogsPanel: processLogsPanelReducer,
properties: propertiesReducer,
resources: resourcesReducer,
router: routerReducer,
+import { LogEventType } from '../models/log';
// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0
-export enum ResourceEventMessageType {
- CREATE = 'create',
- UPDATE = 'update',
- HOTSTAT = 'hotstat',
- CRUNCH_RUN = 'crunch-run',
- NODE_INFO = 'node-info',
-}
-
export interface ResourceEventMessage {
eventAt: string;
- eventType: ResourceEventMessageType;
+ eventType: LogEventType;
id: string;
msgID: string;
objectKind: string;
import { AuthService } from '~/services/auth-service/auth-service';
import { Config } from '~/common/config';
import { WebSocketService } from './websocket-service';
-import { ResourceEventMessage, ResourceEventMessageType } from './resource-event-message';
+import { ResourceEventMessage } from './resource-event-message';
import { ResourceKind } from '~/models/resource';
import { loadProcess } from '~/store/processes/processes-actions';
import { loadContainers } from '../store/processes/processes-actions';
import { FilterBuilder } from '~/common/api/filter-builder';
+import { LogEventType } from '../models/log';
export const initWebSocket = (config: Config, authService: AuthService, store: RootStore) => {
const webSocketService = new WebSocketService(config.websocketUrl, authService);
};
const messageListener = (store: RootStore) => (message: ResourceEventMessage) => {
- if (message.eventType === ResourceEventMessageType.CREATE || message.eventType === ResourceEventMessageType.UPDATE) {
+ if (message.eventType === LogEventType.CREATE || message.eventType === LogEventType.UPDATE) {
switch (message.objectKind) {
case ResourceKind.CONTAINER_REQUEST:
return store.dispatch(loadProcess(message.objectUuid));