import { initAdvanceFormProjectsTree } from '~/store/search-bar/search-bar-actions';
import { repositoryActionSet } from '~/views-components/context-menu/action-sets/repository-action-set';
import { sshKeyActionSet } from '~/views-components/context-menu/action-sets/ssh-key-action-set';
+ import { keepServiceActionSet } from '~/views-components/context-menu/action-sets/keep-service-action-set';
import { loadVocabulary } from '~/store/vocabulary/vocabulary-actions';
+import { virtualMachineActionSet } from '~/views-components/context-menu/action-sets/virtual-machine-action-set';
console.log(`Starting arvados [${getBuildInfo()}]`);
addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet);
addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet);
+addMenuActionSet(ContextMenuKind.VIRTUAL_MACHINE, virtualMachineActionSet);
+ addMenuActionSet(ContextMenuKind.KEEP_SERVICE, keepServiceActionSet);
fetchConfig()
.then(({ config, apiHost }) => {
import { FilterBuilder } from '~/services/api/filter-builder';
import { RepositoryResource } from '~/models/repositories';
import { SshKeyResource } from '~/models/ssh-key';
+import { VirtualMachinesResource } from '~/models/virtual-machines';
+import { UserResource } from '~/models/user';
+import { ListResults } from '~/services/common-service/common-resource-service';
+import { LinkResource } from '~/models/link';
+ import { KeepServiceResource } from '~/models/keep-services';
export const ADVANCED_TAB_DIALOG = 'advancedTabDialog';
CREATED_AT = 'created_at'
}
- VIRTUAL_MACHINES = 'virtual_machines'
+enum VirtualMachineData {
+ VIRTUAL_MACHINE = 'virtual_machine',
+ CREATED_AT = 'created_at'
+}
+
+enum ResourcePrefix {
+ REPOSITORIES = 'repositories',
+ AUTORIZED_KEYS = 'authorized_keys',
- type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData;
++ VIRTUAL_MACHINES = 'virtual_machines',
++ KEEP_SERVICES = 'keep_services'
+}
+
-type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | KeepServiceData;
-type AdvanceResourcePrefix = GroupContentsResourcePrefix | 'repositories' | 'authorized_keys' | 'keep_services';
+ enum KeepServiceData {
+ KEEP_SERVICE = 'keep_services',
+ CREATED_AT = 'created_at'
+ }
+
++type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData;
+type AdvanceResourcePrefix = GroupContentsResourcePrefix | ResourcePrefix;
-export const openAdvancedTabDialog = (uuid: string, index?: number) =>
+export const openAdvancedTabDialog = (uuid: string) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const kind = extractUuidKind(uuid);
switch (kind) {
dispatch<any>(initAdvancedTabDialog(advanceDataRepository));
break;
case ResourceKind.SSH_KEY:
- const dataSshKey = getState().auth.sshKeys[index!];
- const advanceDataSshKey: AdvancedTabDialogData = advancedTabData(uuid, '', '', sshKeyApiResponse, dataSshKey, SshKeyData.SSH_KEY, 'authorized_keys', SshKeyData.CREATED_AT, dataSshKey.createdAt);
+ const dataSshKey = getState().auth.sshKeys.find(it => it.uuid === uuid);
+ const advanceDataSshKey: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: '',
+ user: '',
+ apiResponseKind: sshKeyApiResponse,
+ data: dataSshKey,
+ resourceKind: SshKeyData.SSH_KEY,
+ resourcePrefix: ResourcePrefix.AUTORIZED_KEYS,
+ resourceKindProperty: SshKeyData.CREATED_AT,
+ property: dataSshKey!.createdAt
+ });
dispatch<any>(initAdvancedTabDialog(advanceDataSshKey));
break;
- const dataKeepService = getState().keepServices[index!];
- const advanceDataKeepService: AdvancedTabDialogData = advancedTabData(uuid, '', '', keepServiceApiResponse, dataKeepService, KeepServiceData.KEEP_SERVICE, 'keep_services', KeepServiceData.CREATED_AT, dataKeepService.createdAt);
+ case ResourceKind.VIRTUAL_MACHINE:
+ const dataVirtualMachine = getState().virtualMachines.virtualMachines.items.find(it => it.uuid === uuid);
+ const advanceDataVirtualMachine: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: '',
+ user: '',
+ apiResponseKind: virtualMachineApiResponse,
+ data: dataVirtualMachine,
+ resourceKind: VirtualMachineData.VIRTUAL_MACHINE,
+ resourcePrefix: ResourcePrefix.VIRTUAL_MACHINES,
+ resourceKindProperty: VirtualMachineData.CREATED_AT,
+ property: dataVirtualMachine.createdAt
+ });
+ dispatch<any>(initAdvancedTabDialog(advanceDataVirtualMachine));
+ break;
+ case ResourceKind.KEEP_SERVICE:
++ const dataKeepService = getState().keepServices.find(it => it.uuid === uuid);
++ const advanceDataKeepService: AdvancedTabDialogData = advancedTabData({
++ uuid,
++ metadata: '',
++ user: '',
++ apiResponseKind: keepServiceApiResponse,
++ data: dataKeepService,
++ resourceKind: KeepServiceData.KEEP_SERVICE,
++ resourcePrefix: ResourcePrefix.KEEP_SERVICES,
++ resourceKindProperty: KeepServiceData.CREATED_AT,
++ property: dataKeepService!.createdAt
++ });
+ dispatch<any>(initAdvancedTabDialog(advanceDataKeepService));
+ break;
default:
dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Could not open advanced tab for this resource.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
}
return response;
};
- const response = `"uuid": "${uuid}",
+const virtualMachineApiResponse = (apiResponse: VirtualMachinesResource) => {
+ const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, hostname } = apiResponse;
- "hostname": ${stringify(hostname)},
++ const response = `"hostname": ${stringify(hostname)},
++"uuid": "${uuid}",
+"owner_uuid": "${ownerUuid}",
+"modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
+"modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
+"modified_at": ${stringify(modifiedAt)},
++"modified_at": ${stringify(modifiedAt)},
+"created_at": "${createdAt}"`;
++
++ return response;
++};
++
+ const keepServiceApiResponse = (apiResponse: KeepServiceResource) => {
+ const {
+ uuid, readOnly, serviceHost, servicePort, serviceSslFlag, serviceType,
+ ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid
+ } = apiResponse;
+ const response = `"uuid": "${uuid}",
+ "owner_uuid": "${ownerUuid}",
+ "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
+ "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
+ "modified_at": ${stringify(modifiedAt)},
+ "service_host": "${serviceHost}",
+ "service_port": "${servicePort}",
+ "service_ssl_flag": "${stringify(serviceSslFlag)}",
+ "service_type": "${serviceType}",
+ "created_at": "${createdAt}",
+ "read_only": "${stringify(readOnly)}"`;
+
return response;
};
import { Process } from '~/store/processes/process';
import { RepositoryResource } from '~/models/repositories';
import { SshKeyResource } from '~/models/ssh-key';
+import { VirtualMachinesResource } from '~/models/virtual-machines';
+ import { KeepServiceResource } from '~/models/keep-services';
export const contextMenuActions = unionize({
OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
}));
};
-export const openKeepServiceContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, keepService: KeepServiceResource) =>
++export const openKeepServiceContextMenu = (event: React.MouseEvent<HTMLElement>, keepService: KeepServiceResource) =>
+ (dispatch: Dispatch) => {
+ dispatch<any>(openContextMenu(event, {
+ name: '',
+ uuid: keepService.uuid,
+ ownerUuid: keepService.ownerUuid,
+ kind: ResourceKind.KEEP_SERVICE,
- menuKind: ContextMenuKind.KEEP_SERVICE,
- index
++ menuKind: ContextMenuKind.KEEP_SERVICE
+ }));
+ };
+
export const openRootProjectContextMenu = (event: React.MouseEvent<HTMLElement>, projectUuid: string) =>
(dispatch: Dispatch, getState: () => RootState) => {
const res = getResource<UserResource>(projectUuid)(getState().resources);
--- /dev/null
-export const openKeepServiceAttributesDialog = (index: number) =>
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+
+ import { Dispatch } from "redux";
+ import { unionize, ofType, UnionOf } from "~/common/unionize";
+ import { RootState } from '~/store/store';
+ import { setBreadcrumbs } from '~/store/breadcrumbs/breadcrumbs-actions';
+ import { ServiceRepository } from "~/services/services";
+ import { KeepServiceResource } from '~/models/keep-services';
+ import { dialogActions } from '~/store/dialog/dialog-actions';
+ import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+ import { navigateToRootProject } from '~/store/navigation/navigation-action';
+
+ export const keepServicesActions = unionize({
+ SET_KEEP_SERVICES: ofType<KeepServiceResource[]>(),
+ REMOVE_KEEP_SERVICE: ofType<string>()
+ });
+
+ export type KeepServicesActions = UnionOf<typeof keepServicesActions>;
+
+ export const KEEP_SERVICE_REMOVE_DIALOG = 'keepServiceRemoveDialog';
+ export const KEEP_SERVICE_ATTRIBUTES_DIALOG = 'keepServiceAttributesDialog';
+
+ export const loadKeepServicesPanel = () =>
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ const user = getState().auth.user;
+ if(user && user.isAdmin) {
+ try {
+ dispatch(setBreadcrumbs([{ label: 'Keep Services' }]));
+ const response = await services.keepService.list();
+ dispatch(keepServicesActions.SET_KEEP_SERVICES(response.items));
+ } catch (e) {
+ return;
+ }
+ } else {
+ dispatch(navigateToRootProject);
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "You don't have permissions to view this page", hideDuration: 2000 }));
+ }
+ };
+
- const keepService = getState().keepServices[index];
++export const openKeepServiceAttributesDialog = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState) => {
++ const keepService = getState().keepServices.find(it => it.uuid === uuid);
+ dispatch(dialogActions.OPEN_DIALOG({ id: KEEP_SERVICE_ATTRIBUTES_DIALOG, data: { keepService } }));
+ };
+
+ export const openKeepServiceRemoveDialog = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState) => {
+ dispatch(dialogActions.OPEN_DIALOG({
+ id: KEEP_SERVICE_REMOVE_DIALOG,
+ data: {
+ title: 'Remove keep service',
+ text: 'Are you sure you want to remove this keep service?',
+ confirmButtonLabel: 'Remove',
+ uuid
+ }
+ }));
+ };
+
+ export const removeKeepService = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+ try {
+ await services.keepService.delete(uuid);
+ dispatch(keepServicesActions.REMOVE_KEEP_SERVICE(uuid));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Keep service has been successfully removed.', hideDuration: 2000 }));
+ } catch (e) {
+ return;
+ }
+ };
--- /dev/null
- execute: (dispatch, { index }) => {
- dispatch<any>(openKeepServiceAttributesDialog(index!));
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+
+ import { openKeepServiceAttributesDialog, openKeepServiceRemoveDialog } from '~/store/keep-services/keep-services-actions';
+ import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab';
+ import { ContextMenuActionSet } from "~/views-components/context-menu/context-menu-action-set";
+ import { AdvancedIcon, RemoveIcon, AttributesIcon } from "~/components/icon/icon";
+
+ export const keepServiceActionSet: ContextMenuActionSet = [[{
+ name: "Attributes",
+ icon: AttributesIcon,
- execute: (dispatch, { uuid, index }) => {
- dispatch<any>(openAdvancedTabDialog(uuid, index));
++ execute: (dispatch, { uuid }) => {
++ dispatch<any>(openKeepServiceAttributesDialog(uuid));
+ }
+ }, {
+ name: "Advanced",
+ icon: AdvancedIcon,
++ execute: (dispatch, { uuid }) => {
++ dispatch<any>(openAdvancedTabDialog(uuid));
+ }
+ }, {
+ name: "Remove",
+ icon: RemoveIcon,
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openKeepServiceRemoveDialog(uuid));
+ }
+ }]];
PROCESS_LOGS = "ProcessLogs",
REPOSITORY = "Repository",
SSH_KEY = "SshKey",
- VIRTUAL_MACHINE = "VirtualMachine"
++ VIRTUAL_MACHINE = "VirtualMachine",
+ KEEP_SERVICE = "KeepService"
}
--- /dev/null
- openRowOptions: (event: React.MouseEvent<HTMLElement>, index: number, keepService: KeepServiceResource) => void;
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+
+ import * as React from 'react';
+ import { StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Button, Typography, Grid, Table, TableHead, TableRow, TableCell, TableBody, Tooltip, IconButton, Checkbox } from '@material-ui/core';
+ import { ArvadosTheme } from '~/common/custom-theme';
+ import { MoreOptionsIcon } from '~/components/icon/icon';
+ import { KeepServiceResource } from '~/models/keep-services';
+
+ type CssRules = 'root' | 'tableRow';
+
+ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ width: '100%',
+ overflow: 'auto'
+ },
+ tableRow: {
+ '& td, th': {
+ whiteSpace: 'nowrap'
+ }
+ }
+ });
+
+ export interface KeepServicePanelRootActionProps {
- <IconButton onClick={event => openRowOptions(event, index, keepService)}>
++ openRowOptions: (event: React.MouseEvent<HTMLElement>, keepService: KeepServiceResource) => void;
+ }
+
+ export interface KeepServicePanelRootDataProps {
+ keepServices: KeepServiceResource[];
+ hasKeepSerices: boolean;
+ }
+
+ type KeepServicePanelRootProps = KeepServicePanelRootActionProps & KeepServicePanelRootDataProps & WithStyles<CssRules>;
+
+ export const KeepServicePanelRoot = withStyles(styles)(
+ ({ classes, hasKeepSerices, keepServices, openRowOptions }: KeepServicePanelRootProps) =>
+ <Card className={classes.root}>
+ <CardContent>
+ {hasKeepSerices && <Grid container direction="row">
+ <Grid item xs={12}>
+ <Table>
+ <TableHead>
+ <TableRow className={classes.tableRow}>
+ <TableCell>UUID</TableCell>
+ <TableCell>Read only</TableCell>
+ <TableCell>Service host</TableCell>
+ <TableCell>Service port</TableCell>
+ <TableCell>Service SSL flag</TableCell>
+ <TableCell>Service type</TableCell>
+ <TableCell />
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {keepServices.map((keepService, index) =>
+ <TableRow key={index} className={classes.tableRow}>
+ <TableCell>{keepService.uuid}</TableCell>
+ <TableCell>
+ <Checkbox
+ disableRipple
+ color="primary"
+ checked={keepService.readOnly} />
+ </TableCell>
+ <TableCell>{keepService.serviceHost}</TableCell>
+ <TableCell>{keepService.servicePort}</TableCell>
+ <TableCell>
+ <Checkbox
+ disableRipple
+ color="primary"
+ checked={keepService.serviceSslFlag} />
+ </TableCell>
+ <TableCell>{keepService.serviceType}</TableCell>
+ <TableCell>
+ <Tooltip title="More options" disableFocusListener>
++ <IconButton onClick={event => openRowOptions(event, keepService)}>
+ <MoreOptionsIcon />
+ </IconButton>
+ </Tooltip>
+ </TableCell>
+ </TableRow>)}
+ </TableBody>
+ </Table>
+ </Grid>
+ </Grid>}
+ </CardContent>
+ </Card>
+ );
--- /dev/null
- openRowOptions: (event, index, keepService) => {
- dispatch<any>(openKeepServiceContextMenu(event, index, keepService));
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+
+ import { RootState } from '~/store/store';
+ import { Dispatch } from 'redux';
+ import { connect } from 'react-redux';
+ import { } from '~/store/keep-services/keep-services-actions';
+ import {
+ KeepServicePanelRoot,
+ KeepServicePanelRootDataProps,
+ KeepServicePanelRootActionProps
+ } from '~/views/keep-service-panel/keep-service-panel-root';
+ import { openKeepServiceContextMenu } from '~/store/context-menu/context-menu-actions';
+
+ const mapStateToProps = (state: RootState): KeepServicePanelRootDataProps => {
+ return {
+ keepServices: state.keepServices,
+ hasKeepSerices: state.keepServices.length > 0
+ };
+ };
+
+ const mapDispatchToProps = (dispatch: Dispatch): KeepServicePanelRootActionProps => ({
++ openRowOptions: (event, keepService) => {
++ dispatch<any>(openKeepServiceContextMenu(event, keepService));
+ }
+ });
+
+ export const KeepServicePanel = connect(mapStateToProps, mapDispatchToProps)(KeepServicePanelRoot);
import { RemoveRepositoryDialog } from '~/views-components/repository-remove-dialog/repository-remove-dialog';
import { CreateSshKeyDialog } from '~/views-components/dialog-forms/create-ssh-key-dialog';
import { PublicKeyDialog } from '~/views-components/ssh-keys-dialog/public-key-dialog';
+ import { RemoveKeepServiceDialog } from '~/views-components/keep-services-dialog/remove-dialog';
import { RemoveSshKeyDialog } from '~/views-components/ssh-keys-dialog/remove-dialog';
+ import { AttributesKeepServiceDialog } from '~/views-components/keep-services-dialog/attributes-dialog';
import { AttributesSshKeyDialog } from '~/views-components/ssh-keys-dialog/attributes-dialog';
+import { VirtualMachineAttributesDialog } from '~/views-components/virtual-machines-dialog/attributes-dialog';
+import { RemoveVirtualMachineDialog } from '~/views-components/virtual-machines-dialog/remove-dialog';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';