* refactor(http): parse axios errors (#6325) * refactor(home): use endpoint-list as react component [EE-1814] (#6060) * refactor(home): use endpoint-list as react component fix(home): add missing features and refactors - kubebutton - group name - poll when endpoint is off - state management refactor(endpoints): use stat component fix(endpoints): add space between items refactor(endpoints): move stats to components refactor(endpoints): fetch time refactor(home): move logic refactor(home): move fe render logic refactor(settings): use vanilla js for publicSettings refactor(kube): remove angular from kube config service feat(home): add kubeconfig button feat(home): send analytics when opening kubeconfig modal fix(home): memoize footer refactor(home): use react-query for loading fix(home): show correct control for kubeconfig modal refactor(home): use debounce refactor(home): use new components refactor(home): replace endpoints with environments refactor(home): move endpoint-list component to home fix(home): show group name refactor(home): use switch for environment icon fix(kubeconfig): fix default case refactor(axios): use parse axios error refactor(home): use link components for navigate fix(home): align azure icon refactor(home): refactor stats refactor(home): export envstatusbadge refactor(home): remove unused bindings * chore(home): write tests for edge indicator * chore(home): basic stories for environment item * style(settings): reformat * fix(environments): add publicurl * refactor(home): use table components * refactor(datatables): merge useSearchBarState * refactor(home): fetch group in env item * chore(tests): basic tests * chore(home): test when no envs * refactor(tags): use axios for tagService * refactor(env-groups): use axios for getGroups * feat(app): ui-state context provider * refactor(home): create MotdPanel * refactor(app): create InformationPanel * feat(endpoints): fetch number of total endpoints * refactor(app): merge hooks * refactor(home): migrate view to react [EE-1810] fixes [EE-1810] refactor(home): wip use react view feat(home): show message if no endpoints refactor(home): show endpoint list refactor(home): don't use home to manage link refactor(home): move state refactor(home): check if edge using util refactor(home): move inf panels chore(home): tests refactor(home): load groups and tags in env-item refactor(settings): revert publicSettings change refactor(home): move confirm snapshot method * fix(home): show tags * fix(environments): handle missing snapshots * fix(kube/volumes): fetch pesistent volume claims * refactor(kube): remove use of endpointProvider * refactor(endpoints): set current endpoint * chore(home): add data-cy for tests * chore(tests): mock axios-progress-bar * refactor(home): move use env list to env module * feat(app): sync home view changes with ee * fix(home): sort page header * fix(app): fix tests * chore(github): use yarn cache * refactor(environments): load list of groups * chore(babel): remove auto 18n keys extraction * chore(environments): fix tests * refactor(k8s/application): use current endpoint * fix(app/header): add margin to header * refactor(app): remove unused types * refactor(app): use rq onError handler * refactor(home): wrap element with button
160 lines
4.7 KiB
TypeScript
160 lines
4.7 KiB
TypeScript
import { ReactNode, useEffect, useState } from 'react';
|
|
import clsx from 'clsx';
|
|
|
|
import { PaginationControls } from '@/portainer/components/pagination-controls';
|
|
import { usePaginationLimitState } from '@/portainer/hooks/usePaginationLimitState';
|
|
import { Environment } from '@/portainer/environments/types';
|
|
import { Button } from '@/portainer/components/Button';
|
|
import { useIsAdmin } from '@/portainer/hooks/useUser';
|
|
import {
|
|
SearchBar,
|
|
useSearchBarState,
|
|
} from '@/portainer/components/datatables/components/SearchBar';
|
|
import {
|
|
TableActions,
|
|
TableContainer,
|
|
TableTitle,
|
|
} from '@/portainer/components/datatables/components';
|
|
import { TableFooter } from '@/portainer/components/datatables/components/TableFooter';
|
|
import { useDebounce } from '@/portainer/hooks/useDebounce';
|
|
import { useEnvironmentList } from '@/portainer/environments/queries';
|
|
import { useGroups } from '@/portainer/environment-groups/queries';
|
|
|
|
import { EnvironmentItem } from './EnvironmentItem';
|
|
import { KubeconfigButton } from './KubeconfigButton';
|
|
import styles from './EnvironmentList.module.css';
|
|
import { NoEnvironmentsInfoPanel } from './NoEnvironmentsInfoPanel';
|
|
|
|
interface Props {
|
|
onClickItem(environment: Environment): void;
|
|
onRefresh(): void;
|
|
}
|
|
|
|
export function EnvironmentList({ onClickItem, onRefresh }: Props) {
|
|
const homepageLoadTime = usePageLoadingTime();
|
|
|
|
const isAdmin = useIsAdmin();
|
|
const storageKey = 'home_endpoints';
|
|
|
|
const [searchBarValue, setSearchBarValue] = useSearchBarState(storageKey);
|
|
const [pageLimit, setPageLimit] = usePaginationLimitState(storageKey);
|
|
const [page, setPage] = useState(1);
|
|
|
|
const debouncedTextFilter = useDebounce(searchBarValue);
|
|
|
|
useEffect(() => {
|
|
setPage(1);
|
|
}, [searchBarValue]);
|
|
|
|
const groupsQuery = useGroups();
|
|
|
|
const { isLoading, environments, totalCount, totalAvailable } =
|
|
useEnvironmentList(page, pageLimit, debouncedTextFilter, true);
|
|
|
|
return (
|
|
<>
|
|
{totalAvailable === 0 && <NoEnvironmentsInfoPanel isAdmin={isAdmin} />}
|
|
<div className="row">
|
|
<div className="col-sm-12">
|
|
<TableContainer>
|
|
<TableTitle icon="fa-plug" label="Environments" />
|
|
|
|
<TableActions className={styles.actionBar}>
|
|
<div className={styles.description}>
|
|
<i className="fa fa-exclamation-circle blue-icon space-right" />
|
|
Click on an environment to manage
|
|
</div>
|
|
|
|
{isAdmin && (
|
|
<Button
|
|
onClick={onRefresh}
|
|
data-cy="home-refreshEndpointsButton"
|
|
className={clsx(styles.refreshEnvironmentsButton)}
|
|
>
|
|
<i className="fa fa-sync space-right" aria-hidden="true" />
|
|
Refresh
|
|
</Button>
|
|
)}
|
|
|
|
<KubeconfigButton environments={environments} />
|
|
</TableActions>
|
|
|
|
<SearchBar
|
|
value={searchBarValue}
|
|
onChange={setSearchBarValue}
|
|
placeholder="Search by name, group, tag, status, URL..."
|
|
data-cy="home-endpointsSearchInput"
|
|
/>
|
|
|
|
<div className="blocklist" data-cy="home-endpointList">
|
|
{renderItems(
|
|
isLoading,
|
|
totalCount,
|
|
environments.map((env) => (
|
|
<EnvironmentItem
|
|
key={env.Id}
|
|
environment={env}
|
|
groupName={
|
|
groupsQuery.data?.find((g) => g.Id === env.GroupId)?.Name
|
|
}
|
|
onClick={onClickItem}
|
|
homepageLoadTime={homepageLoadTime}
|
|
/>
|
|
))
|
|
)}
|
|
</div>
|
|
|
|
<TableFooter>
|
|
<PaginationControls
|
|
showAll={totalCount <= 100}
|
|
pageLimit={pageLimit}
|
|
page={page}
|
|
onPageChange={setPage}
|
|
totalCount={totalCount}
|
|
onPageLimitChange={setPageLimit}
|
|
/>
|
|
</TableFooter>
|
|
</TableContainer>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function renderItems(
|
|
isLoading: boolean,
|
|
totalCount: number,
|
|
|
|
items: ReactNode
|
|
) {
|
|
if (isLoading) {
|
|
return (
|
|
<div className="text-center text-muted" data-cy="home-loadingEndpoints">
|
|
Loading...
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!totalCount) {
|
|
return (
|
|
<div className="text-center text-muted" data-cy="home-noEndpoints">
|
|
No environments available.
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
function usePageLoadingTime() {
|
|
const [homepageLoadTime, setHomepageLoadTime] = useState<
|
|
number | undefined
|
|
>();
|
|
|
|
useEffect(() => {
|
|
setHomepageLoadTime(Math.floor(Date.now() / 1000));
|
|
}, []);
|
|
|
|
return homepageLoadTime;
|
|
}
|