mirror of https://github.com/portainer/portainer
refactor(settings): kube settings panel [EE-5504] (#9079)
parent
806e1fdffa
commit
7dc6a1559f
|
@ -7,6 +7,7 @@ import { r2a } from '@/react-tools/react2angular';
|
||||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||||
import { ApplicationSettingsPanel } from '@/react/portainer/settings/SettingsView/ApplicationSettingsPanel';
|
import { ApplicationSettingsPanel } from '@/react/portainer/settings/SettingsView/ApplicationSettingsPanel';
|
||||||
|
import { KubeSettingsPanel } from '@/react/portainer/settings/SettingsView/KubeSettingsPanel';
|
||||||
|
|
||||||
export const settingsModule = angular
|
export const settingsModule = angular
|
||||||
.module('portainer.app.react.components.settings', [])
|
.module('portainer.app.react.components.settings', [])
|
||||||
|
@ -22,4 +23,8 @@ export const settingsModule = angular
|
||||||
.component(
|
.component(
|
||||||
'applicationSettingsPanel',
|
'applicationSettingsPanel',
|
||||||
r2a(withReactQuery(ApplicationSettingsPanel), ['onSuccess'])
|
r2a(withReactQuery(ApplicationSettingsPanel), ['onSuccess'])
|
||||||
|
)
|
||||||
|
.component(
|
||||||
|
'kubeSettingsPanel',
|
||||||
|
r2a(withUIRouter(withReactQuery(KubeSettingsPanel)), [])
|
||||||
).name;
|
).name;
|
||||||
|
|
|
@ -2,92 +2,7 @@
|
||||||
|
|
||||||
<application-settings-panel on-success="(handleSuccess)"></application-settings-panel>
|
<application-settings-panel on-success="(handleSuccess)"></application-settings-panel>
|
||||||
|
|
||||||
<div class="row">
|
<kube-settings-panel></kube-settings-panel>
|
||||||
<div class="col-sm-12">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-header icon="svg-kube" title-text="Kubernetes settings"></rd-widget-header>
|
|
||||||
<rd-widget-body>
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<!-- helm charts -->
|
|
||||||
<div class="col-sm-12 form-section-title"> Helm Repository </div>
|
|
||||||
<div>
|
|
||||||
<div class="form-group">
|
|
||||||
<span class="col-sm-12 text-muted small">
|
|
||||||
You can specify the URL to your own helm repository here. See the
|
|
||||||
<a href="https://helm.sh/docs/topics/chart_repository/" target="_blank">official documentation</a> for more details.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="helmrepository_url" class="col-sm-2 control-label text-left"> URL </label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" ng-model="settings.HelmRepositoryURL" id="helmrepository_url" placeholder="https://charts.bitnami.com/bitnami" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !helm charts -->
|
|
||||||
<!-- kube -->
|
|
||||||
<div class="col-sm-12 form-section-title"> Kubeconfig </div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="kubeconfig_expiry" class="col-sm-2 control-label text-left"> Kubeconfig expiry </label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<select
|
|
||||||
id="kubeconfig_expiry"
|
|
||||||
class="form-control"
|
|
||||||
ng-model="formValues.KubeconfigExpiry"
|
|
||||||
ng-options="opt.value as opt.key for opt in state.availableKubeconfigExpiryOptions"
|
|
||||||
></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- ! kube -->
|
|
||||||
<!-- deployment options -->
|
|
||||||
<div class="col-sm-12 form-section-title"> Deployment Options </div>
|
|
||||||
<div class="form-group">
|
|
||||||
<por-switch-field
|
|
||||||
label="'Enforce code-based deployment'"
|
|
||||||
name="'toggle_hideAddWithForm'"
|
|
||||||
feature-id="enforceDeploymentOptions"
|
|
||||||
disabled="true"
|
|
||||||
checked="false"
|
|
||||||
field-class="'col-sm-12'"
|
|
||||||
label-class="'col-sm-2'"
|
|
||||||
tooltip="'Hides the \'Add with form\' buttons and prevents adding/editing of resources via forms'"
|
|
||||||
></por-switch-field>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<por-switch-field
|
|
||||||
label="'Require a note on applications'"
|
|
||||||
name="'toggle_requireNoteOnApplications'"
|
|
||||||
feature-id="requireNoteOnApplications"
|
|
||||||
disabled="true"
|
|
||||||
checked="false"
|
|
||||||
field-class="'col-sm-12'"
|
|
||||||
label-class="'col-sm-2'"
|
|
||||||
tooltip="'BE allows entry of notes in Add/Edit application. Using this setting will enforce entry of a note in Add/Edit application (and prevent complete clearing of it in Application details).'"
|
|
||||||
></por-switch-field>
|
|
||||||
</div>
|
|
||||||
<!-- !deployment options -->
|
|
||||||
<!-- actions -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-click="saveKubernetesSettings()"
|
|
||||||
ng-disabled="state.kubeSettingsActionInProgress"
|
|
||||||
button-spinner="state.kubeSettingsActionInProgress"
|
|
||||||
data-cy="settings-saveKubeSettingsButton"
|
|
||||||
>
|
|
||||||
<span ng-hide="state.kubeSettingsActionInProgress">Save Kubernetes settings</span>
|
|
||||||
<span ng-show="state.kubeSettingsActionInProgress">Saving...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !actions -->
|
|
||||||
</form>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ssl-ca-file-settings></ssl-ca-file-settings>
|
<ssl-ca-file-settings></ssl-ca-file-settings>
|
||||||
<ssl-certificate-settings ng-show="state.showHTTPS"></ssl-certificate-settings>
|
<ssl-certificate-settings ng-show="state.showHTTPS"></ssl-certificate-settings>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
|
||||||
|
|
||||||
angular.module('portainer.app').controller('SettingsController', [
|
angular.module('portainer.app').controller('SettingsController', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
|
@ -10,40 +8,13 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
function ($scope, Notifications, SettingsService, StateManager) {
|
function ($scope, Notifications, SettingsService, StateManager) {
|
||||||
$scope.updateSettings = updateSettings;
|
$scope.updateSettings = updateSettings;
|
||||||
$scope.handleSuccess = handleSuccess;
|
$scope.handleSuccess = handleSuccess;
|
||||||
$scope.requireNoteOnApplications = FeatureId.K8S_REQUIRE_NOTE_ON_APPLICATIONS;
|
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
availableKubeconfigExpiryOptions: [
|
|
||||||
{
|
|
||||||
key: '1 day',
|
|
||||||
value: '24h',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '7 days',
|
|
||||||
value: `${24 * 7}h`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '30 days',
|
|
||||||
value: `${24 * 30}h`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '1 year',
|
|
||||||
value: `${24 * 30 * 12}h`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'No expiry',
|
|
||||||
value: '0',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
backupInProgress: false,
|
|
||||||
featureLimited: false,
|
|
||||||
showHTTPS: !window.ddExtension,
|
showHTTPS: !window.ddExtension,
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
KubeconfigExpiry: undefined,
|
|
||||||
HelmRepositoryURL: undefined,
|
|
||||||
BlackListedLabels: [],
|
BlackListedLabels: [],
|
||||||
labelName: '',
|
labelName: '',
|
||||||
labelValue: '',
|
labelValue: '',
|
||||||
|
@ -66,18 +37,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
updateSettings(filteredSettingsPayload, 'Hidden container settings updated');
|
updateSettings(filteredSettingsPayload, 'Hidden container settings updated');
|
||||||
};
|
};
|
||||||
|
|
||||||
// only update the values from the kube settings widget. In future separate the api endpoints
|
|
||||||
$scope.saveKubernetesSettings = function () {
|
|
||||||
const kubeSettingsPayload = {
|
|
||||||
KubeconfigExpiry: $scope.formValues.KubeconfigExpiry,
|
|
||||||
HelmRepositoryURL: $scope.formValues.HelmRepositoryURL,
|
|
||||||
GlobalDeploymentOptions: $scope.formValues.GlobalDeploymentOptions,
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.state.kubeSettingsActionInProgress = true;
|
|
||||||
updateSettings(kubeSettingsPayload, 'Kubernetes settings updated');
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateSettings(settings, successMessage = 'Settings updated') {
|
function updateSettings(settings, successMessage = 'Settings updated') {
|
||||||
return SettingsService.update(settings)
|
return SettingsService.update(settings)
|
||||||
.then(function success(settings) {
|
.then(function success(settings) {
|
||||||
|
@ -88,7 +47,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
Notifications.error('Failure', err, 'Unable to update settings');
|
Notifications.error('Failure', err, 'Unable to update settings');
|
||||||
})
|
})
|
||||||
.finally(function final() {
|
.finally(function final() {
|
||||||
$scope.state.kubeSettingsActionInProgress = false;
|
|
||||||
$scope.state.actionInProgress = false;
|
$scope.state.actionInProgress = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -100,10 +58,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
StateManager.updateEnableTelemetry(settings.EnableTelemetry);
|
StateManager.updateEnableTelemetry(settings.EnableTelemetry);
|
||||||
$scope.formValues.BlackListedLabels = settings.BlackListedLabels;
|
$scope.formValues.BlackListedLabels = settings.BlackListedLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger an event to update the deployment options for the react based sidebar
|
|
||||||
const event = new CustomEvent('portainer:deploymentOptionsUpdated');
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
|
@ -112,8 +66,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
var settings = data;
|
var settings = data;
|
||||||
$scope.settings = settings;
|
$scope.settings = settings;
|
||||||
|
|
||||||
$scope.formValues.KubeconfigExpiry = settings.KubeconfigExpiry;
|
|
||||||
$scope.formValues.HelmRepositoryURL = settings.HelmRepositoryURL;
|
|
||||||
$scope.formValues.BlackListedLabels = settings.BlackListedLabels;
|
$scope.formValues.BlackListedLabels = settings.BlackListedLabels;
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { FormControl } from '@@/form-components/FormControl';
|
||||||
import { Input } from '@@/form-components/Input';
|
import { Input } from '@@/form-components/Input';
|
||||||
import { SwitchField } from '@@/form-components/SwitchField';
|
import { SwitchField } from '@@/form-components/SwitchField';
|
||||||
|
|
||||||
import { useToggledValue } from './useToggledValue';
|
import { useToggledValue } from '../useToggledValue';
|
||||||
|
|
||||||
import { DemoAlert } from './DemoAlert';
|
import { DemoAlert } from './DemoAlert';
|
||||||
|
|
||||||
export function LogoFieldset() {
|
export function LogoFieldset() {
|
||||||
|
|
|
@ -7,7 +7,8 @@ import { FormControl } from '@@/form-components/FormControl';
|
||||||
import { TextArea } from '@@/form-components/Input/Textarea';
|
import { TextArea } from '@@/form-components/Input/Textarea';
|
||||||
import { SwitchField } from '@@/form-components/SwitchField';
|
import { SwitchField } from '@@/form-components/SwitchField';
|
||||||
|
|
||||||
import { useToggledValue } from './useToggledValue';
|
import { useToggledValue } from '../useToggledValue';
|
||||||
|
|
||||||
import { DemoAlert } from './DemoAlert';
|
import { DemoAlert } from './DemoAlert';
|
||||||
|
|
||||||
export function ScreenBannerFieldset() {
|
export function ScreenBannerFieldset() {
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||||
|
import { isLimitedToBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||||
|
|
||||||
|
import { FormSection } from '@@/form-components/FormSection';
|
||||||
|
import { SwitchField } from '@@/form-components/SwitchField';
|
||||||
|
|
||||||
|
import { KubeNoteMinimumCharacters } from './KubeNoteMinimumCharacters';
|
||||||
|
import { FormValues } from './types';
|
||||||
|
|
||||||
|
export function DeploymentOptionsSection() {
|
||||||
|
const {
|
||||||
|
values: { globalDeploymentOptions: values },
|
||||||
|
setFieldValue,
|
||||||
|
} = useFormikContext<FormValues>();
|
||||||
|
const limitedFeature = isLimitedToBE(FeatureId.ENFORCE_DEPLOYMENT_OPTIONS);
|
||||||
|
return (
|
||||||
|
<FormSection title="Deployment Options">
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<SwitchField
|
||||||
|
label="Enforce code-based deployment"
|
||||||
|
checked={values.hideAddWithForm}
|
||||||
|
name="toggle_hideAddWithForm"
|
||||||
|
featureId={FeatureId.ENFORCE_DEPLOYMENT_OPTIONS}
|
||||||
|
onChange={(value) => handleToggleAddWithForm(value)}
|
||||||
|
labelClass="col-sm-3 col-lg-2"
|
||||||
|
tooltip="Hides the 'Add with form' buttons and prevents adding/editing of resources via forms"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{values.hideAddWithForm && (
|
||||||
|
<div className="form-group flex flex-col gap-y-1">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<SwitchField
|
||||||
|
label="Allow web editor and custom template use"
|
||||||
|
checked={!values.hideWebEditor}
|
||||||
|
name="toggle_hideWebEditor"
|
||||||
|
onChange={(value) =>
|
||||||
|
setFieldValue('globalDeploymentOptions.hideWebEditor', !value)
|
||||||
|
}
|
||||||
|
labelClass="col-sm-2 !pl-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<SwitchField
|
||||||
|
label="Allow specifying of a manifest via a URL"
|
||||||
|
checked={!values.hideFileUpload}
|
||||||
|
name="toggle_hideFileUpload"
|
||||||
|
onChange={(value) =>
|
||||||
|
setFieldValue('globalDeploymentOptions.hideFileUpload', !value)
|
||||||
|
}
|
||||||
|
labelClass="col-sm-2 !pl-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!limitedFeature && (
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<SwitchField
|
||||||
|
label="Allow per environment override"
|
||||||
|
checked={values.perEnvOverride}
|
||||||
|
onChange={(value) =>
|
||||||
|
setFieldValue('globalDeploymentOptions.perEnvOverride', value)
|
||||||
|
}
|
||||||
|
name="toggle_perEnvOverride"
|
||||||
|
labelClass="col-sm-3 col-lg-2"
|
||||||
|
tooltip="Allows overriding of deployment options in the Cluster setup screen of each environment"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<KubeNoteMinimumCharacters />
|
||||||
|
</FormSection>
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleToggleAddWithForm(checked: boolean) {
|
||||||
|
await setFieldValue('globalDeploymentOptions.hideWebEditor', checked);
|
||||||
|
await setFieldValue('globalDeploymentOptions.hideFileUpload', checked);
|
||||||
|
await setFieldValue('globalDeploymentOptions.hideAddWithForm', checked);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Field, useField } from 'formik';
|
||||||
|
|
||||||
|
import { TextTip } from '@@/Tip/TextTip';
|
||||||
|
import { FormControl } from '@@/form-components/FormControl';
|
||||||
|
import { FormSection } from '@@/form-components/FormSection';
|
||||||
|
import { Input } from '@@/form-components/Input';
|
||||||
|
|
||||||
|
export function HelmSection() {
|
||||||
|
const [{ name }, { error }] = useField<string>('helmRepositoryUrl');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormSection title="Helm Repository">
|
||||||
|
<div className="mb-2">
|
||||||
|
<TextTip color="blue">
|
||||||
|
You can specify the URL to your own helm repository here. See the{' '}
|
||||||
|
<a
|
||||||
|
href="https://helm.sh/docs/topics/chart_repository/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
official documentation
|
||||||
|
</a>{' '}
|
||||||
|
for more details.
|
||||||
|
</TextTip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormControl label="URL" errors={error} inputId="helm-repo-url">
|
||||||
|
<Field
|
||||||
|
as={Input}
|
||||||
|
id="helm-repo-url"
|
||||||
|
name={name}
|
||||||
|
placeholder="https://charts.bitnami.com/bitnami"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormSection>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { useField } from 'formik';
|
||||||
|
|
||||||
|
import { FormControl } from '@@/form-components/FormControl';
|
||||||
|
import { FormSection } from '@@/form-components/FormSection';
|
||||||
|
import { PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: '1 day',
|
||||||
|
value: '24h',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '7 days',
|
||||||
|
value: `${24 * 7}h`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '30 days',
|
||||||
|
value: `${24 * 30}h`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1 year',
|
||||||
|
value: `${24 * 30 * 12}h`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No expiry',
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function KubeConfigSection() {
|
||||||
|
const [{ value }, { error }, { setValue }] =
|
||||||
|
useField<string>('kubeconfigExpiry');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormSection title="Kubeconfig">
|
||||||
|
<FormControl label="Kubeconfig expiry" errors={error}>
|
||||||
|
<PortainerSelect
|
||||||
|
value={value}
|
||||||
|
options={options}
|
||||||
|
onChange={(value) => value && setValue(value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormSection>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { useField } from 'formik';
|
||||||
|
|
||||||
|
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||||
|
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||||
|
|
||||||
|
import { FormControl } from '@@/form-components/FormControl';
|
||||||
|
import { SwitchField } from '@@/form-components/SwitchField';
|
||||||
|
import { Input } from '@@/form-components/Input';
|
||||||
|
|
||||||
|
import { useToggledValue } from '../useToggledValue';
|
||||||
|
|
||||||
|
export function KubeNoteMinimumCharacters() {
|
||||||
|
const [{ value }, { error }, { setValue }] = useField<number>(
|
||||||
|
'globalDeploymentOptions.minApplicationNoteLength'
|
||||||
|
);
|
||||||
|
const [isEnabled, setIsEnabled] = useToggledValue(
|
||||||
|
'globalDeploymentOptions.minApplicationNoteLength',
|
||||||
|
'globalDeploymentOptions.requireNoteOnApplications'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<SwitchField
|
||||||
|
label="Require a note on applications"
|
||||||
|
checked={isEnabled}
|
||||||
|
name="toggle_requireNoteOnApplications"
|
||||||
|
onChange={(value) => setIsEnabled(value)}
|
||||||
|
featureId={FeatureId.K8S_REQUIRE_NOTE_ON_APPLICATIONS}
|
||||||
|
labelClass="col-sm-3 col-lg-2"
|
||||||
|
tooltip={`${
|
||||||
|
isBE ? '' : 'BE allows entry of notes in Add/Edit application. '
|
||||||
|
}Using this will enforce entry of a note in Add/Edit application (and prevent complete clearing of it in Application details).`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isEnabled && (
|
||||||
|
<FormControl
|
||||||
|
label={
|
||||||
|
<span className="pl-4">
|
||||||
|
Minimum number of characters note must have
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
errors={error}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
name="minNoteLength"
|
||||||
|
type="number"
|
||||||
|
placeholder="50"
|
||||||
|
min="1"
|
||||||
|
max="9999"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.valueAsNumber)}
|
||||||
|
className="w-1/4"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
import { Form, Formik } from 'formik';
|
||||||
|
import { useQueryClient } from 'react-query';
|
||||||
|
|
||||||
|
import kubeIcon from '@/assets/ico/kube.svg?c';
|
||||||
|
import { notifySuccess } from '@/portainer/services/notifications';
|
||||||
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
|
||||||
|
import { LoadingButton } from '@@/buttons';
|
||||||
|
import { Widget } from '@@/Widget';
|
||||||
|
|
||||||
|
import { useSettings, useUpdateSettingsMutation } from '../../queries';
|
||||||
|
|
||||||
|
import { HelmSection } from './HelmSection';
|
||||||
|
import { KubeConfigSection } from './KubeConfigSection';
|
||||||
|
import { FormValues } from './types';
|
||||||
|
import { DeploymentOptionsSection } from './DeploymentOptionsSection';
|
||||||
|
import { validation } from './validation';
|
||||||
|
|
||||||
|
export function KubeSettingsPanel() {
|
||||||
|
const settingsQuery = useSettings();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const environmentId = useEnvironmentId(false);
|
||||||
|
const mutation = useUpdateSettingsMutation();
|
||||||
|
|
||||||
|
if (!settingsQuery.data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues: FormValues = {
|
||||||
|
helmRepositoryUrl: settingsQuery.data.HelmRepositoryURL || '',
|
||||||
|
kubeconfigExpiry: settingsQuery.data.KubeconfigExpiry || '0',
|
||||||
|
globalDeploymentOptions: settingsQuery.data.GlobalDeploymentOptions || {
|
||||||
|
requireNoteOnApplications: false,
|
||||||
|
minApplicationNoteLength: 0,
|
||||||
|
hideAddWithForm: false,
|
||||||
|
hideFileUpload: false,
|
||||||
|
hideWebEditor: false,
|
||||||
|
perEnvOverride: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<Widget>
|
||||||
|
<Widget.Title icon={kubeIcon} title="Kubernetes settings" />
|
||||||
|
<Widget.Body>
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
validationSchema={validation}
|
||||||
|
validateOnMount
|
||||||
|
>
|
||||||
|
{() => (
|
||||||
|
<Form className="form-horizontal">
|
||||||
|
<HelmSection />
|
||||||
|
<KubeConfigSection />
|
||||||
|
<DeploymentOptionsSection />
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<LoadingButton
|
||||||
|
isLoading={mutation.isLoading}
|
||||||
|
loadingText="Saving"
|
||||||
|
className="!ml-0"
|
||||||
|
>
|
||||||
|
Save Kubernetes Settings
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Widget.Body>
|
||||||
|
</Widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleSubmit(values: FormValues) {
|
||||||
|
mutation.mutate(
|
||||||
|
{
|
||||||
|
HelmRepositoryURL: values.helmRepositoryUrl,
|
||||||
|
KubeconfigExpiry: values.kubeconfigExpiry,
|
||||||
|
GlobalDeploymentOptions: {
|
||||||
|
...values.globalDeploymentOptions,
|
||||||
|
requireNoteOnApplications:
|
||||||
|
values.globalDeploymentOptions.requireNoteOnApplications,
|
||||||
|
minApplicationNoteLength: values.globalDeploymentOptions
|
||||||
|
.requireNoteOnApplications
|
||||||
|
? values.globalDeploymentOptions.minApplicationNoteLength
|
||||||
|
: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
async onSuccess() {
|
||||||
|
if (environmentId) {
|
||||||
|
await queryClient.invalidateQueries([
|
||||||
|
'environments',
|
||||||
|
environmentId,
|
||||||
|
'deploymentOptions',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
notifySuccess('Success', 'Kubernetes settings updated');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { KubeSettingsPanel } from './KubeSettingsPanel';
|
|
@ -0,0 +1,12 @@
|
||||||
|
export interface FormValues {
|
||||||
|
helmRepositoryUrl: string;
|
||||||
|
kubeconfigExpiry: string;
|
||||||
|
globalDeploymentOptions: {
|
||||||
|
hideAddWithForm: boolean;
|
||||||
|
perEnvOverride: boolean;
|
||||||
|
hideWebEditor: boolean;
|
||||||
|
hideFileUpload: boolean;
|
||||||
|
requireNoteOnApplications: boolean;
|
||||||
|
minApplicationNoteLength: number;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { SchemaOf, object, string, boolean, number } from 'yup';
|
||||||
|
|
||||||
|
import { isValidUrl } from '@@/form-components/validate-url';
|
||||||
|
|
||||||
|
import { FormValues } from './types';
|
||||||
|
|
||||||
|
export function validation(): SchemaOf<FormValues> {
|
||||||
|
return object().shape({
|
||||||
|
helmRepositoryUrl: string()
|
||||||
|
.default('')
|
||||||
|
.test('valid-url', 'Invalid URL', (value) => !value || isValidUrl(value)),
|
||||||
|
kubeconfigExpiry: string().required(),
|
||||||
|
globalDeploymentOptions: object().shape({
|
||||||
|
hideAddWithForm: boolean().required(),
|
||||||
|
perEnvOverride: boolean().required(),
|
||||||
|
hideWebEditor: boolean().required(),
|
||||||
|
hideFileUpload: boolean().required(),
|
||||||
|
requireNoteOnApplications: boolean().required(),
|
||||||
|
minApplicationNoteLength: number()
|
||||||
|
.typeError('Must be a number')
|
||||||
|
.default(0)
|
||||||
|
.when('requireNoteOnApplications', {
|
||||||
|
is: true,
|
||||||
|
then: (schema) =>
|
||||||
|
schema
|
||||||
|
.required()
|
||||||
|
.min(1, 'Value should be between 1 to 9999')
|
||||||
|
.max(9999, 'Value should be between 1 to 9999'),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export function useToggledValue(fieldName: string) {
|
export function useToggledValue(
|
||||||
|
fieldName: string,
|
||||||
|
toggleFieldName = `${fieldName}Enabled`
|
||||||
|
) {
|
||||||
const [, { value }, { setValue }] = useField<string>(fieldName);
|
const [, { value }, { setValue }] = useField<string>(fieldName);
|
||||||
const [, { value: isEnabled }, { setValue: setIsEnabled }] =
|
const [, { value: isEnabled }, { setValue: setIsEnabled }] =
|
||||||
useField<boolean>(`${fieldName}Enabled`);
|
useField<boolean>(toggleFieldName);
|
||||||
const [oldValue, setOldValue] = useState(value);
|
const [oldValue, setOldValue] = useState(value);
|
||||||
|
|
||||||
async function handleIsEnabledChange(enabled: boolean) {
|
async function handleIsEnabledChange(enabled: boolean) {
|
|
@ -130,6 +130,7 @@ export interface Settings {
|
||||||
AllowStackManagementForRegularUsers: boolean;
|
AllowStackManagementForRegularUsers: boolean;
|
||||||
AllowDeviceMappingForRegularUsers: boolean;
|
AllowDeviceMappingForRegularUsers: boolean;
|
||||||
AllowContainerCapabilitiesForRegularUsers: boolean;
|
AllowContainerCapabilitiesForRegularUsers: boolean;
|
||||||
|
GlobalDeploymentOptions?: GlobalDeploymentOptions;
|
||||||
Edge: {
|
Edge: {
|
||||||
PingInterval: number;
|
PingInterval: number;
|
||||||
SnapshotInterval: number;
|
SnapshotInterval: number;
|
||||||
|
@ -148,6 +149,9 @@ interface GlobalDeploymentOptions {
|
||||||
hideWebEditor: boolean;
|
hideWebEditor: boolean;
|
||||||
/** Hide the file upload option in the remaining visible forms */
|
/** Hide the file upload option in the remaining visible forms */
|
||||||
hideFileUpload: boolean;
|
hideFileUpload: boolean;
|
||||||
|
/** Make note on application add/edit screen required */
|
||||||
|
requireNoteOnApplications: boolean;
|
||||||
|
minApplicationNoteLength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PublicSettingsResponse {
|
export interface PublicSettingsResponse {
|
||||||
|
|
Loading…
Reference in New Issue