mirror of https://github.com/portainer/portainer
fix(stacks): show stack containers [EE-2359] (#6375)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>pull/6398/head
parent
085762a1f4
commit
584a46d9d4
|
@ -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>
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' } },
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 ./...",
|
||||||
|
|
Loading…
Reference in New Issue