Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla@contractors.roche.com>
Scheme: string
}
};
- Mail?: {
- SupportEmailAddress: string;
+ Mail?: {
+ SupportEmailAddress: string;
+ };
+ Services: {
+ Controller: {
+ ExternalURL: string;
};
- Services: {
- Controller: {
- ExternalURL: string
- }
- Workbench1: {
- ExternalURL: string
- }
- Workbench2: {
- ExternalURL: string
- }
- Websocket: {
- ExternalURL: string
- }
- WebDAV: {
- ExternalURL: string
- },
- WebDAVDownload: {
- ExternalURL: string
- },
- WebShell: {
- ExternalURL: string
- }
+ Workbench1: {
+ ExternalURL: string;
+ };
+ Workbench2: {
+ ExternalURL: string;
};
- BannerUUID: string;
+ Workbench: {
+ DisableSharingURLsUI: boolean;
+ ArvadosDocsite: 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;
- }
+ };
+ Websocket: {
+ ExternalURL: string;
};
- Collections: {
- ForwardSlashNameSubstitution: string;
- ManagedProperties?: {
- [key: string]: {
- Function: string,
- Value: string,
- Protected?: boolean,
- }
- },
- TrustAllContent: boolean
+ WebDAV: {
+ ExternalURL: string;
};
- Volumes: {
- [key: string]: {
- StorageClasses: {
- [key: string]: boolean;
- }
- }
+ WebDAVDownload: {
+ ExternalURL: string;
+ };
+ WebShell: {
+ ExternalURL: string;
+ };
+ };
+ Workbench: {
+ DisableSharingURLsUI: boolean;
+ ArvadosDocsite: string;
+ FileViewersConfigURL: string;
+ WelcomePageHTML: string;
+ InactivePageHTML: string;
+ SSHHelpPageHTML: string;
+ SSHHelpHostSuffix: string;
+ SiteName: string;
+ IdleTimeout: string;
++ BannerUUID: 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;
+ ManagedProperties?: {
+ [key: string]: {
+ Function: string;
+ Value: string;
+ Protected?: boolean;
+ };
+ };
+ TrustAllContent: boolean;
+ };
+ Volumes: {
+ [key: string]: {
+ StorageClasses: {
+ [key: string]: boolean;
+ };
+ };
+ };
}
export class Config {
};
// 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 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 => ({
- API: {
- UnfreezeProjectRequiresAdmin: false,
+export const mockClusterConfigJSON = (
+ config: Partial<ClusterConfigJSON>
+): ClusterConfigJSON => ({
+ API: {
+ UnfreezeProjectRequiresAdmin: false,
+ MaxItemsPerResponse: 1000,
+ },
+ ClusterID: '',
+ RemoteClusters: {},
+ Services: {
+ Controller: { ExternalURL: '' },
+ Workbench1: { ExternalURL: '' },
+ Workbench2: { ExternalURL: '' },
+ Websocket: { ExternalURL: '' },
+ WebDAV: { ExternalURL: '' },
+ WebDAVDownload: { ExternalURL: '' },
+ WebShell: { ExternalURL: '' },
++ Workbench: {
++ DisableSharingURLsUI: false,
++ ArvadosDocsite: "",
++ FileViewersConfigURL: "",
++ WelcomePageHTML: "",
++ InactivePageHTML: "",
++ SSHHelpPageHTML: "",
++ SSHHelpHostSuffix: "",
++ SiteName: "",
++ IdleTimeout: "0s",
+ },
- ClusterID: "",
- RemoteClusters: {},
- Services: {
- Controller: { ExternalURL: "" },
- Workbench1: { ExternalURL: "" },
- Workbench2: { ExternalURL: "" },
- Websocket: { ExternalURL: "" },
- WebDAV: { ExternalURL: "" },
- WebDAVDownload: { ExternalURL: "" },
- WebShell: { ExternalURL: "" },
+ },
+ Workbench: {
+ DisableSharingURLsUI: false,
+ ArvadosDocsite: '',
+ FileViewersConfigURL: '',
+ WelcomePageHTML: '',
+ InactivePageHTML: '',
+ SSHHelpPageHTML: '',
+ SSHHelpHostSuffix: '',
+ SiteName: '',
+ IdleTimeout: '0s',
++ BannerUUID: "",
+ },
+ Login: {
+ LoginCluster: '',
+ Google: {
+ Enable: false,
},
- Workbench: {
- DisableSharingURLsUI: false,
- ArvadosDocsite: "",
- FileViewersConfigURL: "",
- WelcomePageHTML: "",
- InactivePageHTML: "",
- SSHHelpPageHTML: "",
- SSHHelpHostSuffix: "",
- SiteName: "",
- IdleTimeout: "0s",
- BannerUUID: "",
+ LDAP: {
+ Enable: false,
+ },
+ OpenIDConnect: {
+ Enable: false,
},
- Login: {
- LoginCluster: "",
- Google: {
- Enable: false,
- },
- LDAP: {
- Enable: false,
- },
- OpenIDConnect: {
- Enable: false,
- },
- PAM: {
- Enable: false,
- },
- SSO: {
- Enable: false,
- },
- Test: {
- Enable: false,
- },
+ PAM: {
+ Enable: false,
},
- Collections: {
- ForwardSlashNameSubstitution: "",
- TrustAllContent: false,
+ SSO: {
+ Enable: false,
},
- Volumes: {},
- ...config
+ Test: {
+ Enable: false,
+ },
+ },
+ Collections: {
+ ForwardSlashNameSubstitution: '',
+ TrustAllContent: false,
+ },
+ Volumes: {},
+ ...config,
});
export const mockConfig = (config: Partial<Config>): Config => ({
import { Config } from 'common/config';
import { pluginConfig } from 'plugins';
import { MiddlewareListReducer } from 'common/plugintypes';
- import { sidePanelReducer } from './side-panel/side-panel-reducer'
++import { sidePanelReducer } from './side-panel/side-panel-reducer';
+import { tooltipsMiddleware } from './tooltips/tooltips-middleware';
+ import { bannerReducer } from './banner/banner-reducer';
+
+
declare global {
interface Window {
- __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
+ __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
}
}
--- /dev/null
- const TOOLTIP_LOCAL_STORAGE_KEY = "TOOLTIP_LOCAL_STORAGE_KEY";
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { CollectionDirectory, CollectionFile } from "models/collection-file";
+import { Middleware, Store } from "redux";
+import { ServiceRepository } from "services/services";
+import { RootState } from "store/store";
+import tippy, { createSingleton } from 'tippy.js';
+import 'tippy.js/dist/tippy.css';
+
+let running = false;
+let tooltipsContents = null;
+let tooltipsFetchFailed = false;
- const result = localStorage.getItem(TOOLTIP_LOCAL_STORAGE_KEY);
- const { BannerURL } = (state.auth.config.clusterConfig.Workbench as any);
++export const TOOLTIP_LOCAL_STORAGE_KEY = "TOOLTIP_LOCAL_STORAGE_KEY";
++
++const tippySingleton = createSingleton([], {delay: 10});
+
+export const tooltipsMiddleware = (services: ServiceRepository): Middleware => (store: Store) => next => action => {
+ const state: RootState = store.getState();
- let bannerUUID = !!BannerURL ? BannerURL : 'tordo-4zz18-1buneu6sb8zxiti';
++ const hideTooltip = localStorage.getItem(TOOLTIP_LOCAL_STORAGE_KEY);
++ const { BannerUUID } = (state.auth.config.clusterConfig.Workbench as any);
+
- if (bannerUUID && !tooltipsContents && !result && !tooltipsFetchFailed && !running) {
++ const bannerUUID = BannerUUID || 'tordo-4zz18-1buneu6sb8zxiti';
+
- } else if (tooltipsContents && !result && !tooltipsFetchFailed) {
++ if (bannerUUID && !tooltipsContents && !hideTooltip && !tooltipsFetchFailed && !running) {
+ running = true;
+ fetchTooltips(services, bannerUUID);
- createSingleton(tippyInstances, {delay: 10});
++ } else if (tooltipsContents && !hideTooltip && !tooltipsFetchFailed) {
+ applyTooltips();
+ }
+
+ return next(action);
+};
+
+const fetchTooltips = (services, bannerUUID) => {
+ services.collectionService.files(bannerUUID)
+ .then(results => {
+ const tooltipsFile: CollectionDirectory | CollectionFile | undefined = results.find(({ name }) => name === 'tooltips.json');
+
+ if (tooltipsFile) {
+ running = true;
+ services.collectionService.getFileContents(tooltipsFile as CollectionFile)
+ .then(data => {
+ tooltipsContents = JSON.parse(data);
+ applyTooltips();
+ })
+ .catch(() => {})
+ .finally(() => {
+ running = false;
+ });
+ } else {
+ tooltipsFetchFailed = true;
+ }
+ })
+ .catch(() => {})
+ .finally(() => {
+ running = false;
+ });
+};
+
+const applyTooltips = () => {
+ const tippyInstances: any[] = Object.keys(tooltipsContents as any)
+ .map((key) => {
+ const content = (tooltipsContents as any)[key]
+ const element = document.querySelector(key);
+
+ if (element) {
+ const hasTippyAttatched = !!(element as any)._tippy;
+
+ if (!hasTippyAttatched && tooltipsContents) {
+ return tippy(element as any, { content });
+ }
+ }
+
+ return null;
+ })
+ .filter(data => !!data);
+
++ if (tippyInstances.length > 0) {
++ tippySingleton.setInstances(tippyInstances);
++ }
+};
// SPDX-License-Identifier: AGPL-3.0
import React from "react";
- import { Badge, MenuItem } from '@material-ui/core';
+ import { Dispatch } from "redux";
+ import { connect } from "react-redux";
+ import { Badge, MenuItem } from "@material-ui/core";
import { DropdownMenu } from "components/dropdown-menu/dropdown-menu";
- import { NotificationIcon } from 'components/icon/icon';
-
- export const NotificationsMenu =
- () =>
- <DropdownMenu
- icon={
- <Badge
- badgeContent={0}
- color="primary">
- <NotificationIcon />
- </Badge>}
- id="account-menu"
- title="Notifications">
- <MenuItem>You are up to date</MenuItem>
- </DropdownMenu>;
+ import { NotificationIcon } from "components/icon/icon";
+ import bannerActions from "store/banner/banner-action";
+ import { BANNER_LOCAL_STORAGE_KEY } from "views-components/baner/banner";
+ import { RootState } from "store/store";
++import { TOOLTIP_LOCAL_STORAGE_KEY } from "store/tooltips/tooltips-middleware";
++import { useCallback } from "react";
- const result = localStorage.getItem(BANNER_LOCAL_STORAGE_KEY);
+ const mapStateToProps = (state: RootState): NotificationsMenuProps => ({
+ isOpen: state.banner.isOpen,
+ bannerUUID: state.auth.config.clusterConfig.Workbench.BannerUUID,
+ });
+
+ const mapDispatchToProps = (dispatch: Dispatch) => ({
+ openBanner: () => dispatch<any>(bannerActions.openBanner()),
+ });
+
+ type NotificationsMenuProps = {
+ isOpen: boolean;
+ bannerUUID?: string;
+ }
+
+ type NotificationsMenuComponentProps = NotificationsMenuProps & {
+ openBanner: any;
+ }
+
+ export const NotificationsMenuComponent = (props: NotificationsMenuComponentProps) => {
+ const { isOpen, openBanner } = props;
- if (!isOpen && result) {
++ const bannerResult = localStorage.getItem(BANNER_LOCAL_STORAGE_KEY);
++ const tooltipResult = localStorage.getItem(TOOLTIP_LOCAL_STORAGE_KEY);
+ const menuItems: any[] = [];
+
- menuItems.map(item => item)
++ if (!isOpen && bannerResult) {
+ menuItems.push(<MenuItem><span onClick={openBanner}>Restore Banner</span></MenuItem>);
+ }
+
++ const toggleTooltips = useCallback(() => {
++ if (tooltipResult) {
++ localStorage.removeItem(TOOLTIP_LOCAL_STORAGE_KEY);
++ } else {
++ localStorage.setItem(TOOLTIP_LOCAL_STORAGE_KEY, 'true');
++ }
++ window.location.reload();
++ }, [tooltipResult]);
++
++ if (tooltipResult) {
++ menuItems.push(<MenuItem><span onClick={toggleTooltips}>Enable tooltips</span></MenuItem>);
++ } else {
++ menuItems.push(<MenuItem><span onClick={toggleTooltips}>Disable tooltips</span></MenuItem>);
++ }
++
+ if (menuItems.length === 0) {
+ menuItems.push(<MenuItem>You are up to date</MenuItem>);
+ }
+
+ return (<DropdownMenu
+ icon={
+ <Badge
+ badgeContent={0}
+ color="primary">
+ <NotificationIcon />
+ </Badge>}
+ id="account-menu"
+ title="Notifications">
+ {
++ menuItems.map((item, i) => <div key={i}>{item}</div>)
+ }
+ </DropdownMenu>);
+ }
+
+ export const NotificationsMenu = connect(mapStateToProps, mapDispatchToProps)(NotificationsMenuComponent);
import { WebDavS3InfoDialog } from 'views-components/webdav-s3-dialog/webdav-s3-dialog';
import { pluginConfig } from 'plugins';
import { ElementListReducer } from 'common/plugintypes';
+import { COLLAPSE_ICON_SIZE } from 'views-components/side-panel-toggle/side-panel-toggle'
+ import { Banner } from 'views-components/baner/banner';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
<VirtualMachineAttributesDialog />
<FedLogin />
<WebDavS3InfoDialog />
+ <Banner />
{React.createElement(React.Fragment, null, pluginConfig.dialogs)}
- </Grid>
+ </Grid>}
);