diff --git a/.eslintrc.yml b/.eslintrc.yml
index 5891d1965..bd801a518 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -23,6 +23,8 @@ parserOptions:
modules: true
rules:
+ no-console: error
+ no-alert: error
no-control-regex: 'off'
no-empty: warn
no-empty-function: warn
@@ -86,8 +88,8 @@ overrides:
no-plusplus: off
func-style: [error, 'declaration']
import/prefer-default-export: off
- no-use-before-define: "off"
- '@typescript-eslint/no-use-before-define': ['error', { functions: false, "allowNamedExports": true }]
+ no-use-before-define: 'off'
+ '@typescript-eslint/no-use-before-define': ['error', { functions: false, 'allowNamedExports': true }]
no-shadow: 'off'
'@typescript-eslint/no-shadow': off
jsx-a11y/no-autofocus: warn
diff --git a/app/docker/components/container-capabilities/container-capabilities.controller.js b/app/docker/components/container-capabilities/container-capabilities.controller.js
deleted file mode 100644
index a743f5a50..000000000
--- a/app/docker/components/container-capabilities/container-capabilities.controller.js
+++ /dev/null
@@ -1,23 +0,0 @@
-export default class ContainerCapabilitiesController {
- /* @ngInject */
- constructor($scope) {
- this.$scope = $scope;
-
- this.oldCapabilities = [];
- }
-
- createOnChangeHandler(capability) {
- return (checked) => {
- this.$scope.$evalAsync(() => {
- capability.allowed = checked;
- });
- };
- }
-
- $doCheck() {
- if (this.oldCapabilities.length !== this.capabilities.length) {
- this.oldCapabilities = this.capabilities;
- this.capabilitiesOnChange = Object.fromEntries(this.capabilities.map((cap) => [cap.capability, this.createOnChangeHandler(cap)]));
- }
- }
-}
diff --git a/app/docker/components/container-capabilities/container-capabilities.js b/app/docker/components/container-capabilities/container-capabilities.js
deleted file mode 100644
index aa13b5ac8..000000000
--- a/app/docker/components/container-capabilities/container-capabilities.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import controller from './container-capabilities.controller';
-
-angular.module('portainer.docker').component('containerCapabilities', {
- templateUrl: './containerCapabilities.html',
- bindings: {
- capabilities: '=',
- },
- controller,
-});
diff --git a/app/docker/components/container-capabilities/containerCapabilities.html b/app/docker/components/container-capabilities/containerCapabilities.html
deleted file mode 100644
index d2e0e47b2..000000000
--- a/app/docker/components/container-capabilities/containerCapabilities.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
diff --git a/app/docker/models/containerCapabilities.js b/app/docker/models/containerCapabilities.js
deleted file mode 100644
index dadf14eac..000000000
--- a/app/docker/models/containerCapabilities.js
+++ /dev/null
@@ -1,90 +0,0 @@
-var capDesc = {
- SETPCAP: 'Modify process capabilities.',
- MKNOD: 'Create special files using mknod(2).',
- AUDIT_WRITE: 'Write records to kernel auditing log.',
- CHOWN: 'Make arbitrary changes to file UIDs and GIDs (see chown(2)).',
- NET_RAW: 'Use RAW and PACKET sockets.',
- DAC_OVERRIDE: 'Bypass file read, write, and execute permission checks.',
- FOWNER: 'Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file.',
- FSETID: 'Don’t clear set-user-ID and set-group-ID permission bits when a file is modified.',
- KILL: 'Bypass permission checks for sending signals.',
- SETGID: 'Make arbitrary manipulations of process GIDs and supplementary GID list.',
- SETUID: 'Make arbitrary manipulations of process UIDs.',
- NET_BIND_SERVICE: 'Bind a socket to internet domain privileged ports (port numbers less than 1024).',
- SYS_CHROOT: 'Use chroot(2), change root directory.',
- SETFCAP: 'Set file capabilities.',
- SYS_MODULE: 'Load and unload kernel modules.',
- SYS_RAWIO: 'Perform I/O port operations (iopl(2) and ioperm(2)).',
- SYS_PACCT: 'Use acct(2), switch process accounting on or off.',
- SYS_ADMIN: 'Perform a range of system administration operations.',
- SYS_NICE: 'Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes.',
- SYS_RESOURCE: 'Override resource Limits.',
- SYS_TIME: 'Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock.',
- SYS_TTY_CONFIG: 'Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals.',
- AUDIT_CONTROL: 'Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules.',
- MAC_ADMIN: 'Allow MAC configuration or state changes. Implemented for the Smack LSM.',
- MAC_OVERRIDE: 'Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM).',
- NET_ADMIN: 'Perform various network-related operations.',
- SYSLOG: 'Perform privileged syslog(2) operations.',
- DAC_READ_SEARCH: 'Bypass file read permission checks and directory read and execute permission checks.',
- LINUX_IMMUTABLE: 'Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags.',
- NET_BROADCAST: 'Make socket broadcasts, and listen to multicasts.',
- IPC_LOCK: 'Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)).',
- IPC_OWNER: 'Bypass permission checks for operations on System V IPC objects.',
- SYS_PTRACE: 'Trace arbitrary processes using ptrace(2).',
- SYS_BOOT: 'Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution.',
- LEASE: 'Establish leases on arbitrary files (see fcntl(2)).',
- WAKE_ALARM: 'Trigger something that will wake up the system.',
- BLOCK_SUSPEND: 'Employ features that can block system suspend.',
-};
-
-export function ContainerCapabilities() {
- // all capabilities can be found at https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
- return [
- new ContainerCapability('SETPCAP', true),
- new ContainerCapability('MKNOD', true),
- new ContainerCapability('AUDIT_WRITE', true),
- new ContainerCapability('CHOWN', true),
- new ContainerCapability('NET_RAW', true),
- new ContainerCapability('DAC_OVERRIDE', true),
- new ContainerCapability('FOWNER', true),
- new ContainerCapability('FSETID', true),
- new ContainerCapability('KILL', true),
- new ContainerCapability('SETGID', true),
- new ContainerCapability('SETUID', true),
- new ContainerCapability('NET_BIND_SERVICE', true),
- new ContainerCapability('SYS_CHROOT', true),
- new ContainerCapability('SETFCAP', true),
- new ContainerCapability('SYS_MODULE', false),
- new ContainerCapability('SYS_RAWIO', false),
- new ContainerCapability('SYS_PACCT', false),
- new ContainerCapability('SYS_ADMIN', false),
- new ContainerCapability('SYS_NICE', false),
- new ContainerCapability('SYS_RESOURCE', false),
- new ContainerCapability('SYS_TIME', false),
- new ContainerCapability('SYS_TTY_CONFIG', false),
- new ContainerCapability('AUDIT_CONTROL', false),
- new ContainerCapability('MAC_ADMIN', false),
- new ContainerCapability('MAC_OVERRIDE', false),
- new ContainerCapability('NET_ADMIN', false),
- new ContainerCapability('SYSLOG', false),
- new ContainerCapability('DAC_READ_SEARCH', false),
- new ContainerCapability('LINUX_IMMUTABLE', false),
- new ContainerCapability('NET_BROADCAST', false),
- new ContainerCapability('IPC_LOCK', false),
- new ContainerCapability('IPC_OWNER', false),
- new ContainerCapability('SYS_PTRACE', false),
- new ContainerCapability('SYS_BOOT', false),
- new ContainerCapability('LEASE', false),
- new ContainerCapability('WAKE_ALARM', false),
- new ContainerCapability('BLOCK_SUSPEND', false),
- ].sort(function (a, b) {
- return a.capability < b.capability ? -1 : 1;
- });
-}
-
-export function ContainerCapability(cap, allowed) {
- this.capability = cap;
- this.allowed = allowed;
- this.description = capDesc[cap];
-}
diff --git a/app/docker/react/components/containers.ts b/app/docker/react/components/containers.ts
index 1f573b4eb..d562205ce 100644
--- a/app/docker/react/components/containers.ts
+++ b/app/docker/react/components/containers.ts
@@ -30,6 +30,10 @@ import {
resourcesTabUtils,
type ResourcesTabValues,
} from '@/react/docker/containers/CreateView/ResourcesTab';
+import {
+ CapabilitiesTab,
+ capabilitiesTabUtils,
+} from '@/react/docker/containers/CreateView/CapabilitiesTab';
const ngModule = angular
.module('portainer.docker.react.components.containers', [])
@@ -91,3 +95,11 @@ withFormValidation, ResourcesTabValues>(
],
resourcesTabUtils.validation
);
+
+withFormValidation(
+ ngModule,
+ CapabilitiesTab,
+ 'dockerCreateContainerCapabilitiesTab',
+ [],
+ capabilitiesTabUtils.validation
+);
diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js
index 14b7a0092..d17067cef 100644
--- a/app/docker/views/containers/create/createContainerController.js
+++ b/app/docker/views/containers/create/createContainerController.js
@@ -9,7 +9,7 @@ import { buildConfirmButton } from '@@/modals/utils';
import { commandsTabUtils } from '@/react/docker/containers/CreateView/CommandsTab';
import { volumesTabUtils } from '@/react/docker/containers/CreateView/VolumesTab';
import { networkTabUtils } from '@/react/docker/containers/CreateView/NetworkTab';
-import { ContainerCapabilities, ContainerCapability } from '@/docker/models/containerCapabilities';
+import { capabilitiesTabUtils } from '@/react/docker/containers/CreateView/CapabilitiesTab';
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
import { ContainerDetailsViewModel } from '@/docker/models/container';
@@ -85,13 +85,13 @@ angular.module('portainer.docker').controller('CreateContainerController', [
DnsSecondary: '',
AccessControlData: new AccessControlFormData(),
NodeName: null,
- capabilities: [],
RegistryModel: new PorImageRegistryModel(),
commands: commandsTabUtils.getDefaultViewModel(),
envVars: envVarsTabUtils.getDefaultViewModel(),
volumes: volumesTabUtils.getDefaultViewModel(),
network: networkTabUtils.getDefaultViewModel(),
resources: resourcesTabUtils.getDefaultViewModel(),
+ capabilities: capabilitiesTabUtils.getDefaultViewModel(),
};
$scope.state = {
@@ -140,6 +140,12 @@ angular.module('portainer.docker').controller('CreateContainerController', [
});
};
+ $scope.onCapabilitiesChange = function (capabilities) {
+ return $scope.$evalAsync(() => {
+ $scope.formValues.capabilities = capabilities;
+ });
+ };
+
function onAlwaysPullChange(checked) {
return $scope.$evalAsync(() => {
$scope.formValues.alwaysPull = checked;
@@ -301,21 +307,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
config.Labels = labels;
}
- function prepareCapabilities(config) {
- var allowed = $scope.formValues.capabilities.filter(function (item) {
- return item.allowed === true;
- });
- var notAllowed = $scope.formValues.capabilities.filter(function (item) {
- return item.allowed === false;
- });
-
- var getCapName = function (item) {
- return item.capability;
- };
- config.HostConfig.CapAdd = allowed.map(getCapName);
- config.HostConfig.CapDrop = notAllowed.map(getCapName);
- }
-
function prepareConfiguration() {
var config = angular.copy($scope.config);
config = commandsTabUtils.toRequest(config, $scope.formValues.commands);
@@ -323,11 +314,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
config = volumesTabUtils.toRequest(config, $scope.formValues.volumes);
config = networkTabUtils.toRequest(config, $scope.formValues.network, $scope.fromContainer.Id);
config = resourcesTabUtils.toRequest(config, $scope.formValues.resources);
+ config = capabilitiesTabUtils.toRequest(config, $scope.formValues.capabilities);
prepareImageConfig(config);
preparePortBindings(config);
prepareLabels(config);
- prepareCapabilities(config);
return config;
}
@@ -354,35 +345,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
});
}
- function loadFromContainerCapabilities(d) {
- if (d.HostConfig.CapAdd) {
- d.HostConfig.CapAdd.forEach(function (cap) {
- $scope.formValues.capabilities.push(new ContainerCapability(cap, true));
- });
- }
- if (d.HostConfig.CapDrop) {
- d.HostConfig.CapDrop.forEach(function (cap) {
- $scope.formValues.capabilities.push(new ContainerCapability(cap, false));
- });
- }
-
- function hasCapability(item) {
- return item.capability === cap.capability;
- }
-
- var capabilities = new ContainerCapabilities();
- for (var i = 0; i < capabilities.length; i++) {
- var cap = capabilities[i];
- if (!_.find($scope.formValues.capabilities, hasCapability)) {
- $scope.formValues.capabilities.push(cap);
- }
- }
-
- $scope.formValues.capabilities.sort(function (a, b) {
- return a.capability < b.capability ? -1 : 1;
- });
- }
-
function loadFromContainerSpec() {
// Get container
Container.get({ id: $transition$.params().from })
@@ -408,12 +370,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
$scope.formValues.volumes = volumesTabUtils.toViewModel(d);
$scope.formValues.network = networkTabUtils.toViewModel(d, $scope.availableNetworks, $scope.runningContainers);
$scope.formValues.resources = resourcesTabUtils.toViewModel(d);
+ $scope.formValues.capabilities = capabilitiesTabUtils.toViewModel(d);
loadFromContainerPortBindings(d);
loadFromContainerLabels(d);
loadFromContainerImageConfig(d);
-
- loadFromContainerCapabilities(d);
})
.then(() => {
$scope.state.containerIsLoaded = true;
@@ -456,7 +417,9 @@ angular.module('portainer.docker').controller('CreateContainerController', [
} else {
$scope.state.containerIsLoaded = true;
$scope.fromContainer = {};
- $scope.formValues.capabilities = $scope.areContainerCapabilitiesEnabled ? new ContainerCapabilities() : [];
+ if ($scope.areContainerCapabilitiesEnabled) {
+ $scope.formValues.capabilities = capabilitiesTabUtils.getDefaultViewModel();
+ }
}
})
.catch((e) => {
diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html
index a5acb7786..5cbe87427 100644
--- a/app/docker/views/containers/create/createcontainer.html
+++ b/app/docker/views/containers/create/createcontainer.html
@@ -302,7 +302,7 @@
-
+
diff --git a/app/react/docker/containers/CreateView/CapabilitiesTab/CapabilitiesTab.tsx b/app/react/docker/containers/CreateView/CapabilitiesTab/CapabilitiesTab.tsx
new file mode 100644
index 000000000..cf8b0fc09
--- /dev/null
+++ b/app/react/docker/containers/CreateView/CapabilitiesTab/CapabilitiesTab.tsx
@@ -0,0 +1,39 @@
+import { FormSection } from '@@/form-components/FormSection';
+import { SwitchField } from '@@/form-components/SwitchField';
+
+import { capabilities } from './types';
+
+export type Values = string[];
+
+export function CapabilitiesTab({
+ values,
+ onChange,
+}: {
+ values: Values;
+ onChange: (values: Values) => void;
+}) {
+ return (
+
+
+ {capabilities.map((cap) => (
+
+ {
+ if (value) {
+ onChange([...values, cap.key]);
+ } else {
+ onChange(values.filter((v) => v !== cap.key));
+ }
+ }}
+ />
+
+ ))}
+
+
+ );
+}
diff --git a/app/react/docker/containers/CreateView/CapabilitiesTab/index.ts b/app/react/docker/containers/CreateView/CapabilitiesTab/index.ts
new file mode 100644
index 000000000..8c70ca8a5
--- /dev/null
+++ b/app/react/docker/containers/CreateView/CapabilitiesTab/index.ts
@@ -0,0 +1,15 @@
+import { validation } from './validation';
+import { toViewModel, getDefaultViewModel } from './toViewModel';
+import { toRequest } from './toRequest';
+
+export {
+ CapabilitiesTab,
+ type Values as CapabilitiesTabValues,
+} from './CapabilitiesTab';
+
+export const capabilitiesTabUtils = {
+ toRequest,
+ toViewModel,
+ validation,
+ getDefaultViewModel,
+};
diff --git a/app/react/docker/containers/CreateView/CapabilitiesTab/toRequest.ts b/app/react/docker/containers/CreateView/CapabilitiesTab/toRequest.ts
new file mode 100644
index 000000000..3db379c9f
--- /dev/null
+++ b/app/react/docker/containers/CreateView/CapabilitiesTab/toRequest.ts
@@ -0,0 +1,20 @@
+import { CreateContainerRequest } from '@/react/docker/containers/CreateView/types';
+
+import { capabilities } from './types';
+import { Values } from './CapabilitiesTab';
+
+export function toRequest(
+ oldConfig: CreateContainerRequest,
+ values: Values
+): CreateContainerRequest {
+ return {
+ ...oldConfig,
+ HostConfig: {
+ ...oldConfig.HostConfig,
+ CapAdd: values,
+ CapDrop: capabilities
+ .filter((cap) => !values.includes(cap.key))
+ .map((cap) => cap.key),
+ },
+ };
+}
diff --git a/app/react/docker/containers/CreateView/CapabilitiesTab/toViewModel.ts b/app/react/docker/containers/CreateView/CapabilitiesTab/toViewModel.ts
new file mode 100644
index 000000000..2f1fe3e4d
--- /dev/null
+++ b/app/react/docker/containers/CreateView/CapabilitiesTab/toViewModel.ts
@@ -0,0 +1,28 @@
+import { ContainerJSON } from '@/react/docker/containers/queries/container';
+
+import { capabilities } from './types';
+import { Values } from './CapabilitiesTab';
+
+export function toViewModel(config: ContainerJSON): Values {
+ const { CapAdd, CapDrop } = getDefaults(config);
+
+ const missingCaps = capabilities
+ .filter(
+ (cap) =>
+ cap.default && !CapAdd.includes(cap.key) && !CapDrop.includes(cap.key)
+ )
+ .map((cap) => cap.key);
+
+ return [...CapAdd, ...missingCaps];
+
+ function getDefaults(config: ContainerJSON) {
+ return {
+ CapAdd: config.HostConfig?.CapAdd || [],
+ CapDrop: config.HostConfig?.CapDrop || [],
+ };
+ }
+}
+
+export function getDefaultViewModel(): Values {
+ return capabilities.filter((cap) => cap.default).map((cap) => cap.key);
+}
diff --git a/app/react/docker/containers/CreateView/CapabilitiesTab/types.ts b/app/react/docker/containers/CreateView/CapabilitiesTab/types.ts
new file mode 100644
index 000000000..843c2278a
--- /dev/null
+++ b/app/react/docker/containers/CreateView/CapabilitiesTab/types.ts
@@ -0,0 +1,185 @@
+export interface Capability {
+ key: string;
+ description: string;
+ default?: boolean;
+}
+
+const capDesc: Array = [
+ {
+ key: 'SETPCAP',
+ description: 'Modify process capabilities.',
+ default: true,
+ },
+ {
+ key: 'MKNOD',
+ description: 'Create special files using mknod(2).',
+ default: true,
+ },
+ {
+ key: 'AUDIT_WRITE',
+ description: 'Write records to kernel auditing log.',
+ default: true,
+ },
+ {
+ key: 'CHOWN',
+ description: 'Make arbitrary changes to file UIDs and GIDs (see chown(2)).',
+ default: true,
+ },
+ {
+ key: 'NET_RAW',
+ description: 'Use RAW and PACKET sockets.',
+ default: true,
+ },
+ {
+ key: 'DAC_OVERRIDE',
+ description: 'Bypass file read, write, and execute permission checks.',
+ default: true,
+ },
+ {
+ key: 'FOWNER',
+ description:
+ 'Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file.',
+ default: true,
+ },
+ {
+ key: 'FSETID',
+ description:
+ 'Don’t clear set-user-ID and set-group-ID permission bits when a file is modified.',
+ default: true,
+ },
+ {
+ key: 'KILL',
+ description: 'Bypass permission checks for sending signals.',
+ default: true,
+ },
+ {
+ key: 'SETGID',
+ description:
+ 'Make arbitrary manipulations of process GIDs and supplementary GID list.',
+ default: true,
+ },
+ {
+ key: 'SETUID',
+ description: 'Make arbitrary manipulations of process UIDs.',
+ default: true,
+ },
+ {
+ key: 'NET_BIND_SERVICE',
+ description:
+ 'Bind a socket to internet domain privileged ports (port numbers less than 1024).',
+ default: true,
+ },
+ {
+ key: 'SYS_CHROOT',
+ description: 'Use chroot(2), change root directory.',
+ default: true,
+ },
+ {
+ key: 'SETFCAP',
+ description: 'Set file capabilities.',
+ default: true,
+ },
+ {
+ key: 'SYS_MODULE',
+ description: 'Load and unload kernel modules.',
+ },
+ {
+ key: 'SYS_RAWIO',
+ description: 'Perform I/O port operations (iopl(2) and ioperm(2)).',
+ },
+ {
+ key: 'SYS_PACCT',
+ description: 'Use acct(2), switch process accounting on or off.',
+ },
+ {
+ key: 'SYS_ADMIN',
+ description: 'Perform a range of system administration operations.',
+ },
+ {
+ key: 'SYS_NICE',
+ description:
+ 'Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes.',
+ },
+ {
+ key: 'SYS_RESOURCE',
+ description: 'Override resource Limits.',
+ },
+ {
+ key: 'SYS_TIME',
+ description:
+ 'Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock.',
+ },
+ {
+ key: 'SYS_TTY_CONFIG',
+ description:
+ 'Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals.',
+ },
+ {
+ key: 'AUDIT_CONTROL',
+ description:
+ 'Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules.',
+ },
+ {
+ key: 'MAC_ADMIN',
+ description:
+ 'Allow MAC configuration or state changes. Implemented for the Smack LSM.',
+ },
+ {
+ key: 'MAC_OVERRIDE',
+ description:
+ 'Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM).',
+ },
+ {
+ key: 'NET_ADMIN',
+ description: 'Perform various network-related operations.',
+ },
+ {
+ key: 'SYSLOG',
+ description: 'Perform privileged syslog(2) operations.',
+ },
+ {
+ key: 'DAC_READ_SEARCH',
+ description:
+ 'Bypass file read permission checks and directory read and execute permission checks.',
+ },
+ {
+ key: 'LINUX_IMMUTABLE',
+ description: 'Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags.',
+ },
+ {
+ key: 'NET_BROADCAST',
+ description: 'Make socket broadcasts, and listen to multicasts.',
+ },
+ {
+ key: 'IPC_LOCK',
+ description: 'Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)).',
+ },
+ {
+ key: 'IPC_OWNER',
+ description:
+ 'Bypass permission checks for operations on System V IPC objects.',
+ },
+ {
+ key: 'SYS_PTRACE',
+ description: 'Trace arbitrary processes using ptrace(2).',
+ },
+ {
+ key: 'SYS_BOOT',
+ description:
+ 'Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution.',
+ },
+ {
+ key: 'LEASE',
+ description: 'Establish leases on arbitrary files (see fcntl(2)).',
+ },
+ {
+ key: 'WAKE_ALARM',
+ description: 'Trigger something that will wake up the system.',
+ },
+ {
+ key: 'BLOCK_SUSPEND',
+ description: 'Employ features that can block system suspend.',
+ },
+];
+
+export const capabilities = capDesc.sort((a, b) => (a.key < b.key ? -1 : 1));
diff --git a/app/react/docker/containers/CreateView/CapabilitiesTab/validation.ts b/app/react/docker/containers/CreateView/CapabilitiesTab/validation.ts
new file mode 100644
index 000000000..3d679fc20
--- /dev/null
+++ b/app/react/docker/containers/CreateView/CapabilitiesTab/validation.ts
@@ -0,0 +1,7 @@
+import { array, SchemaOf, string } from 'yup';
+
+import { Values } from './CapabilitiesTab';
+
+export function validation(): SchemaOf {
+ return array(string().default('')).default([]);
+}