import Axios from "axios";
-export const CONFIG_URL = process.env.REACT_APP_ARVADOS_CONFIG_URL || "/config.json";
+export const WORKBENCH_CONFIG_URL = process.env.REACT_APP_ARVADOS_CONFIG_URL || "/config.json";
-export interface Config {
- auth: {};
- basePath: string;
+interface WorkbenchConfig {
+ API_HOST: string;
+ VOCABULARY_URL?: string;
+ FILE_VIEWERS_CONFIG_URL?: string;
+}
+
+export interface ClusterConfigJSON {
+ ClusterID: string;
+ RemoteClusters: {
+ [key: string]: {
+ ActivateUsers: boolean
+ Host: string
+ Insecure: boolean
+ Proxy: boolean
+ Scheme: string
+ }
+ };
+ Mail?: {
+ SupportEmailAddress: string;
+ };
+ Services: {
+ Controller: {
+ ExternalURL: string
+ }
+ Workbench1: {
+ ExternalURL: string
+ }
+ Workbench2: {
+ ExternalURL: string
+ }
+ Websocket: {
+ ExternalURL: string
+ }
+ WebDAV: {
+ ExternalURL: string
+ },
+ WebDAVDownload: {
+ ExternalURL: string
+ },
+ WebShell: {
+ ExternalURL: string
+ }
+ };
+ Workbench: {
+ ArvadosDocsite: string;
+ VocabularyURL: string;
+ FileViewersConfigURL: string;
+ WelcomePageHTML: string;
+ InactivePageHTML: string;
+ SSHHelpPageHTML: string;
+ SSHHelpHostSuffix: string;
+ SiteName: string;
+ IdleTimeout: string;
+ };
+ Login: {
+ LoginCluster: string;
+ Google: {
+ Enable: boolean;
+ }
+ LDAP: {
+ Enable: boolean;
+ }
+ OpenIDConnect: {
+ Enable: boolean;
+ }
+ PAM: {
+ Enable: boolean;
+ }
+ SSO: {
+ Enable: boolean;
+ }
+ Test: {
+ Enable: boolean;
+ }
+ };
+ Collections: {
+ ForwardSlashNameSubstitution: string;
+ };
+}
+
+export class Config {
baseUrl: string;
- batchPath: string;
- blobSignatureTtl: number;
- crunchLimitLogBytesPerJob: number;
- crunchLogBytesPerEvent: number;
- crunchLogPartialLineThrottlePeriod: number;
- crunchLogSecondsBetweenEvents: number;
- crunchLogThrottleBytes: number;
- crunchLogThrottleLines: number;
- crunchLogThrottlePeriod: number;
- defaultCollectionReplication: number;
- defaultTrashLifetime: number;
- description: string;
- discoveryVersion: string;
- dockerImageFormats: string[];
- documentationLink: string;
- generatedAt: string;
- gitUrl: string;
- id: string;
keepWebServiceUrl: string;
- kind: string;
- maxRequestSize: number;
- name: string;
- packageVersion: string;
- parameters: {};
- protocol: string;
+ keepWebInlineServiceUrl: string;
remoteHosts: {
[key: string]: string
};
- remoteHostsViaDNS: boolean;
- resources: {};
- revision: string;
rootUrl: string;
- schemas: {};
- servicePath: string;
- sourceVersion: string;
- source_version: string;
- title: string;
uuidPrefix: string;
- version: string;
websocketUrl: string;
workbenchUrl: string;
- workbench2Url?: string;
+ workbench2Url: string;
vocabularyUrl: string;
fileViewersConfigUrl: string;
+ loginCluster: string;
+ clusterConfig: ClusterConfigJSON;
+ apiRevision: number;
}
+export const buildConfig = (clusterConfig: ClusterConfigJSON): Config => {
+ const clusterConfigJSON = removeTrailingSlashes(clusterConfig);
+ const config = new Config();
+ config.rootUrl = clusterConfigJSON.Services.Controller.ExternalURL;
+ config.baseUrl = `${config.rootUrl}/${ARVADOS_API_PATH}`;
+ config.uuidPrefix = clusterConfigJSON.ClusterID;
+ config.websocketUrl = clusterConfigJSON.Services.Websocket.ExternalURL;
+ config.workbench2Url = clusterConfigJSON.Services.Workbench2.ExternalURL;
+ config.workbenchUrl = clusterConfigJSON.Services.Workbench1.ExternalURL;
+ config.keepWebServiceUrl = clusterConfigJSON.Services.WebDAVDownload.ExternalURL;
+ config.keepWebInlineServiceUrl = clusterConfigJSON.Services.WebDAV.ExternalURL;
+ config.loginCluster = clusterConfigJSON.Login.LoginCluster;
+ config.clusterConfig = clusterConfigJSON;
+ config.apiRevision = 0;
+ mapRemoteHosts(clusterConfigJSON, config);
+ return config;
+};
+
+const getApiRevision = async (apiUrl: string) => {
+ try {
+ const dd = (await Axios.get<any>(`${apiUrl}/${DISCOVERY_DOC_PATH}`)).data;
+ return parseInt(dd.revision, 10) || 0;
+ } catch {
+ console.warn("Unable to get API Revision number, defaulting to zero. Some features may not work properly.");
+ return 0;
+ }
+};
+
+const removeTrailingSlashes = (config: ClusterConfigJSON): ClusterConfigJSON => {
+ const svcs: any = {};
+ Object.keys(config.Services).map((s) => {
+ svcs[s] = config.Services[s];
+ if (svcs[s].hasOwnProperty('ExternalURL')) {
+ svcs[s].ExternalURL = svcs[s].ExternalURL.replace(/\/+$/, '');
+ }
+ });
+ return { ...config, Services: svcs };
+};
+
export const fetchConfig = () => {
return Axios
- .get<ConfigJSON>(CONFIG_URL + "?nocache=" + (new Date()).getTime())
+ .get<WorkbenchConfig>(WORKBENCH_CONFIG_URL + "?nocache=" + (new Date()).getTime())
.then(response => response.data)
- .catch(() => Promise.resolve(getDefaultConfig()))
- .then(config => Axios
- .get<Config>(getDiscoveryURL(config.API_HOST))
- .then(response => ({
- // TODO: After tests delete `|| '/vocabulary-example.json'`
- // TODO: After tests delete `|| '/file-viewers-example.json'`
- config: {
- ...response.data,
- vocabularyUrl: config.VOCABULARY_URL || '/vocabulary-example.json',
- fileViewersConfigUrl: config.FILE_VIEWERS_CONFIG_URL || '/file-viewers-example.json'
- },
- apiHost: config.API_HOST,
- })));
+ .catch(() => {
+ console.warn(`There was an exception getting the Workbench config file at ${WORKBENCH_CONFIG_URL}. Using defaults instead.`);
+ return Promise.resolve(getDefaultConfig());
+ })
+ .then(workbenchConfig => {
+ if (workbenchConfig.API_HOST === undefined) {
+ throw new Error(`Unable to start Workbench. API_HOST is undefined in ${WORKBENCH_CONFIG_URL} or the environment.`);
+ }
+ return Axios.get<ClusterConfigJSON>(getClusterConfigURL(workbenchConfig.API_HOST)).then(async response => {
+ const apiRevision = await getApiRevision(response.data.Services.Controller.ExternalURL.replace(/\/+$/, ''));
+ const config = { ...buildConfig(response.data), apiRevision };
+ const warnLocalConfig = (varName: string) => console.warn(
+ `A value for ${varName} was found in ${WORKBENCH_CONFIG_URL}. To use the Arvados centralized configuration instead, \
+remove the entire ${varName} entry from ${WORKBENCH_CONFIG_URL}`);
+
+ // Check if the workbench config has an entry for vocabulary and file viewer URLs
+ // If so, use these values (even if it is an empty string), but print a console warning.
+ // Otherwise, use the cluster config.
+ let fileViewerConfigUrl;
+ if (workbenchConfig.FILE_VIEWERS_CONFIG_URL !== undefined) {
+ warnLocalConfig("FILE_VIEWERS_CONFIG_URL");
+ fileViewerConfigUrl = workbenchConfig.FILE_VIEWERS_CONFIG_URL;
+ }
+ else {
+ fileViewerConfigUrl = config.clusterConfig.Workbench.FileViewersConfigURL || "/file-viewers-example.json";
+ }
+ config.fileViewersConfigUrl = fileViewerConfigUrl;
+
+ let vocabularyUrl;
+ if (workbenchConfig.VOCABULARY_URL !== undefined) {
+ warnLocalConfig("VOCABULARY_URL");
+ vocabularyUrl = workbenchConfig.VOCABULARY_URL;
+ }
+ else {
+ vocabularyUrl = config.clusterConfig.Workbench.VocabularyURL || "/vocabulary-example.json";
+ }
+ config.vocabularyUrl = vocabularyUrl;
+ return { config, apiHost: workbenchConfig.API_HOST };
+ });
+ });
};
+// Maps remote cluster hosts and removes the default RemoteCluster entry
+export const mapRemoteHosts = (clusterConfigJSON: ClusterConfigJSON, config: Config) => {
+ config.remoteHosts = {};
+ Object.keys(clusterConfigJSON.RemoteClusters).forEach(k => { config.remoteHosts[k] = clusterConfigJSON.RemoteClusters[k].Host; });
+ delete config.remoteHosts["*"];
+};
+
+export const mockClusterConfigJSON = (config: Partial<ClusterConfigJSON>): ClusterConfigJSON => ({
+ ClusterID: "",
+ RemoteClusters: {},
+ Services: {
+ Controller: { ExternalURL: "" },
+ Workbench1: { ExternalURL: "" },
+ Workbench2: { ExternalURL: "" },
+ Websocket: { ExternalURL: "" },
+ WebDAV: { ExternalURL: "" },
+ WebDAVDownload: { ExternalURL: "" },
+ WebShell: { ExternalURL: "" },
+ },
+ Workbench: {
+ ArvadosDocsite: "",
+ VocabularyURL: "",
+ FileViewersConfigURL: "",
+ WelcomePageHTML: "",
+ InactivePageHTML: "",
+ SSHHelpPageHTML: "",
+ SSHHelpHostSuffix: "",
+ SiteName: "",
+ IdleTimeout: "0s",
+ },
+ Login: {
+ LoginCluster: "",
+ Google: {
+ Enable: false,
+ },
+ LDAP: {
+ Enable: false,
+ },
+ OpenIDConnect: {
+ Enable: false,
+ },
+ PAM: {
+ Enable: false,
+ },
+ SSO: {
+ Enable: false,
+ },
+ Test: {
+ Enable: false,
+ },
+ },
+ Collections: {
+ ForwardSlashNameSubstitution: "",
+ },
+ ...config
+});
+
export const mockConfig = (config: Partial<Config>): Config => ({
- auth: {},
- basePath: '',
- baseUrl: '',
- batchPath: '',
- blobSignatureTtl: 0,
- crunchLimitLogBytesPerJob: 0,
- crunchLogBytesPerEvent: 0,
- crunchLogPartialLineThrottlePeriod: 0,
- crunchLogSecondsBetweenEvents: 0,
- crunchLogThrottleBytes: 0,
- crunchLogThrottleLines: 0,
- crunchLogThrottlePeriod: 0,
- defaultCollectionReplication: 0,
- defaultTrashLifetime: 0,
- description: '',
- discoveryVersion: '',
- dockerImageFormats: [],
- documentationLink: '',
- generatedAt: '',
- gitUrl: '',
- id: '',
- keepWebServiceUrl: '',
- kind: '',
- maxRequestSize: 0,
- name: '',
- packageVersion: '',
- parameters: {},
- protocol: '',
+ baseUrl: "",
+ keepWebServiceUrl: "",
+ keepWebInlineServiceUrl: "",
remoteHosts: {},
- remoteHostsViaDNS: false,
- resources: {},
- revision: '',
- rootUrl: '',
- schemas: {},
- servicePath: '',
- sourceVersion: '',
- source_version: '',
- title: '',
- uuidPrefix: '',
- version: '',
- websocketUrl: '',
- workbenchUrl: '',
- vocabularyUrl: '',
- fileViewersConfigUrl: '',
+ rootUrl: "",
+ uuidPrefix: "",
+ websocketUrl: "",
+ workbenchUrl: "",
+ workbench2Url: "",
+ vocabularyUrl: "",
+ fileViewersConfigUrl: "",
+ loginCluster: "",
+ clusterConfig: mockClusterConfigJSON({}),
+ apiRevision: 0,
...config
});
-interface ConfigJSON {
- API_HOST: string;
- VOCABULARY_URL: string;
- FILE_VIEWERS_CONFIG_URL: string;
-}
-
-const getDefaultConfig = (): ConfigJSON => ({
- API_HOST: process.env.REACT_APP_ARVADOS_API_HOST || "",
- VOCABULARY_URL: "",
- FILE_VIEWERS_CONFIG_URL: "",
-});
+const getDefaultConfig = (): WorkbenchConfig => {
+ let apiHost = "";
+ const envHost = process.env.REACT_APP_ARVADOS_API_HOST;
+ if (envHost !== undefined) {
+ console.warn(`Using default API host ${envHost}.`);
+ apiHost = envHost;
+ }
+ else {
+ console.warn(`No API host was found in the environment. Workbench may not be able to communicate with Arvados components.`);
+ }
+ return {
+ API_HOST: apiHost,
+ VOCABULARY_URL: undefined,
+ FILE_VIEWERS_CONFIG_URL: undefined,
+ };
+};
-export const DISCOVERY_URL = 'discovery/v1/apis/arvados/v1/rest';
-export const getDiscoveryURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${DISCOVERY_URL}?nocache=${(new Date()).getTime()}`;
+export const ARVADOS_API_PATH = "arvados/v1";
+export const CLUSTER_CONFIG_PATH = "arvados/v1/config";
+export const DISCOVERY_DOC_PATH = "discovery/v1/apis/arvados/v1/rest";
+export const getClusterConfigURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${(new Date()).getTime()}`;