Commit 5b6575ce authored by Ryo Yamashita's avatar Ryo Yamashita Committed by Yas Naoi
Browse files

Issue #3322385 by Ryo Yamashita, yas: Fix the action of the sidebar on the Cloud Dashboard

parent c4c7081a
Loading
Loading
Loading
Loading
+10 −0
Changes for modules/cloud_dashboard/cloud_dashboard/src/constant/others/other.ts: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -33,6 +33,16 @@ export const CLOUD_SERVICE_PROVIDER_LIST: CloudServiceProvider[] = [
  'aws_cloud', 'k8s', 'openstack', 'vmware',
];

export const CLOUD_SERVICE_PROVIDER_INFO: Record<CloudServiceProvider, {
  entityTypeId: string,
  label: string,
}> = {
  'aws_cloud': { entityTypeId: 'instance', label: 'AWS Cloud' },
  'k8s': { entityTypeId: 'node', label: 'K8s' },
  'openstack': { entityTypeId: 'instance', label: 'OpenStack' },
  'vmware': { entityTypeId: 'vm', label: 'VMware' },
};

export const VMWARE_OS_DICT: Record<string, string> = {
  'WINDOWS_HYPERV': 'Windows Hyper-V',
  'WINDOWS_9_SERVER_64': 'Windows 10 Server (64 bit)',
+4 −0
Changes for modules/cloud_dashboard/cloud_dashboard/src/molecules/DataTableRow.tsx: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -24,9 +24,13 @@ const DataTableRow = ({ dataRecord, dataColumnList, className, detailInfo }: {
    {dataColumnList.map((dataColumn) => {
      let link = undefined;
      if (dataColumn.key === detailInfo?.column) {
        if ('url' in dataRecord.value) {
          link = dataRecord.value['url'];
        } else {
          const temp = detailInfo.path.split('/');
          link = `/${temp[1]}/${dataRecord.cloudContext}/${temp[2]}/${dataRecord.id}`;
        }
      }

      return <DataTableData key={dataColumn.key} text={
        dataColumn.key in dataRecord.value
+10 −1
Changes for modules/cloud_dashboard/cloud_dashboard/src/organisms/CloudServiceProviderTable.tsx: 10 added lines, 1 removed line.
Original line number Diff line number Diff line
import { CLOUD_SERVICE_PROVIDER_INFO } from 'constant/others/other';
import SortInfo from 'model/SortInfo';
import LoadingSpinner from 'molecules/LoadingSpinner';
import DataTable from 'organisms/DataTable';
@@ -39,10 +40,14 @@ const CloudServiceProviderTable = () => {
              })
              .filter((r) => r.name !== 'ALL')
              .map((r) => {
                const temp = CLOUD_SERVICE_PROVIDER_INFO[r.cloudServiceProvider];
                return {
                  id: `${r.cloudServiceProvider}_${r.name}`,
                  entityTypeId: '',
                  value: { 'labelName': r.labelName },
                  value: {
                    'labelName': r.labelName,
                    'url': `/${r.cloudServiceProvider}/${r.name}/${temp.entityTypeId}`
                  },
                  cloudContext: r.name
                };
              })
@@ -51,6 +56,10 @@ const CloudServiceProviderTable = () => {
          setSortInfo={setSortInfo}
          hasOperationLinks={false}
          operationLinksName="Operations"
          detailInfo={{
            column: 'labelName',
            path: 'test'
          }}
        />
    }
  </Form>;
+225 −128
Changes for modules/cloud_dashboard/cloud_dashboard/src/organisms/SideBar.tsx: 225 added lines, 128 removed lines.
Original line number Diff line number Diff line
import { CLOUD_SERVICE_PROVIDER_LIST, ROUTE_URL } from 'constant/others/other';
import {
    CLOUD_SERVICE_PROVIDER_INFO, CLOUD_SERVICE_PROVIDER_LIST, ROUTE_URL
} from 'constant/others/other';
import CloudServiceProvider from 'model/CloudServiceProvider';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { Link, useParams } from 'react-router-dom';
import { useAppState } from 'service/state';

const DEFAULT_CLOUD_CONTEXT_NAME = 'Cloud Service Provider';
const CLOUD_SERVICE_PROVIDER_INFO: Record<CloudServiceProvider, {
  entityTypeId: string,
  label: string,
}> = {
  'aws_cloud': { entityTypeId: 'instance', label: 'AWS Cloud' },
  'k8s': { entityTypeId: 'node', label: 'K8s' },
  'openstack': { entityTypeId: 'instance', label: 'OpenStack' },
  'vmware': { entityTypeId: 'vm', label: 'VMware' },
};

const SideBar = () => {

  const params: {
    cloudServiceProvider?: string,
    cloudContext?: string,
    entityName?: string,
    entityId?: string,
    action?: string
  } = useParams();
  const { cloudContextList } = useAppState();
  const { t } = useTranslation();
  const [cloudContextName, setCloudContextName] = useState(DEFAULT_CLOUD_CONTEXT_NAME);
  const [submenuStatus, setSubmenuStatus] = useState<CloudServiceProvider | ''>('');
const DEFAULT_CLOUD_CONTEXT_NAME = 'Cloud service providers';

  useEffect(() => {
    const temp = cloudContextList.filter((c) => c.name === params.cloudContext);
    setCloudContextName(
      temp.length > 0
        ? temp[0].labelName
        : DEFAULT_CLOUD_CONTEXT_NAME
    );
  }, [params, cloudContextList]);

  const getFilteredList = (cloudServiceProvider: CloudServiceProvider) => {
    return cloudContextList.filter((c) => {
      return c.name !== 'ALL' && c.cloudServiceProvider === cloudServiceProvider;
    })
  }

  // Note: The magic number 78.9843px comes from the style set in the body tag.
  return <div className="col-lg-3 col-xl-3 col-xxl-2 left-section" style={{ top: '78.9843px' }}>
    <header className="navbar-default">
      <nav className="navbar navbar-expand-lg navbar-light d-block">
        <div className="navbar-header">
const HeaderLogo = () => {
  return <div className="navbar-header">
    <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-collapse" aria-controls="navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
      <svg focusable="false" aria-hidden="true" viewBox="0 0 24 24" tabIndex={-1} fill="#fff"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></svg>
    </button>
@@ -59,115 +20,251 @@ const SideBar = () => {
      </a>
      <a className="name navbar-brand d-none" href={`${ROUTE_URL}/providers`} title="Home" rel="home">sample-site</a>
    </div>
        </div>
        <div className="navbar-collapse collapse">
          <div className="region region-navigation">
  </div>;
}

const HeaderLogo2 = () => {
  return <div className="region region-navigation">
    <a className="logo navbar-btn pull-left m-md-auto text-md-center" href={`${ROUTE_URL}/providers`} title="Home" rel="home">
      <img src="/themes/contrib/rigel/logo.svg" alt="Home" />
    </a>
    <a className="name navbar-brand d-none" href={`${ROUTE_URL}/providers`} title="Home" rel="home">sample-site</a>
          </div>
          <div className="region region-navigation-collapsible">
            <nav role="navigation" aria-labelledby="block-rigel-main-menu-menu" className="contextual-region open">
              <h2 className="sr-only" >Main navigation</h2>
              <ul className="nav navbar-nav" role="menu">
                <li><a href={`${ROUTE_URL}/providers`}>{t('Home')}</a></li>
                <li className="dropdown">
                  <a href={`${ROUTE_URL}/providers`} className="dropdown-toggle" data-toggle="dropdown">{cloudContextName}<span className="caret"></span></a>
                  <ul className="dropdown-menu" role="menu">
                    <li className="dropdown-submenu" onClick={() => setSubmenuStatus('')}>
  </div>;
}

const MenuLink = ({ url, label }: {
  url: string,
  label: string
}) => {
  if (url.startsWith(ROUTE_URL)) {
    const url2 = url.replace(`${ROUTE_URL}/`, '/');
    return <li>
      <Link to={url2}>
        {label}
      </Link>
    </li>;
  }

  return <li>
    <a href={url}>
      {label}
    </a>
  </li>;
}

const SubMenu = ({ isOpen, label, menuLink }: {
  isOpen: boolean,
  label: string,
  menuLink: { url: string, label: string }[]
}) => {
  return <li className={"dropdown-submenu" + (isOpen ? ' open' : '')}>
    {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
                      <a href="#" className="dropdown-submenu-toggle dropdown-toggle" data-toggle="dropdown">All<span className="caret"></span></a>
    <a href="#" className="dropdown-submenu-toggle dropdown-toggle" data-toggle="dropdown">
      {label}<span className="caret"></span>
    </a>
    <ul className="dropdown-menu" role="menu">
                        <li><a href={`${ROUTE_URL}/providers`}>Cloud Service Providers</a></li>
      {
                          CLOUD_SERVICE_PROVIDER_LIST.map((csp) => {
                            const list = getFilteredList(csp);
                            const pair = CLOUD_SERVICE_PROVIDER_INFO[csp];
                            return list.length > 0
                              ? <li key={csp}><a href={`${ROUTE_URL}/${csp}/${pair.entityTypeId}`}>{pair.label} resources</a></li>
                              : <></>;
        menuLink.map((pair) => {
          return <MenuLink key={pair.label} url={pair.url} label={pair.label} />;
        })
      }
    </ul>
                    </li>
                    {
                      CLOUD_SERVICE_PROVIDER_LIST.map((csp) => {
  </li>;
}

const AllCspSubMenu = ({ isOpen }: {
  isOpen: boolean
}) => {
  const { cloudContextList } = useAppState();

  const getFilteredList = (cloudServiceProvider: CloudServiceProvider) => {
    return cloudContextList.filter((c) => {
      return c.name !== 'ALL' && c.cloudServiceProvider === cloudServiceProvider;
    })
  }

  return <SubMenu label="All" isOpen={isOpen}
    menuLink={[
      { url: `${ROUTE_URL}/providers`, label: 'Cloud Service Providers' },
      ...CLOUD_SERVICE_PROVIDER_LIST
        .filter((csp) => {
          const list = getFilteredList(csp);
          return list.length > 0;
        })
        .map((csp) => {
          const pair = CLOUD_SERVICE_PROVIDER_INFO[csp];
                        return list.length > 0
                          ? <li className={'dropdown-submenu' + (submenuStatus === csp ? ' open' : '')}
                            onClick={() => setSubmenuStatus(csp)} key={csp}>
                            {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
                            <a href="#" className="dropdown-submenu-toggle dropdown-toggle" data-toggle="dropdown">{pair.label}<span className="caret"></span></a>
                            <ul className="dropdown-menu" role="menu">
                              {
                                list.map((c) => {
                                  return <li key={c.name}><a href={`${ROUTE_URL}/${c.cloudServiceProvider}/${c.name}/${pair.entityTypeId}`}>{c.labelName}</a></li>;
          return { url: `${ROUTE_URL}/${csp}/${pair.entityTypeId}`, label: pair.label };
        })
    ]} />;
}
                            </ul>
                          </li>
                          : <></>;

const CspSubMenu = ({ csp, isOpen }: {
  csp: CloudServiceProvider,
  isOpen: boolean
}) => {
  const { cloudContextList } = useAppState();

  const getFilteredList = (cloudServiceProvider: CloudServiceProvider) => {
    return cloudContextList.filter((c) => {
      return c.name !== 'ALL' && c.cloudServiceProvider === cloudServiceProvider;
    })
  }
                  </ul>
                </li>
                <li className="dropdown">
                  <a href="/clouds/design" className="dropdown-toggle" data-toggle="dropdown">Design<span className="caret"></span></a>
                  <ul className="dropdown-menu" role="menu">
                    <li className="dropdown-submenu">
                      <a href="/clouds" className="dropdown-submenu-toggle dropdown-toggle" data-toggle="dropdown">All<span className="caret"></span></a>
                      <ul className="dropdown-menu" role="menu">
                        {
                          getFilteredList('aws_cloud').length > 0
                            ? <li><a href={`${ROUTE_URL}/aws_cloud/server_template`}>AWS launch templates</a></li>
                            : <></>

  const list = getFilteredList(csp);
  const pair = CLOUD_SERVICE_PROVIDER_INFO[csp];
  if (list.length === 0) {
    return <></>;
  }
                        {
                          getFilteredList('k8s').length > 0
                            ? <>
                              <li><a href={`${ROUTE_URL}/k8s/server_template`}>K8s launch templates</a></li>
                              <li><a href={`${ROUTE_URL}/k8s/project`}>K8s cloud projects</a></li>
                            </>
                            : <></>

  return <SubMenu label={pair.label} isOpen={isOpen}
    menuLink={list.map((c) => {
      return { url: `${ROUTE_URL}/${c.cloudServiceProvider}/${c.name}/${pair.entityTypeId}`, label: c.labelName };
    })} />;
}
                      </ul>
                    </li>
                    <li className="dropdown-submenu">
                      <a href="/clouds" className="dropdown-submenu-toggle dropdown-toggle" data-toggle="dropdown">Launch templates<span className="caret"></span></a>
                      <ul className="dropdown-menu" role="menu">
                        {

const AllCltSubMenu = ({ isOpen }: {
  isOpen: boolean
}) => {
  const { cloudContextList } = useAppState();

  const getFilteredList = (cloudServiceProvider: CloudServiceProvider) => {
    return cloudContextList.filter((c) => {
      return c.name !== 'ALL' && c.cloudServiceProvider === cloudServiceProvider;
    })
  }

  const menuLinks = [];
  if (getFilteredList('aws_cloud').length > 0) {
    menuLinks.push({ url: `${ROUTE_URL}/aws_cloud/server_template`, label: 'AWS launch templates' });
  }
  if (getFilteredList('k8s').length > 0) {
    menuLinks.push({ url: `${ROUTE_URL}/k8s/server_template`, label: 'K8s launch templates' });
    menuLinks.push({ url: `${ROUTE_URL}/k8s/project`, label: 'K8s cloud projects' });
  }

  return <SubMenu isOpen={isOpen} label="All" menuLink={menuLinks} />;
}

const CltSubMenu = ({ isOpen }: {
  isOpen: boolean
}) => {
  const { cloudContextList } = useAppState();

  return <SubMenu label="Launch templates" isOpen={isOpen} menuLink={
    cloudContextList.filter((c) => {
      return c.name !== 'ALL';
    }).map((c) => {
                            return <li><a href={`${ROUTE_URL}/${c.cloudServiceProvider}/${c.name}/server_template`}>{c.labelName}</a></li>
      return { url: `${ROUTE_URL}/${c.cloudServiceProvider}/${c.name}/server_template`, label: c.labelName };
    })
  } />;
}
                      </ul>
                    </li>
                    <li className="dropdown-submenu">
                      <a href="/clouds" className="dropdown-submenu-toggle dropdown-toggle" data-toggle="dropdown">Projects<span className="caret"></span></a>
                      <ul className="dropdown-menu" role="menu">
                        {
                          getFilteredList('k8s').map((c) => {
                            return <li key={c.name}><a href={`${ROUTE_URL}/${c.cloudServiceProvider}/${c.name}/project`}>{c.labelName}</a></li>;

const ProjectSubMenu = ({ isOpen }: {
  isOpen: boolean
}) => {
  const { cloudContextList } = useAppState();

  const getFilteredList = (cloudServiceProvider: CloudServiceProvider) => {
    return cloudContextList.filter((c) => {
      return c.name !== 'ALL' && c.cloudServiceProvider === cloudServiceProvider;
    })
  }
                      </ul>
                    </li>
                    <li className="dropdown-submenu">
                      <a href="/clouds" className="dropdown-submenu-toggle dropdown-toggle" data-toggle="dropdown">Stores<span className="caret"></span></a>

  return <SubMenu label="Projects" isOpen={isOpen} menuLink={
    getFilteredList('k8s').map((c) => {
      return { url: `${ROUTE_URL}/${c.cloudServiceProvider}/${c.name}/project`, label: c.labelName };
    })} />;
}

const StoreSubMenu = ({ isOpen }: {
  isOpen: boolean
}) => {
  return <SubMenu label="Stores" isOpen={isOpen} menuLink={[
    { url: `${ROUTE_URL}/k8s_cost_store`, label: 'K8s cost store' },
    { url: `${ROUTE_URL}/k8s_namespace_resource_store`, label: 'K8s namespace resource store' },
    { url: `${ROUTE_URL}/k8s_node_resource_store`, label: 'K8s node resource store' },
    { url: `${ROUTE_URL}/k8s_pod_resource_store`, label: 'K8s pod resource store' },
  ]} />;
}

const DropdownMenu = ({ isOpen, label, children }: {
  isOpen: boolean,
  label: string,
  children: React.ReactNode
}) => {
  return <li className={"dropdown" + (isOpen ? ' open' : '')}>
    {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
    <a href="#" className="dropdown-toggle" data-toggle="dropdown">
      {label}<span className="caret"></span>
    </a>
    <ul className="dropdown-menu" role="menu">
                        <li><a href={`${ROUTE_URL}/k8s_cost_store`}>K8s cost store</a></li>
                        <li><a href={`${ROUTE_URL}/k8s_namespace_resource_store`}>K8s namespace resource store</a></li>
                        <li><a href={`${ROUTE_URL}/k8s_node_resource_store`}>K8s node resource store</a></li>
                        <li><a href={`${ROUTE_URL}/k8s_pod_resource_store`}>K8s pod resource store</a></li>
      {children}
    </ul>
                    </li>
                  </ul>
                </li>
                <li><a href="/admin/structure/cloud_config">Config</a></li>
  </li>;
}

const CspMenu = ({ isOpen, cloudContextName }: {
  isOpen: boolean,
  cloudContextName: string
}) => {
  return <DropdownMenu isOpen={isOpen} label={cloudContextName}>
    <AllCspSubMenu isOpen={true} />
    {
      CLOUD_SERVICE_PROVIDER_LIST.map((csp) => {
        return <CspSubMenu key={csp} csp={csp} isOpen={true} />;
      })
    }
  </DropdownMenu>;
}

const DesignMenu = ({ isOpen }: {
  isOpen: boolean
}) => {
  return <DropdownMenu isOpen={isOpen} label="Design">
    <AllCltSubMenu isOpen={true} />
    <CltSubMenu isOpen={true} />
    <ProjectSubMenu isOpen={true} />
    <StoreSubMenu isOpen={true} />
  </DropdownMenu>;
}

const SideBar = () => {

  const params: {
    cloudServiceProvider?: string,
    cloudContext?: string,
    entityName?: string,
    entityId?: string,
    action?: string
  } = useParams();
  const { cloudContextList } = useAppState();
  const { t } = useTranslation();
  const [cloudContextName, setCloudContextName] = useState(DEFAULT_CLOUD_CONTEXT_NAME);

  useEffect(() => {
    const temp = cloudContextList.filter((c) => c.name === params.cloudContext);
    setCloudContextName(
      temp.length > 0
        ? temp[0].labelName
        : DEFAULT_CLOUD_CONTEXT_NAME
    );
  }, [params, cloudContextList]);

  // Note: The magic number 78.9843px comes from the style set in the body tag.
  return <div className="col-lg-3 col-xl-3 col-xxl-2 left-section" style={{ top: '78.9843px' }}>
    <header className="navbar-default">
      <nav className="navbar navbar-expand-lg navbar-light d-block">
        <HeaderLogo />
        <div className="navbar-collapse collapse">
          <HeaderLogo2 />
          <div className="region region-navigation-collapsible">
            <nav role="navigation" aria-labelledby="block-rigel-main-menu-menu" className="contextual-region open">
              <h2 className="sr-only" >Main navigation</h2>
              <ul className="nav navbar-nav" role="menu">
                <MenuLink url={`${ROUTE_URL}/providers`} label={t('Home')} />
                <CspMenu isOpen={true} cloudContextName={cloudContextName} />
                <DesignMenu isOpen={true} />
                <MenuLink url="/admin/structure/cloud_config" label="Config" />
              </ul>
            </nav>
          </div>