Compare commits
5 Commits
develop-ja
...
fix/EE-514
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fc6fdff17 | ||
|
|
4beedb5f69 | ||
|
|
c609f6912f | ||
|
|
346fe9e3f1 | ||
|
|
69f14e569b |
@@ -82,9 +82,11 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
|
||||
}
|
||||
|
||||
err = manager.deployer.Remove(ctx, stack.Name, nil, libstack.Options{
|
||||
WorkingDir: stack.ProjectPath,
|
||||
EnvFilePath: envFilePath,
|
||||
Host: url,
|
||||
})
|
||||
|
||||
return errors.Wrap(err, "failed to remove a stack")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import angular from 'angular';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withControlledInput } from '@/react-tools/withControlledInput';
|
||||
import { StackContainersDatatable } from '@/react/docker/stacks/ItemView/StackContainersDatatable';
|
||||
import { ContainerQuickActions } from '@/react/docker/containers/components/ContainerQuickActions';
|
||||
import { TemplateListDropdownAngular } from '@/react/docker/app-templates/TemplateListDropdown';
|
||||
@@ -11,6 +12,8 @@ import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { DockerfileDetails } from '@/react/docker/images/ItemView/DockerfileDetails';
|
||||
import { HealthStatus } from '@/react/docker/containers/ItemView/HealthStatus';
|
||||
import { GpusList } from '@/react/docker/host/SetupView/GpusList';
|
||||
import { GpusInsights } from '@/react/docker/host/SetupView/GpusInsights';
|
||||
|
||||
export const componentsModule = angular
|
||||
.module('portainer.docker.react.components', [])
|
||||
@@ -45,4 +48,9 @@ export const componentsModule = angular
|
||||
'usedAllGpus',
|
||||
'enableGpuManagement',
|
||||
])
|
||||
).name;
|
||||
)
|
||||
.component(
|
||||
'gpusList',
|
||||
r2a(withControlledInput(GpusList), ['value', 'onChange'])
|
||||
)
|
||||
.component('gpusInsights', r2a(GpusInsights, [])).name;
|
||||
|
||||
@@ -151,21 +151,7 @@
|
||||
<div class="col-sm-12 form-section-title"> Other </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 pb-3">
|
||||
<insights-box
|
||||
header="'GPU settings update'"
|
||||
set-html-content="true"
|
||||
insight-close-id="'gpu-settings-update-closed'"
|
||||
content="'
|
||||
<p>
|
||||
From 2.18 on, the set-up of available GPUs for a Docker Standalone environment has been shifted from Add environment and Environment details to Host -> Setup, so as to align with other settings.
|
||||
</p>
|
||||
<p>
|
||||
A toggle has been introduced for enabling/disabling management of GPU settings in the Portainer UI - to alleviate the performance impact of showing those settings.
|
||||
</p>
|
||||
<p>
|
||||
The UI has been updated to clarify that GPU settings support is only for Docker Standalone (and not Docker Swarm, which was never supported in the UI).
|
||||
</p>'"
|
||||
></insights-box>
|
||||
<gpus-insights></gpus-insights>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
|
||||
@@ -141,7 +141,9 @@ export default class HelmTemplatesController {
|
||||
try {
|
||||
const resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
|
||||
const nonSystemNamespaces = resourcePools.filter((resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
const nonSystemNamespaces = resourcePools.filter(
|
||||
(resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && resourcePool.Namespace.Status === 'Active'
|
||||
);
|
||||
this.state.resourcePools = _.sortBy(nonSystemNamespaces, ({ Namespace }) => (Namespace.Name === 'default' ? 0 : 1));
|
||||
this.state.resourcePool = this.state.resourcePools[0];
|
||||
} catch (err) {
|
||||
|
||||
@@ -81,18 +81,19 @@ class KubernetesNamespaceService {
|
||||
}
|
||||
}
|
||||
|
||||
async get(name) {
|
||||
async get(name, refreshCache = false) {
|
||||
if (name) {
|
||||
return this.$async(this.getAsync, name);
|
||||
}
|
||||
const cachedAllowedNamespaces = this.LocalStorage.getAllowedNamespaces();
|
||||
if (cachedAllowedNamespaces) {
|
||||
updateNamespaces(cachedAllowedNamespaces);
|
||||
return cachedAllowedNamespaces;
|
||||
} else {
|
||||
if (!cachedAllowedNamespaces || refreshCache) {
|
||||
const allowedNamespaces = await this.getAllAsync();
|
||||
this.LocalStorage.storeAllowedNamespaces(allowedNamespaces);
|
||||
updateNamespaces(allowedNamespaces);
|
||||
return allowedNamespaces;
|
||||
} else {
|
||||
updateNamespaces(cachedAllowedNamespaces);
|
||||
return cachedAllowedNamespaces;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ export function KubernetesResourcePoolService(
|
||||
}
|
||||
|
||||
// getting the quota for all namespaces is costly by default, so disable getting it by default
|
||||
async function getAll({ getQuota = false }) {
|
||||
const namespaces = await KubernetesNamespaceService.get();
|
||||
async function getAll({ getQuota = false, refreshCache = false }) {
|
||||
const namespaces = await KubernetesNamespaceService.get('', refreshCache);
|
||||
const pools = await Promise.all(
|
||||
_.map(namespaces, async (namespace) => {
|
||||
const name = namespace.Name;
|
||||
|
||||
@@ -189,7 +189,7 @@ class KubernetesApplicationsController {
|
||||
};
|
||||
|
||||
this.state.namespaces = await this.KubernetesNamespaceService.get();
|
||||
this.state.namespaces = this.state.namespaces.filter((n) => n.Status !== 'Terminating');
|
||||
this.state.namespaces = this.state.namespaces.filter((n) => n.Status === 'Active');
|
||||
this.state.namespaces = _.sortBy(this.state.namespaces, 'Name');
|
||||
this.state.namespace = this.state.namespaces.length ? (this.state.namespaces.find((n) => n.Name === 'default') ? 'default' : this.state.namespaces[0].Name) : '';
|
||||
|
||||
|
||||
@@ -1208,7 +1208,10 @@ class KubernetesCreateApplicationController {
|
||||
]);
|
||||
this.nodesLimits = nodesLimits;
|
||||
|
||||
const nonSystemNamespaces = _.filter(resourcePools, (resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
const nonSystemNamespaces = _.filter(
|
||||
resourcePools,
|
||||
(resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && resourcePool.Namespace.Status === 'Active'
|
||||
);
|
||||
|
||||
this.allNamespaces = resourcePools.map(({ Namespace }) => Namespace.Name);
|
||||
this.resourcePools = _.sortBy(nonSystemNamespaces, ({ Namespace }) => (Namespace.Name === 'default' ? 0 : 1));
|
||||
|
||||
@@ -196,7 +196,10 @@ class KubernetesCreateConfigurationController {
|
||||
|
||||
try {
|
||||
const resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||
this.resourcePools = _.filter(
|
||||
resourcePools,
|
||||
(resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && resourcePool.Namespace.Status === 'Active'
|
||||
);
|
||||
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
await this.getConfigurations();
|
||||
|
||||
@@ -165,7 +165,10 @@ class KubernetesConfigureController {
|
||||
const allResourcePools = await this.KubernetesResourcePoolService.get();
|
||||
const resourcePools = _.filter(
|
||||
allResourcePools,
|
||||
(resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && !KubernetesNamespaceHelper.isDefaultNamespace(resourcePool.Namespace.Name)
|
||||
(resourcePool) =>
|
||||
!KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) &&
|
||||
!KubernetesNamespaceHelper.isDefaultNamespace(resourcePool.Namespace.Name) &&
|
||||
resourcePool.Namespace.Status === 'Active'
|
||||
);
|
||||
|
||||
ingressesToDel.forEach((ingress) => {
|
||||
|
||||
@@ -284,7 +284,8 @@ class KubernetesDeployController {
|
||||
async getNamespacesAsync() {
|
||||
try {
|
||||
const pools = await this.KubernetesResourcePoolService.get();
|
||||
const namespaces = _.map(pools, 'Namespace').sort((a, b) => {
|
||||
let namespaces = pools.filter((pool) => pool.Namespace.Status === 'Active');
|
||||
namespaces = _.map(namespaces, 'Namespace').sort((a, b) => {
|
||||
if (a.Name === 'default') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ class KubernetesResourcePoolsController {
|
||||
} finally {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
await this.KubernetesNamespaceService.refreshCacheAsync();
|
||||
this.$state.reload(this.$state.current);
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.KubernetesNamespaceService.refreshCacheAsync();
|
||||
}
|
||||
|
||||
removeAction(selectedItems) {
|
||||
@@ -77,7 +77,7 @@ class KubernetesResourcePoolsController {
|
||||
|
||||
async getResourcePoolsAsync() {
|
||||
try {
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true, refreshCache: true });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retreive namespaces');
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import angular from 'angular';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withControlledInput } from '@/react-tools/withControlledInput';
|
||||
import { EdgeKeyDisplay } from '@/react/portainer/environments/ItemView/EdgeKeyDisplay';
|
||||
import { KVMControl } from '@/react/portainer/environments/KvmView/KVMControl';
|
||||
import { GpusList } from '@/react/docker/host/SetupView/GpusList';
|
||||
|
||||
export const environmentsModule = angular
|
||||
.module('portainer.app.react.components.environments', [])
|
||||
.component('edgeKeyDisplay', r2a(EdgeKeyDisplay, ['edgeKey']))
|
||||
.component('kvmControl', r2a(KVMControl, ['deviceId', 'server', 'token']))
|
||||
.component(
|
||||
'gpusList',
|
||||
r2a(withControlledInput(GpusList), ['value', 'onChange'])
|
||||
'kvmControl',
|
||||
r2a(KVMControl, ['deviceId', 'server', 'token'])
|
||||
).name;
|
||||
|
||||
@@ -26,7 +26,6 @@ import { Slider } from '@@/form-components/Slider';
|
||||
import { TagButton } from '@@/TagButton';
|
||||
import { BETeaserButton } from '@@/BETeaserButton';
|
||||
import { CodeEditor } from '@@/CodeEditor';
|
||||
import { InsightsBox } from '@@/InsightsBox';
|
||||
|
||||
import { fileUploadField } from './file-upload-field';
|
||||
import { switchField } from './switch-field';
|
||||
@@ -80,10 +79,6 @@ export const componentsModule = angular
|
||||
.component('badge', r2a(Badge, ['type', 'className']))
|
||||
.component('fileUploadField', fileUploadField)
|
||||
.component('porSwitchField', switchField)
|
||||
.component(
|
||||
'insightsBox',
|
||||
r2a(InsightsBox, ['header', 'content', 'setHtmlContent', 'insightCloseId'])
|
||||
)
|
||||
.component(
|
||||
'passwordCheckHint',
|
||||
r2a(withReactQuery(PasswordCheckHint), [
|
||||
|
||||
@@ -211,22 +211,7 @@
|
||||
<!-- !open-amt info -->
|
||||
<!-- gpus info -->
|
||||
<div class="mb-4">
|
||||
<insights-box
|
||||
ng-if="isDockerStandaloneEnv"
|
||||
header="'GPU settings update'"
|
||||
set-html-content="true"
|
||||
insight-close-id="'gpu-settings-update-closed'"
|
||||
content="'
|
||||
<p>
|
||||
From 2.18 on, the set-up of available GPUs for a Docker Standalone environment has been shifted from Add environment and Environment details to Host -> Setup, so as to align with other settings.
|
||||
</p>
|
||||
<p>
|
||||
A toggle has been introduced for enabling/disabling management of GPU settings in the Portainer UI - to alleviate the performance impact of showing those settings.
|
||||
</p>
|
||||
<p>
|
||||
The UI has been updated to clarify that GPU settings support is only for Docker Standalone (and not Docker Swarm, which was never supported in the UI).
|
||||
</p>'"
|
||||
></insights-box>
|
||||
<gpus-insights></gpus-insights>
|
||||
</div>
|
||||
<!-- gpus info -->
|
||||
<div class="form-group">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import { Lightbulb, X } from 'lucide-react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import sanitize from 'sanitize-html';
|
||||
import { ReactNode } from 'react';
|
||||
import { useStore } from 'zustand';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
@@ -15,25 +14,11 @@ export type Props = {
|
||||
insightCloseId?: string; // set if you want to be able to close the box and not show it again
|
||||
};
|
||||
|
||||
export function InsightsBox({
|
||||
header,
|
||||
content,
|
||||
setHtmlContent,
|
||||
insightCloseId,
|
||||
}: Props) {
|
||||
export function InsightsBox({ header, content, insightCloseId }: Props) {
|
||||
// allow to close the box and not show it again in local storage with zustand
|
||||
const { addInsightIDClosed, isClosed } = useStore(insightStore);
|
||||
const isInsightClosed = isClosed(insightCloseId);
|
||||
|
||||
// allow angular views to set html messages for the insights box
|
||||
const htmlContent = useMemo(() => {
|
||||
if (setHtmlContent && typeof content === 'string') {
|
||||
// eslint-disable-next-line react/no-danger
|
||||
return <div dangerouslySetInnerHTML={{ __html: sanitize(content) }} />;
|
||||
}
|
||||
return null;
|
||||
}, [setHtmlContent, content]);
|
||||
|
||||
if (isInsightClosed) {
|
||||
return null;
|
||||
}
|
||||
@@ -44,10 +29,16 @@ export function InsightsBox({
|
||||
<Lightbulb className="h-4 text-warning-7 th-highcontrast:text-warning-6 th-dark:text-warning-6" />
|
||||
</div>
|
||||
<div>
|
||||
<p className={clsx('mb-2 font-bold', insightCloseId && 'pr-4')}>
|
||||
<p
|
||||
className={clsx(
|
||||
// text-[0.9em] matches .form-horizontal .control-label font-size used in many labels in portainer
|
||||
'mb-2 text-[0.9em] font-medium',
|
||||
insightCloseId && 'pr-4'
|
||||
)}
|
||||
>
|
||||
{header}
|
||||
</p>
|
||||
<div>{htmlContent || content}</div>
|
||||
<div className="small">{content}</div>
|
||||
</div>
|
||||
{insightCloseId && (
|
||||
<Button
|
||||
|
||||
@@ -63,7 +63,11 @@ export function Button<TasProps = unknown>({
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
className={clsx(`btn btn-${color}`, sizeClass(size), className)}
|
||||
onClick={onClick}
|
||||
onClick={(e) => {
|
||||
if (!disabled) {
|
||||
onClick?.(e);
|
||||
}
|
||||
}}
|
||||
title={title}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...ariaProps}
|
||||
|
||||
29
app/react/docker/host/SetupView/GpusInsights.tsx
Normal file
29
app/react/docker/host/SetupView/GpusInsights.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { InsightsBox } from '@@/InsightsBox';
|
||||
|
||||
export function GpusInsights() {
|
||||
return (
|
||||
<InsightsBox
|
||||
content={
|
||||
<>
|
||||
<p>
|
||||
From 2.18 on, the set-up of available GPUs for a Docker Standalone
|
||||
environment has been shifted from Add environment and Environment
|
||||
details to Host -> Setup, so as to align with other settings.
|
||||
</p>
|
||||
<p>
|
||||
A toggle has been introduced for enabling/disabling management of
|
||||
GPU settings in the Portainer UI - to alleviate the performance
|
||||
impact of showing those settings.
|
||||
</p>
|
||||
<p>
|
||||
The UI has been updated to clarify that GPU settings support is only
|
||||
for Docker Standalone (and not Docker Swarm, which was never
|
||||
supported in the UI).
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
header="GPU settings update"
|
||||
insightCloseId="gpu-settings-update-closed"
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user