1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import Axios from 'axios';
7 export const WORKBENCH_CONFIG_URL =
8 process.env.REACT_APP_ARVADOS_CONFIG_URL || '/config.json';
10 interface WorkbenchConfig {
12 VOCABULARY_URL?: string;
13 FILE_VIEWERS_CONFIG_URL?: string;
16 export interface ClusterConfigJSON {
18 UnfreezeProjectRequiresAdmin: boolean
19 MaxItemsPerResponse: number
23 ReserveExtraRAM: number;
30 DriverVersion: string;
31 HardwareCapability: string;
33 IncludedScratch: number;
43 ActivateUsers: boolean
61 DisableSharingURLsUI: boolean;
62 ArvadosDocsite: string;
63 FileViewersConfigURL: string;
64 WelcomePageHTML: string;
65 InactivePageHTML: string;
66 SSHHelpPageHTML: string;
67 SSHHelpHostSuffix: string;
85 DisableSharingURLsUI: boolean;
86 ArvadosDocsite: string;
87 FileViewersConfigURL: string;
88 WelcomePageHTML: string;
89 InactivePageHTML: string;
90 SSHHelpPageHTML: string;
91 SSHHelpHostSuffix: string;
95 UserProfileFormFields: {};
96 UserProfileFormMessage: string;
120 ForwardSlashNameSubstitution: string;
121 ManagedProperties?: {
128 TrustAllContent: boolean;
133 [key: string]: boolean;
138 AnonymousUserToken: string;
139 SupportEmailAddress: string;
143 export class Config {
145 keepWebServiceUrl!: string;
146 keepWebInlineServiceUrl!: string;
148 [key: string]: string;
152 websocketUrl!: string;
153 workbenchUrl!: string;
154 workbench2Url!: string;
155 vocabularyUrl!: string;
156 fileViewersConfigUrl!: string;
157 loginCluster!: string;
158 clusterConfig!: ClusterConfigJSON;
159 apiRevision!: number;
162 export const buildConfig = (clusterConfig: ClusterConfigJSON): Config => {
163 const clusterConfigJSON = removeTrailingSlashes(clusterConfig);
164 const config = new Config();
165 config.rootUrl = clusterConfigJSON.Services.Controller.ExternalURL;
166 config.baseUrl = `${config.rootUrl}/${ARVADOS_API_PATH}`;
167 config.uuidPrefix = clusterConfigJSON.ClusterID;
168 config.websocketUrl = clusterConfigJSON.Services.Websocket.ExternalURL;
169 config.workbench2Url = clusterConfigJSON.Services.Workbench2.ExternalURL;
170 config.workbenchUrl = clusterConfigJSON.Services.Workbench1.ExternalURL;
171 config.keepWebServiceUrl =
172 clusterConfigJSON.Services.WebDAVDownload.ExternalURL;
173 config.keepWebInlineServiceUrl =
174 clusterConfigJSON.Services.WebDAV.ExternalURL;
175 config.loginCluster = clusterConfigJSON.Login.LoginCluster;
176 config.clusterConfig = clusterConfigJSON;
177 config.apiRevision = 0;
178 mapRemoteHosts(clusterConfigJSON, config);
182 export const getStorageClasses = (config: Config): string[] => {
183 const classes: Set<string> = new Set(['default']);
184 const volumes = config.clusterConfig.Volumes;
185 Object.keys(volumes).forEach((v) => {
186 Object.keys(volumes[v].StorageClasses || {}).forEach((sc) => {
187 if (volumes[v].StorageClasses[sc]) {
192 return Array.from(classes);
195 const getApiRevision = async (apiUrl: string) => {
197 const dd = (await Axios.get<any>(`${apiUrl}/${DISCOVERY_DOC_PATH}`)).data;
198 return parseInt(dd.revision, 10) || 0;
201 'Unable to get API Revision number, defaulting to zero. Some features may not work properly.'
207 const removeTrailingSlashes = (
208 config: ClusterConfigJSON
209 ): ClusterConfigJSON => {
210 const svcs: any = {};
211 Object.keys(config.Services).forEach((s) => {
212 svcs[s] = config.Services[s];
213 if (svcs[s].hasOwnProperty('ExternalURL')) {
214 svcs[s].ExternalURL = svcs[s].ExternalURL.replace(/\/+$/, '');
217 return { ...config, Services: svcs };
220 export const fetchConfig = () => {
221 return Axios.get<WorkbenchConfig>(
222 WORKBENCH_CONFIG_URL + '?nocache=' + new Date().getTime()
224 .then((response) => response.data)
227 `There was an exception getting the Workbench config file at ${WORKBENCH_CONFIG_URL}. Using defaults instead.`
229 return Promise.resolve(getDefaultConfig());
231 .then((workbenchConfig) => {
232 if (workbenchConfig.API_HOST === undefined) {
234 `Unable to start Workbench. API_HOST is undefined in ${WORKBENCH_CONFIG_URL} or the environment.`
237 return Axios.get<ClusterConfigJSON>(
238 getClusterConfigURL(workbenchConfig.API_HOST)
239 ).then(async (response) => {
240 const apiRevision = await getApiRevision(
241 response.data.Services.Controller.ExternalURL.replace(/\/+$/, '')
243 const config = { ...buildConfig(response.data), apiRevision };
244 const warnLocalConfig = (varName: string) =>
246 `A value for ${varName} was found in ${WORKBENCH_CONFIG_URL}. To use the Arvados centralized configuration instead, \
247 remove the entire ${varName} entry from ${WORKBENCH_CONFIG_URL}`
250 // Check if the workbench config has an entry for vocabulary and file viewer URLs
251 // If so, use these values (even if it is an empty string), but print a console warning.
252 // Otherwise, use the cluster config.
253 let fileViewerConfigUrl;
254 if (workbenchConfig.FILE_VIEWERS_CONFIG_URL !== undefined) {
255 warnLocalConfig('FILE_VIEWERS_CONFIG_URL');
256 fileViewerConfigUrl = workbenchConfig.FILE_VIEWERS_CONFIG_URL;
258 fileViewerConfigUrl =
259 config.clusterConfig.Workbench.FileViewersConfigURL ||
260 '/file-viewers-example.json';
262 config.fileViewersConfigUrl = fileViewerConfigUrl;
264 if (workbenchConfig.VOCABULARY_URL !== undefined) {
266 `A value for VOCABULARY_URL was found in ${WORKBENCH_CONFIG_URL}. It will be ignored as the cluster already provides its own endpoint, you can safely remove it.`
269 config.vocabularyUrl = getVocabularyURL(workbenchConfig.API_HOST);
271 return { config, apiHost: workbenchConfig.API_HOST };
276 // Maps remote cluster hosts and removes the default RemoteCluster entry
277 export const mapRemoteHosts = (
278 clusterConfigJSON: ClusterConfigJSON,
281 config.remoteHosts = {};
282 Object.keys(clusterConfigJSON.RemoteClusters).forEach((k) => {
283 config.remoteHosts[k] = clusterConfigJSON.RemoteClusters[k].Host;
285 delete config.remoteHosts['*'];
288 export const mockClusterConfigJSON = (
289 config: Partial<ClusterConfigJSON>
290 ): ClusterConfigJSON => ({
292 UnfreezeProjectRequiresAdmin: false,
293 MaxItemsPerResponse: 1000,
297 ReserveExtraRAM: 576716800,
301 Controller: { ExternalURL: '' },
302 Workbench1: { ExternalURL: '' },
303 Workbench2: { ExternalURL: '' },
304 Websocket: { ExternalURL: '' },
305 WebDAV: { ExternalURL: '' },
306 WebDAVDownload: { ExternalURL: '' },
307 WebShell: { ExternalURL: '' },
309 DisableSharingURLsUI: false,
311 FileViewersConfigURL: "",
313 InactivePageHTML: "",
315 SSHHelpHostSuffix: "",
321 DisableSharingURLsUI: false,
323 FileViewersConfigURL: '',
325 InactivePageHTML: '',
327 SSHHelpHostSuffix: '',
331 UserProfileFormFields: {},
332 UserProfileFormMessage: '',
356 ForwardSlashNameSubstitution: '',
357 TrustAllContent: false,
361 AnonymousUserToken: "",
362 SupportEmailAddress: "arvados@example.com",
367 export const mockConfig = (config: Partial<Config>): Config => ({
369 keepWebServiceUrl: '',
370 keepWebInlineServiceUrl: '',
378 fileViewersConfigUrl: '',
380 clusterConfig: mockClusterConfigJSON({}),
385 const getDefaultConfig = (): WorkbenchConfig => {
387 const envHost = process.env.REACT_APP_ARVADOS_API_HOST;
388 if (envHost !== undefined) {
389 console.warn(`Using default API host ${envHost}.`);
393 `No API host was found in the environment. Workbench may not be able to communicate with Arvados components.`
398 VOCABULARY_URL: undefined,
399 FILE_VIEWERS_CONFIG_URL: undefined,
403 export const ARVADOS_API_PATH = 'arvados/v1';
404 export const CLUSTER_CONFIG_PATH = 'arvados/v1/config';
405 export const VOCABULARY_PATH = 'arvados/v1/vocabulary';
406 export const DISCOVERY_DOC_PATH = 'discovery/v1/apis/arvados/v1/rest';
407 export const getClusterConfigURL = (apiHost: string) =>
408 `https://${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${new Date().getTime()}`;
409 export const getVocabularyURL = (apiHost: string) =>
410 `https://${apiHost}/${VOCABULARY_PATH}?nocache=${new Date().getTime()}`;