export class CommonResourceService<T extends Resource> {
- static mapResponseKeys = (response: any): Promise<any> =>
+ static mapResponseKeys = (response: { data: any }): Promise<any> =>
CommonResourceService.mapKeys(_.camelCase)(response.data)
static mapKeys = (mapFn: (key: string) => string) =>
import { addRouteChangeHandlers } from './routes/routes';
import { loadWorkbench } from './store/workbench/workbench-actions';
import { Routes } from '~/routes/routes';
+import { ServiceRepository } from '~/services/services';
+import { initWebSocket } from '~/websocket/websocket';
+import { Config } from '~/common/config';
const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
const services = createServices(config);
const store = configureStore(history, services);
- store.subscribe(initListener(history, store));
-
+ store.subscribe(initListener(history, store, services, config));
store.dispatch(initAuth());
const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props} />;
});
-const initListener = (history: History, store: RootStore) => {
+const initListener = (history: History, store: RootStore, services: ServiceRepository, config: Config) => {
let initialized = false;
return async () => {
const { router, auth } = store.getState();
if (router.location && auth.user && !initialized) {
initialized = true;
+ initWebSocket(config, services.authService, store);
await store.dispatch(loadWorkbench());
addRouteChangeHandlers(history, store);
}
--- /dev/null
+// 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;
+ id: string;
+ msgID: string;
+ objectKind: string;
+ objectOwnerUuid: string;
+ objectUuid: string;
+ properties: {};
+ uuid: string;
+}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { AuthService } from '~/services/auth-service/auth-service';
+import { ResourceEventMessage } from './resource-event-message';
+import { CommonResourceService } from '~/common/api/common-resource-service';
+import { camelCase } from 'lodash';
+
+type MessageListener = (message: ResourceEventMessage) => void;
+
+export class WebSocketService {
+ private ws: WebSocket;
+ private messageListener: MessageListener;
+
+ constructor(private url: string, private authService: AuthService) { }
+
+ connect() {
+ if (this.ws) {
+ this.ws.close();
+ }
+ this.ws = new WebSocket(this.getUrl());
+ this.ws.addEventListener('message', this.handleMessage);
+ this.ws.addEventListener('open', this.handleOpen);
+ }
+
+ setMessageListener = (listener: MessageListener) => {
+ this.messageListener = listener;
+ }
+
+ private getUrl() {
+ return `${this.url}?api_token=${this.authService.getApiToken()}`;
+ }
+
+ private handleMessage = (event: MessageEvent) => {
+ if (this.messageListener) {
+ const data = JSON.parse(event.data);
+ const message = CommonResourceService.mapKeys(camelCase)(data);
+ this.messageListener(message);
+ }
+ }
+
+ private handleOpen = () => {
+ this.ws.send('{"method":"subscribe"}');
+ }
+
+}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { RootStore } from '~/store/store';
+import { AuthService } from '~/services/auth-service/auth-service';
+import { Config } from '~/common/config';
+import { WebSocketService } from './websocket-service';
+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';
+
+export const initWebSocket = (config: Config, authService: AuthService, store: RootStore) => {
+ const webSocketService = new WebSocketService(config.websocketUrl, authService);
+ webSocketService.setMessageListener(messageListener(store));
+ webSocketService.connect();
+};
+
+const messageListener = (store: RootStore) => (message: ResourceEventMessage) => {
+ switch (message.objectKind) {
+ case ResourceKind.CONTAINER_REQUEST:
+ return store.dispatch(loadProcess(message.objectUuid));
+ case ResourceKind.CONTAINER:
+ return store.dispatch(loadContainers(
+ new FilterBuilder().addIn('uuid', [message.objectUuid]).getFilters()
+ ));
+ default:
+ return;
+ }
+};