fix(stacks): show stack containers [EE-2359] (#6375)

Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
pull/6398/head
Chaim Lev-Ari 2022-01-13 07:28:49 +02:00 committed by GitHub
parent 085762a1f4
commit 584a46d9d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 52 additions and 65 deletions

View File

@ -50,9 +50,10 @@ import { useColumns } from './columns';
export interface ContainerTableProps { export interface ContainerTableProps {
isAddActionVisible: boolean; isAddActionVisible: boolean;
dataset: DockerContainer[]; dataset: DockerContainer[];
onRefresh(): Promise<void>; onRefresh?(): Promise<void>;
isHostColumnVisible: boolean; isHostColumnVisible: boolean;
autoFocusSearch: boolean; autoFocusSearch: boolean;
tableKey?: string;
} }
export function ContainersDatatable({ export function ContainersDatatable({
@ -150,7 +151,7 @@ export function ContainersDatatable({
<TableSettingsMenu <TableSettingsMenu
quickActions={<QuickActionsSettings actions={actions} />} quickActions={<QuickActionsSettings actions={actions} />}
> >
<ContainersDatatableSettings /> <ContainersDatatableSettings isRefreshVisible={!!onRefresh} />
</TableSettingsMenu> </TableSettingsMenu>
</TableTitleActions> </TableTitleActions>
</TableTitle> </TableTitle>

View File

@ -13,7 +13,11 @@ interface Props extends ContainerTableProps {
endpoint: Environment; endpoint: Environment;
} }
export function ContainersDatatableContainer({ endpoint, ...props }: Props) { export function ContainersDatatableContainer({
endpoint,
tableKey = 'containers',
...props
}: Props) {
const defaultSettings = { const defaultSettings = {
autoRefreshRate: 0, autoRefreshRate: 0,
truncateContainerName: 32, truncateContainerName: 32,
@ -25,8 +29,8 @@ export function ContainersDatatableContainer({ endpoint, ...props }: Props) {
return ( return (
<EnvironmentProvider environment={endpoint}> <EnvironmentProvider environment={endpoint}>
<TableSettingsProvider defaults={defaultSettings} storageKey="containers"> <TableSettingsProvider defaults={defaultSettings} storageKey={tableKey}>
<SearchBarProvider> <SearchBarProvider storageKey={tableKey}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* eslint-disable-next-line react/jsx-props-no-spreading */}
<ContainersDatatable {...props} /> <ContainersDatatable {...props} />
</SearchBarProvider> </SearchBarProvider>
@ -40,13 +44,10 @@ export const ContainersDatatableAngular = react2angular(
[ [
'endpoint', 'endpoint',
'isAddActionVisible', 'isAddActionVisible',
'containerService',
'httpRequestHelper',
'notifications',
'modalService',
'dataset', 'dataset',
'onRefresh', 'onRefresh',
'isHostColumnVisible', 'isHostColumnVisible',
'autoFocusSearch', 'autoFocusSearch',
'tableKey',
] ]
); );

View File

@ -3,10 +3,13 @@ import { useTableSettings } from '@/portainer/components/datatables/components/u
import { Checkbox } from '@/portainer/components/form-components/Checkbox'; import { Checkbox } from '@/portainer/components/form-components/Checkbox';
import type { ContainersTableSettings } from '@/docker/containers/types'; import type { ContainersTableSettings } from '@/docker/containers/types';
export function ContainersDatatableSettings() { interface Props {
const { settings, setTableSettings } = useTableSettings< isRefreshVisible: boolean;
ContainersTableSettings }
>();
export function ContainersDatatableSettings({ isRefreshVisible }: Props) {
const { settings, setTableSettings } =
useTableSettings<ContainersTableSettings>();
return ( return (
<> <>
@ -22,10 +25,12 @@ export function ContainersDatatableSettings() {
} }
/> />
<TableSettingsMenuAutoRefresh {isRefreshVisible && (
value={settings.autoRefreshRate} <TableSettingsMenuAutoRefresh
onChange={handleRefreshRateChange} value={settings.autoRefreshRate}
/> onChange={handleRefreshRateChange}
/>
)}
</> </>
); );

View File

@ -30,20 +30,22 @@ const SearchBarContext = createContext<
interface SearchBarProviderProps { interface SearchBarProviderProps {
defaultValue?: string; defaultValue?: string;
storageKey: string;
} }
export function SearchBarProvider({ export function SearchBarProvider({
children, children,
storageKey,
defaultValue = '', defaultValue = '',
}: PropsWithChildren<SearchBarProviderProps>) { }: PropsWithChildren<SearchBarProviderProps>) {
const [value, setValue] = useLocalStorage( const state = useLocalStorage(
'datatable_text_filter_containers', `datatable_text_filter_${storageKey}`,
defaultValue, defaultValue,
sessionStorage sessionStorage
); );
return ( return (
<SearchBarContext.Provider value={[value, setValue]}> <SearchBarContext.Provider value={state}>
{children} {children}
</SearchBarContext.Provider> </SearchBarContext.Provider>
); );

View File

@ -2,7 +2,7 @@ import { useEffect, useCallback, useState } from 'react';
export function useRepeater( export function useRepeater(
refreshRate: number, refreshRate: number,
onRefresh: () => Promise<void> onRefresh?: () => Promise<void>
) { ) {
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null); const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
@ -17,7 +17,7 @@ export function useRepeater(
const startRepeater = useCallback( const startRepeater = useCallback(
(refreshRate) => { (refreshRate) => {
if (intervalId) { if (intervalId || !onRefresh) {
return; return;
} }
@ -27,16 +27,16 @@ export function useRepeater(
}, refreshRate * 1000) }, refreshRate * 1000)
); );
}, },
[intervalId] [intervalId, onRefresh]
); );
useEffect(() => { useEffect(() => {
if (!refreshRate) { if (!refreshRate || !onRefresh) {
stopRepeater(); stopRepeater();
} else { } else {
startRepeater(refreshRate); startRepeater(refreshRate);
} }
return stopRepeater; return stopRepeater;
}, [refreshRate, startRepeater, stopRepeater, intervalId]); }, [refreshRate, startRepeater, stopRepeater, intervalId, onRefresh]);
} }

View File

@ -5,7 +5,7 @@ angular.module('portainer.app').factory('Stack', StackFactory);
/* @ngInject */ /* @ngInject */
function StackFactory($resource, API_ENDPOINT_STACKS) { function StackFactory($resource, API_ENDPOINT_STACKS) {
return $resource( return $resource(
API_ENDPOINT_STACKS + '/:id/:action', API_ENDPOINT_STACKS + '/:id/:action/:subaction',
{}, {},
{ {
get: { method: 'GET', params: { id: '@id' } }, get: { method: 'GET', params: { id: '@id' } },

View File

@ -15,16 +15,14 @@
<!-- tab-info --> <!-- tab-info -->
<uib-tab index="0"> <uib-tab index="0">
<uib-tab-heading> <i class="fa fa-th-list" aria-hidden="true"></i> Stack </uib-tab-heading> <uib-tab-heading> <i class="fa fa-th-list" aria-hidden="true"></i> Stack </uib-tab-heading>
<div style="margin-top: 10px;"> <div style="margin-top: 10px">
<!-- stack-information --> <!-- stack-information -->
<div ng-if="external || orphaned"> <div ng-if="external || orphaned">
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title"> Information </div>
Information
</div>
<div class="form-group"> <div class="form-group">
<span class="small"> <span class="small">
<p class="text-muted"> <p class="text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px"></i>
<span ng-if="external">This stack was created outside of Portainer. Control over this stack is limited.</span> <span ng-if="external">This stack was created outside of Portainer. Control over this stack is limited.</span>
<span ng-if="orphaned">This stack is orphaned. You can reassociate it with the current environment using the "Associate to this environment" feature.</span> <span ng-if="orphaned">This stack is orphaned. You can reassociate it with the current environment using the "Associate to this environment" feature.</span>
</p> </p>
@ -34,9 +32,7 @@
<!-- !stack-information --> <!-- !stack-information -->
<!-- stack-details --> <!-- stack-details -->
<div> <div>
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title"> Stack details </div>
Stack details
</div>
<div class="form-group"> <div class="form-group">
{{ stackName }} {{ stackName }}
@ -81,12 +77,8 @@
<!-- associate --> <!-- associate -->
<div ng-if="orphaned"> <div ng-if="orphaned">
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title"> Associate to this environment </div>
Associate to this environment <p class="small text-muted"> This feature allows you to reassociate this stack to the current environment. </p>
</div>
<p class="small text-muted">
This feature allows you to reassociate this stack to the current environment.
</p>
<form class="form-horizontal"> <form class="form-horizontal">
<por-access-control-form form-data="formValues.AccessControlData" hide-title="true"></por-access-control-form> <por-access-control-form form-data="formValues.AccessControlData" hide-title="true"></por-access-control-form>
<div class="form-group"> <div class="form-group">
@ -97,13 +89,13 @@
ng-disabled="state.actionInProgress" ng-disabled="state.actionInProgress"
ng-click="associateStack()" ng-click="associateStack()"
button-spinner="state.actionInProgress" button-spinner="state.actionInProgress"
style="margin-left: -5px;" style="margin-left: -5px"
> >
<i class="fa fa-sync" aria-hidden="true" style="margin-right: 3px;"></i> <i class="fa fa-sync" aria-hidden="true" style="margin-right: 3px"></i>
<span ng-hide="state.actionInProgress">Associate</span> <span ng-hide="state.actionInProgress">Associate</span>
<span ng-show="state.actionInProgress">Association in progress...</span> <span ng-show="state.actionInProgress">Association in progress...</span>
</button> </button>
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span> <span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px">{{ state.formValidationError }}</span>
</div> </div>
</div> </div>
</form> </form>
@ -128,12 +120,12 @@
<!-- tab-file --> <!-- tab-file -->
<uib-tab index="1" select="showEditor()" ng-if="!external && (!stack.GitConfig || stack.FromAppTemplate)"> <uib-tab index="1" select="showEditor()" ng-if="!external && (!stack.GitConfig || stack.FromAppTemplate)">
<uib-tab-heading> <i class="fa fa-pencil-alt space-right" aria-hidden="true"></i> Editor </uib-tab-heading> <uib-tab-heading> <i class="fa fa-pencil-alt space-right" aria-hidden="true"></i> Editor </uib-tab-heading>
<form class="form-horizontal" ng-if="state.showEditorTab" style="margin-top: 10px;" name="stackUpdateForm"> <form class="form-horizontal" ng-if="state.showEditorTab" style="margin-top: 10px" name="stackUpdateForm">
<div class="form-group"> <div class="form-group">
<span class="col-sm-12 text-muted small" style="margin-bottom: 7px;" ng-if="stackType == 2 && composeSyntaxMaxVersion == 2"> <span class="col-sm-12 text-muted small" style="margin-bottom: 7px" ng-if="stackType == 2 && composeSyntaxMaxVersion == 2">
This stack will be deployed using the equivalent of <code>docker-compose</code>. Only Compose file format version <b>2</b> is supported at the moment. This stack will be deployed using the equivalent of <code>docker-compose</code>. Only Compose file format version <b>2</b> is supported at the moment.
</span> </span>
<span class="col-sm-12 text-muted small" style="margin-bottom: 7px;" ng-if="stackType == 2 && composeSyntaxMaxVersion > 2"> <span class="col-sm-12 text-muted small" style="margin-bottom: 7px" ng-if="stackType == 2 && composeSyntaxMaxVersion > 2">
This stack will be deployed using <code>docker-compose</code>. This stack will be deployed using <code>docker-compose</code>.
</span> </span>
<span class="col-sm-12 text-muted small"> <span class="col-sm-12 text-muted small">
@ -163,24 +155,20 @@
<!-- !environment-variables --> <!-- !environment-variables -->
<!-- options --> <!-- options -->
<div ng-if="stack.Type === 1 && applicationState.endpoint.apiVersion >= 1.27" authorization="PortainerStackUpdate"> <div ng-if="stack.Type === 1 && applicationState.endpoint.apiVersion >= 1.27" authorization="PortainerStackUpdate">
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title"> Options </div>
Options
</div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <div class="col-sm-12">
<label for="prune" class="control-label text-left"> <label for="prune" class="control-label text-left">
Prune services Prune services
<portainer-tooltip position="bottom" message="Prune services that are no longer referenced."></portainer-tooltip> <portainer-tooltip position="bottom" message="Prune services that are no longer referenced."></portainer-tooltip>
</label> </label>
<label class="switch" style="margin-left: 20px;"> <input name="prune" type="checkbox" ng-model="formValues.Prune" /><i></i> </label> <label class="switch" style="margin-left: 20px"> <input name="prune" type="checkbox" ng-model="formValues.Prune" /><i></i> </label>
</div> </div>
</div> </div>
</div> </div>
<!-- !options --> <!-- !options -->
<div authorization="PortainerStackUpdate"> <div authorization="PortainerStackUpdate">
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title"> Actions </div>
Actions
</div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <div class="col-sm-12">
<button <button
@ -207,17 +195,7 @@
<div class="row" ng-if="containers && (!orphaned || orphanedRunning)"> <div class="row" ng-if="containers && (!orphaned || orphanedRunning)">
<div class="col-sm-12"> <div class="col-sm-12">
<containers-datatable <containers-datatable dataset="containers" endpoint="endpoint" table-key="stack-containers"></containers-datatable>
title-text="Containers"
title-icon="fa-cubes"
dataset="containers"
table-key="stack-containers"
order-by="Status"
show-host-column="false"
show-add-action="false"
not-auto-focus="true"
endpoint-public-url="endpoint.PublicURL"
></containers-datatable>
</div> </div>
</div> </div>

View File

@ -38,7 +38,7 @@
"format": "prettier --loglevel warn --write \"**/*.{js,css,html,jsx,tsx,ts}\"", "format": "prettier --loglevel warn --write \"**/*.{js,css,html,jsx,tsx,ts}\"",
"lint": "yarn lint:client; yarn lint:server", "lint": "yarn lint:client; yarn lint:server",
"lint:server": "cd api && golangci-lint run -E exportloopref", "lint:server": "cd api && golangci-lint run -E exportloopref",
"lint:client": "eslint --cache --fix ./**/*.{js,jsx,ts,tsx}", "lint:client": "eslint --cache --fix './**/*.{js,jsx,ts,tsx}'",
"lint:pr": "make lint-pr", "lint:pr": "make lint-pr",
"test": "yarn test:client; yarn test:server", "test": "yarn test:client; yarn test:server",
"test:server": "cd api && go test ./...", "test:server": "cd api && go test ./...",