mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(app): limit the docker API version supported by the frontend (#11855)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
This commit is contained in:
parent
4ba16f1b04
commit
6a8e6734f3
212 changed files with 4439 additions and 3281 deletions
33
app/docker/models/build.ts
Normal file
33
app/docker/models/build.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
type Data = {
|
||||
stream: string;
|
||||
errorDetail: { message: string };
|
||||
};
|
||||
|
||||
export class ImageBuildModel {
|
||||
hasError: boolean = false;
|
||||
|
||||
buildLogs: string[];
|
||||
|
||||
constructor(data: Data[]) {
|
||||
const buildLogs: string[] = [];
|
||||
|
||||
data.forEach((line) => {
|
||||
if (line.stream) {
|
||||
// convert unicode chars to readable chars
|
||||
const logLine = line.stream.replace(
|
||||
// eslint-disable-next-line no-control-regex
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
''
|
||||
);
|
||||
buildLogs.push(logLine);
|
||||
}
|
||||
|
||||
if (line.errorDetail) {
|
||||
buildLogs.push(line.errorDetail.message);
|
||||
this.hasError = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.buildLogs = buildLogs;
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
|
||||
function b64DecodeUnicode(str) {
|
||||
try {
|
||||
return decodeURIComponent(
|
||||
atob(str)
|
||||
.split('')
|
||||
.map(function (c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
})
|
||||
.join('')
|
||||
);
|
||||
} catch (err) {
|
||||
return atob(str);
|
||||
}
|
||||
}
|
||||
|
||||
export function ConfigViewModel(data) {
|
||||
this.Id = data.ID;
|
||||
this.CreatedAt = data.CreatedAt;
|
||||
this.UpdatedAt = data.UpdatedAt;
|
||||
this.Version = data.Version.Index;
|
||||
this.Name = data.Spec.Name;
|
||||
this.Labels = data.Spec.Labels;
|
||||
this.Data = b64DecodeUnicode(data.Spec.Data);
|
||||
|
||||
if (data.Portainer && data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
54
app/docker/models/config.ts
Normal file
54
app/docker/models/config.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { Config } from 'docker-types/generated/1.41';
|
||||
|
||||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
|
||||
export class ConfigViewModel implements IResource {
|
||||
Id: string;
|
||||
|
||||
CreatedAt: string;
|
||||
|
||||
UpdatedAt: string;
|
||||
|
||||
Version: number;
|
||||
|
||||
Name: string;
|
||||
|
||||
Labels: Record<string, string>;
|
||||
|
||||
Data: string;
|
||||
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
|
||||
constructor(data: PortainerResponse<Config>) {
|
||||
this.Id = data.ID || '';
|
||||
this.CreatedAt = data.CreatedAt || '';
|
||||
this.UpdatedAt = data.UpdatedAt || '';
|
||||
this.Version = data.Version?.Index || 0;
|
||||
this.Name = data.Spec?.Name || '';
|
||||
this.Labels = data.Spec?.Labels || {};
|
||||
this.Data = b64DecodeUnicode(data.Spec?.Data || '');
|
||||
|
||||
if (data.Portainer && data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(
|
||||
data.Portainer.ResourceControl
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function b64DecodeUnicode(str: string) {
|
||||
try {
|
||||
return decodeURIComponent(
|
||||
window
|
||||
.atob(str)
|
||||
.toString()
|
||||
.split('')
|
||||
.map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
|
||||
.join('')
|
||||
);
|
||||
} catch (err) {
|
||||
return window.atob(str);
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
|
||||
export function createStatus(statusText) {
|
||||
var status = _.toLower(statusText);
|
||||
|
||||
if (status.indexOf('paused') > -1) {
|
||||
return 'paused';
|
||||
} else if (status.indexOf('dead') > -1) {
|
||||
return 'dead';
|
||||
} else if (status.indexOf('created') > -1) {
|
||||
return 'created';
|
||||
} else if (status.indexOf('exited') > -1) {
|
||||
return 'stopped';
|
||||
} else if (status.indexOf('(healthy)') > -1) {
|
||||
return 'healthy';
|
||||
} else if (status.indexOf('(unhealthy)') > -1) {
|
||||
return 'unhealthy';
|
||||
} else if (status.indexOf('(health: starting)') > -1) {
|
||||
return 'starting';
|
||||
}
|
||||
return 'running';
|
||||
}
|
||||
|
||||
export function ContainerViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Status = createStatus(data.Status);
|
||||
this.State = data.State;
|
||||
this.Created = data.Created;
|
||||
this.Names = data.Names;
|
||||
// Unavailable in Docker < 1.10
|
||||
if (data.NetworkSettings && !_.isEmpty(data.NetworkSettings.Networks)) {
|
||||
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
||||
}
|
||||
this.NetworkSettings = data.NetworkSettings;
|
||||
this.Image = data.Image;
|
||||
this.ImageID = data.ImageID;
|
||||
this.Command = data.Command;
|
||||
this.Checked = false;
|
||||
this.Labels = data.Labels;
|
||||
if (this.Labels && this.Labels['com.docker.compose.project']) {
|
||||
this.StackName = this.Labels['com.docker.compose.project'];
|
||||
} else if (this.Labels && this.Labels['com.docker.stack.namespace']) {
|
||||
this.StackName = this.Labels['com.docker.stack.namespace'];
|
||||
}
|
||||
this.Mounts = data.Mounts;
|
||||
|
||||
this.IsPortainer = data.IsPortainer;
|
||||
|
||||
this.Ports = [];
|
||||
if (data.Ports) {
|
||||
for (var i = 0; i < data.Ports.length; ++i) {
|
||||
var p = data.Ports[i];
|
||||
if (p.PublicPort) {
|
||||
this.Ports.push({ host: p.IP, private: p.PrivatePort, public: p.PublicPort });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
if (data.Portainer.Agent && data.Portainer.Agent.NodeName) {
|
||||
this.NodeName = data.Portainer.Agent.NodeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function ContainerStatsViewModel(data) {
|
||||
this.read = data.read;
|
||||
this.preread = data.preread;
|
||||
if (data.memory_stats.privateworkingset !== undefined) {
|
||||
// Windows
|
||||
this.MemoryUsage = data.memory_stats.privateworkingset;
|
||||
this.MemoryCache = 0;
|
||||
this.NumProcs = data.num_procs;
|
||||
this.isWindows = true;
|
||||
} else {
|
||||
// Linux
|
||||
if (data.memory_stats.stats === undefined || data.memory_stats.usage === undefined) {
|
||||
this.MemoryUsage = this.MemoryCache = 0;
|
||||
} else {
|
||||
this.MemoryCache = 0;
|
||||
if (data.memory_stats.stats.cache !== undefined) {
|
||||
// cgroups v1
|
||||
this.MemoryCache = data.memory_stats.stats.cache;
|
||||
}
|
||||
this.MemoryUsage = data.memory_stats.usage - this.MemoryCache;
|
||||
}
|
||||
}
|
||||
this.PreviousCPUTotalUsage = data.precpu_stats.cpu_usage.total_usage;
|
||||
this.PreviousCPUSystemUsage = data.precpu_stats.system_cpu_usage;
|
||||
this.CurrentCPUTotalUsage = data.cpu_stats.cpu_usage.total_usage;
|
||||
this.CurrentCPUSystemUsage = data.cpu_stats.system_cpu_usage;
|
||||
this.CPUCores = 1;
|
||||
if (data.cpu_stats.cpu_usage.percpu_usage) {
|
||||
this.CPUCores = data.cpu_stats.cpu_usage.percpu_usage.length;
|
||||
} else {
|
||||
if (data.cpu_stats.online_cpus !== undefined) {
|
||||
this.CPUCores = data.cpu_stats.online_cpus;
|
||||
}
|
||||
}
|
||||
this.Networks = _.values(data.networks);
|
||||
if (data.blkio_stats !== undefined && data.blkio_stats.io_service_bytes_recursive !== null) {
|
||||
//TODO: take care of multiple block devices
|
||||
var readData = data.blkio_stats.io_service_bytes_recursive.find((d) => d.op === 'Read');
|
||||
if (readData === undefined) {
|
||||
// try the cgroups v2 version
|
||||
readData = data.blkio_stats.io_service_bytes_recursive.find((d) => d.op === 'read');
|
||||
}
|
||||
if (readData !== undefined) {
|
||||
this.BytesRead = readData.value;
|
||||
}
|
||||
var writeData = data.blkio_stats.io_service_bytes_recursive.find((d) => d.op === 'Write');
|
||||
if (writeData === undefined) {
|
||||
// try the cgroups v2 version
|
||||
writeData = data.blkio_stats.io_service_bytes_recursive.find((d) => d.op === 'write');
|
||||
}
|
||||
if (writeData !== undefined) {
|
||||
this.BytesWrite = writeData.value;
|
||||
}
|
||||
} else {
|
||||
//no IO related data is available
|
||||
this.noIOdata = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function ContainerDetailsViewModel(data) {
|
||||
this.Model = data;
|
||||
this.Id = data.Id;
|
||||
this.State = data.State;
|
||||
this.Created = data.Created;
|
||||
this.Name = data.Name;
|
||||
this.NetworkSettings = data.NetworkSettings;
|
||||
this.Args = data.Args;
|
||||
this.Image = data.Image;
|
||||
this.Config = data.Config;
|
||||
this.HostConfig = data.HostConfig;
|
||||
this.Mounts = data.Mounts;
|
||||
if (data.Portainer && data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
this.IsPortainer = data.IsPortainer;
|
||||
}
|
56
app/docker/models/containerDetails.ts
Normal file
56
app/docker/models/containerDetails.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
||||
import { ContainerDetailsResponse } from '@/react/docker/containers/queries/useContainer';
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
|
||||
export class ContainerDetailsViewModel
|
||||
implements IResource, Pick<PortainerResponse<unknown>, 'IsPortainer'>
|
||||
{
|
||||
Model: ContainerDetailsResponse;
|
||||
|
||||
Id: ContainerDetailsResponse['Id'];
|
||||
|
||||
State: ContainerDetailsResponse['State'];
|
||||
|
||||
Created: ContainerDetailsResponse['Created'];
|
||||
|
||||
Name: ContainerDetailsResponse['Name'];
|
||||
|
||||
NetworkSettings: ContainerDetailsResponse['NetworkSettings'];
|
||||
|
||||
Args: ContainerDetailsResponse['Args'];
|
||||
|
||||
Image: ContainerDetailsResponse['Image'];
|
||||
|
||||
Config: ContainerDetailsResponse['Config'];
|
||||
|
||||
HostConfig: ContainerDetailsResponse['HostConfig'];
|
||||
|
||||
Mounts: ContainerDetailsResponse['Mounts'];
|
||||
|
||||
// IResource
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
|
||||
// PortainerResponse
|
||||
IsPortainer?: ContainerDetailsResponse['IsPortainer'];
|
||||
|
||||
constructor(data: ContainerDetailsResponse) {
|
||||
this.Model = data;
|
||||
this.Id = data.Id;
|
||||
this.State = data.State;
|
||||
this.Created = data.Created;
|
||||
this.Name = data.Name;
|
||||
this.NetworkSettings = data.NetworkSettings;
|
||||
this.Args = data.Args;
|
||||
this.Image = data.Image;
|
||||
this.Config = data.Config;
|
||||
this.HostConfig = data.HostConfig;
|
||||
this.Mounts = data.Mounts;
|
||||
if (data.Portainer && data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(
|
||||
data.Portainer.ResourceControl
|
||||
);
|
||||
}
|
||||
this.IsPortainer = data.IsPortainer;
|
||||
}
|
||||
}
|
113
app/docker/models/containerStats.ts
Normal file
113
app/docker/models/containerStats.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
import { values } from 'lodash';
|
||||
|
||||
import { ContainerStats } from '@/react/docker/containers/queries/useContainerStats';
|
||||
import { ValueOf } from '@/types';
|
||||
|
||||
/**
|
||||
* This type is arbitrary and only defined based on what we use / observed from the API responses.
|
||||
*/
|
||||
export class ContainerStatsViewModel {
|
||||
read: string;
|
||||
|
||||
preread: string;
|
||||
|
||||
MemoryUsage: number;
|
||||
|
||||
MemoryCache: number = 0;
|
||||
|
||||
NumProcs: number = 0;
|
||||
|
||||
isWindows: boolean = false;
|
||||
|
||||
PreviousCPUTotalUsage: number;
|
||||
|
||||
PreviousCPUSystemUsage: number;
|
||||
|
||||
CurrentCPUTotalUsage: number;
|
||||
|
||||
CurrentCPUSystemUsage: number;
|
||||
|
||||
CPUCores: number;
|
||||
|
||||
Networks: ValueOf<NonNullable<ContainerStats['networks']>>[];
|
||||
|
||||
BytesRead: number = 0;
|
||||
|
||||
BytesWrite: number = 0;
|
||||
|
||||
noIOdata: boolean = false;
|
||||
|
||||
constructor(data: ContainerStats) {
|
||||
this.read = data.read || '';
|
||||
this.preread = data.preread || '';
|
||||
if (data?.memory_stats?.privateworkingset !== undefined) {
|
||||
// Windows
|
||||
this.MemoryUsage = data?.memory_stats?.privateworkingset;
|
||||
this.MemoryCache = 0;
|
||||
this.NumProcs = data.num_procs || 0;
|
||||
this.isWindows = true;
|
||||
}
|
||||
// Linux
|
||||
else if (
|
||||
data?.memory_stats?.stats === undefined ||
|
||||
data?.memory_stats?.usage === undefined
|
||||
) {
|
||||
this.MemoryUsage = 0;
|
||||
this.MemoryCache = 0;
|
||||
} else {
|
||||
this.MemoryCache = 0;
|
||||
if (data?.memory_stats?.stats?.cache !== undefined) {
|
||||
// cgroups v1
|
||||
this.MemoryCache = data.memory_stats.stats.cache;
|
||||
}
|
||||
this.MemoryUsage = data.memory_stats.usage - this.MemoryCache;
|
||||
}
|
||||
this.PreviousCPUTotalUsage =
|
||||
data?.precpu_stats?.cpu_usage?.total_usage || 0;
|
||||
this.PreviousCPUSystemUsage = data?.precpu_stats?.system_cpu_usage || 0;
|
||||
this.CurrentCPUTotalUsage = data?.cpu_stats?.cpu_usage?.total_usage || 0;
|
||||
this.CurrentCPUSystemUsage = data?.cpu_stats?.system_cpu_usage || 0;
|
||||
this.CPUCores = 1;
|
||||
|
||||
this.CPUCores =
|
||||
data?.cpu_stats?.cpu_usage?.percpu_usage?.length ??
|
||||
data?.cpu_stats?.online_cpus ??
|
||||
1;
|
||||
|
||||
this.Networks = values(data.networks);
|
||||
|
||||
if (
|
||||
data.blkio_stats !== undefined &&
|
||||
data.blkio_stats.io_service_bytes_recursive !== null
|
||||
) {
|
||||
// TODO: take care of multiple block devices
|
||||
let readData = data?.blkio_stats?.io_service_bytes_recursive?.find(
|
||||
(d) => d.op === 'Read'
|
||||
);
|
||||
if (readData === undefined) {
|
||||
// try the cgroups v2 version
|
||||
readData = data?.blkio_stats?.io_service_bytes_recursive?.find(
|
||||
(d) => d.op === 'read'
|
||||
);
|
||||
}
|
||||
if (readData !== undefined) {
|
||||
this.BytesRead = readData.value;
|
||||
}
|
||||
let writeData = data?.blkio_stats?.io_service_bytes_recursive?.find(
|
||||
(d) => d.op === 'Write'
|
||||
);
|
||||
if (writeData === undefined) {
|
||||
// try the cgroups v2 version
|
||||
writeData = data?.blkio_stats?.io_service_bytes_recursive?.find(
|
||||
(d) => d.op === 'write'
|
||||
);
|
||||
}
|
||||
if (writeData !== undefined) {
|
||||
this.BytesWrite = writeData.value;
|
||||
}
|
||||
} else {
|
||||
// no IO related data is available
|
||||
this.noIOdata = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
function createEventDetails(event) {
|
||||
var eventAttr = event.Actor.Attributes;
|
||||
var details = '';
|
||||
|
||||
var action = event.Action;
|
||||
var extra = '';
|
||||
var hasColon = action.indexOf(':');
|
||||
if (hasColon != -1) {
|
||||
extra = action.substring(hasColon);
|
||||
action = action.substring(0, hasColon);
|
||||
}
|
||||
|
||||
switch (event.Type) {
|
||||
case 'container':
|
||||
switch (action) {
|
||||
case 'stop':
|
||||
details = 'Container ' + eventAttr.name + ' stopped';
|
||||
break;
|
||||
case 'destroy':
|
||||
details = 'Container ' + eventAttr.name + ' deleted';
|
||||
break;
|
||||
case 'create':
|
||||
details = 'Container ' + eventAttr.name + ' created';
|
||||
break;
|
||||
case 'start':
|
||||
details = 'Container ' + eventAttr.name + ' started';
|
||||
break;
|
||||
case 'kill':
|
||||
details = 'Container ' + eventAttr.name + ' killed';
|
||||
break;
|
||||
case 'die':
|
||||
details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode;
|
||||
break;
|
||||
case 'commit':
|
||||
details = 'Container ' + eventAttr.name + ' committed';
|
||||
break;
|
||||
case 'restart':
|
||||
details = 'Container ' + eventAttr.name + ' restarted';
|
||||
break;
|
||||
case 'pause':
|
||||
details = 'Container ' + eventAttr.name + ' paused';
|
||||
break;
|
||||
case 'unpause':
|
||||
details = 'Container ' + eventAttr.name + ' unpaused';
|
||||
break;
|
||||
case 'attach':
|
||||
details = 'Container ' + eventAttr.name + ' attached';
|
||||
break;
|
||||
case 'detach':
|
||||
details = 'Container ' + eventAttr.name + ' detached';
|
||||
break;
|
||||
case 'copy':
|
||||
details = 'Container ' + eventAttr.name + ' copied';
|
||||
break;
|
||||
case 'export':
|
||||
details = 'Container ' + eventAttr.name + ' exported';
|
||||
break;
|
||||
case 'health_status':
|
||||
details = 'Container ' + eventAttr.name + ' executed health status';
|
||||
break;
|
||||
case 'oom':
|
||||
details = 'Container ' + eventAttr.name + ' goes in out of memory';
|
||||
break;
|
||||
case 'rename':
|
||||
details = 'Container ' + eventAttr.name + ' renamed';
|
||||
break;
|
||||
case 'resize':
|
||||
details = 'Container ' + eventAttr.name + ' resized';
|
||||
break;
|
||||
case 'top':
|
||||
details = 'Showed running processes for container ' + eventAttr.name;
|
||||
break;
|
||||
case 'update':
|
||||
details = 'Container ' + eventAttr.name + ' updated';
|
||||
break;
|
||||
case 'exec_create':
|
||||
details = 'Exec instance created';
|
||||
break;
|
||||
case 'exec_start':
|
||||
details = 'Exec instance started';
|
||||
break;
|
||||
case 'exec_die':
|
||||
details = 'Exec instance exited';
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
break;
|
||||
case 'image':
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
details = 'Image deleted';
|
||||
break;
|
||||
case 'import':
|
||||
details = 'Image ' + event.Actor.ID + ' imported';
|
||||
break;
|
||||
case 'load':
|
||||
details = 'Image ' + event.Actor.ID + ' loaded';
|
||||
break;
|
||||
case 'tag':
|
||||
details = 'New tag created for ' + eventAttr.name;
|
||||
break;
|
||||
case 'untag':
|
||||
details = 'Image untagged';
|
||||
break;
|
||||
case 'save':
|
||||
details = 'Image ' + event.Actor.ID + ' saved';
|
||||
break;
|
||||
case 'pull':
|
||||
details = 'Image ' + event.Actor.ID + ' pulled';
|
||||
break;
|
||||
case 'push':
|
||||
details = 'Image ' + event.Actor.ID + ' pushed';
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
break;
|
||||
case 'network':
|
||||
switch (action) {
|
||||
case 'create':
|
||||
details = 'Network ' + eventAttr.name + ' created';
|
||||
break;
|
||||
case 'destroy':
|
||||
details = 'Network ' + eventAttr.name + ' deleted';
|
||||
break;
|
||||
case 'remove':
|
||||
details = 'Network ' + eventAttr.name + ' removed';
|
||||
break;
|
||||
case 'connect':
|
||||
details = 'Container connected to ' + eventAttr.name + ' network';
|
||||
break;
|
||||
case 'disconnect':
|
||||
details = 'Container disconnected from ' + eventAttr.name + ' network';
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
break;
|
||||
case 'volume':
|
||||
switch (action) {
|
||||
case 'create':
|
||||
details = 'Volume ' + event.Actor.ID + ' created';
|
||||
break;
|
||||
case 'destroy':
|
||||
details = 'Volume ' + event.Actor.ID + ' deleted';
|
||||
break;
|
||||
case 'mount':
|
||||
details = 'Volume ' + event.Actor.ID + ' mounted';
|
||||
break;
|
||||
case 'unmount':
|
||||
details = 'Volume ' + event.Actor.ID + ' unmounted';
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
details = 'Unsupported event';
|
||||
}
|
||||
return details + extra;
|
||||
}
|
||||
|
||||
export function EventViewModel(data) {
|
||||
// Type, Action, Actor unavailable in Docker < 1.10
|
||||
this.Time = data.time;
|
||||
if (data.Type) {
|
||||
this.Type = data.Type;
|
||||
this.Details = createEventDetails(data);
|
||||
} else {
|
||||
this.Type = data.status;
|
||||
this.Details = data.from;
|
||||
}
|
||||
}
|
134
app/docker/models/event.ts
Normal file
134
app/docker/models/event.ts
Normal file
|
@ -0,0 +1,134 @@
|
|||
import { EventMessage } from 'docker-types/generated/1.41';
|
||||
|
||||
type EventType = NonNullable<EventMessage['Type']>;
|
||||
type Action = string;
|
||||
|
||||
type Attributes = {
|
||||
id: string;
|
||||
name: string;
|
||||
exitCode: string;
|
||||
};
|
||||
|
||||
type EventToTemplateMap = Record<EventType, ActionToTemplateMap>;
|
||||
type ActionToTemplateMap = Record<Action, TemplateBuilder>;
|
||||
type TemplateBuilder = (attr: Attributes) => string;
|
||||
|
||||
/**
|
||||
* {
|
||||
* [EventType]: {
|
||||
* [Action]: TemplateBuilder,
|
||||
* [Action]: TemplateBuilder
|
||||
* },
|
||||
* [EventType]: {
|
||||
* [Action]: TemplateBuilder,
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* EventType are known and defined by Docker specs
|
||||
* Action are unknown and specific for each EventType
|
||||
*/
|
||||
const templates: EventToTemplateMap = {
|
||||
builder: {},
|
||||
config: {},
|
||||
container: {
|
||||
stop: ({ name }) => `Container ${name} stopped`,
|
||||
destroy: ({ name }) => `Container ${name} deleted`,
|
||||
create: ({ name }) => `Container ${name} created`,
|
||||
start: ({ name }) => `Container ${name} started`,
|
||||
kill: ({ name }) => `Container ${name} killed`,
|
||||
die: ({ name, exitCode }) =>
|
||||
`Container ${name} exited with status code ${exitCode}`,
|
||||
commit: ({ name }) => `Container ${name} committed`,
|
||||
restart: ({ name }) => `Container ${name} restarted`,
|
||||
pause: ({ name }) => `Container ${name} paused`,
|
||||
unpause: ({ name }) => `Container ${name} unpaused`,
|
||||
attach: ({ name }) => `Container ${name} attached`,
|
||||
detach: ({ name }) => `Container ${name} detached`,
|
||||
copy: ({ name }) => `Container ${name} copied`,
|
||||
export: ({ name }) => `Container ${name} exported`,
|
||||
health_status: ({ name }) => `Container ${name} executed health status`,
|
||||
oom: ({ name }) => `Container ${name} goes in out of memory`,
|
||||
rename: ({ name }) => `Container ${name} renamed`,
|
||||
resize: ({ name }) => `Container ${name} resized`,
|
||||
top: ({ name }) => `Showed running processes for container ${name}`,
|
||||
update: ({ name }) => `Container ${name} updated`,
|
||||
exec_create: () => `Exec instance created`,
|
||||
exec_start: () => `Exec instance started`,
|
||||
exec_die: () => `Exec instance exited`,
|
||||
},
|
||||
daemon: {},
|
||||
image: {
|
||||
delete: () => `Image deleted`,
|
||||
import: ({ id }) => `Image ${id} imported`,
|
||||
load: ({ id }) => `Image ${id} loaded`,
|
||||
tag: ({ name }) => `New tag created for ${name}`,
|
||||
untag: () => `Image untagged`,
|
||||
save: ({ id }) => `Image ${id} saved`,
|
||||
pull: ({ id }) => `Image ${id} pulled`,
|
||||
push: ({ id }) => `Image ${id} pushed`,
|
||||
},
|
||||
network: {
|
||||
create: ({ name }) => `Network ${name} created`,
|
||||
destroy: ({ name }) => `Network ${name} deleted`,
|
||||
remove: ({ name }) => `Network ${name} removed`,
|
||||
connect: ({ name }) => `Container connected to ${name} network`,
|
||||
disconnect: ({ name }) => `Container disconnected from ${name} network`,
|
||||
prune: () => `Networks pruned`,
|
||||
},
|
||||
node: {},
|
||||
plugin: {},
|
||||
secret: {},
|
||||
service: {},
|
||||
volume: {
|
||||
create: ({ id }) => `Volume ${id} created`,
|
||||
destroy: ({ id }) => `Volume ${id} deleted`,
|
||||
mount: ({ id }) => `Volume ${id} mounted`,
|
||||
unmount: ({ id }) => `Volume ${id} unmounted`,
|
||||
},
|
||||
};
|
||||
|
||||
function createEventDetails(event: EventMessage) {
|
||||
const eventType = event.Type ?? '';
|
||||
|
||||
// An action can be `action:extra`
|
||||
// For example `docker exec -it CONTAINER sh`
|
||||
// Generates the action `exec_create: sh`
|
||||
let extra = '';
|
||||
let action = event.Action ?? '';
|
||||
const hasColon = action?.indexOf(':') ?? -1;
|
||||
if (hasColon !== -1) {
|
||||
extra = action?.substring(hasColon) ?? '';
|
||||
action = action?.substring(0, hasColon);
|
||||
}
|
||||
|
||||
const attr: Attributes = {
|
||||
id: event.Actor?.ID || '',
|
||||
name: event.Actor?.Attributes?.name || '',
|
||||
exitCode: event.Actor?.Attributes?.exitCode || '',
|
||||
};
|
||||
|
||||
// Event types are defined by the docker API specs
|
||||
// Each event has it own set of actions, which a unknown/not defined by specs
|
||||
// If the received event or action has no builder associated to it
|
||||
// We consider the event unsupported and we provide the raw data
|
||||
const detailsBuilder = templates[eventType as EventType]?.[action];
|
||||
const details = detailsBuilder
|
||||
? detailsBuilder(attr)
|
||||
: `Unsupported event: ${eventType} / ${action}`;
|
||||
|
||||
return details + extra;
|
||||
}
|
||||
|
||||
export class EventViewModel {
|
||||
Time: EventMessage['time'];
|
||||
|
||||
Type: EventMessage['Type'];
|
||||
|
||||
Details: string;
|
||||
|
||||
constructor(data: EventMessage) {
|
||||
this.Time = data.time;
|
||||
this.Type = data.Type;
|
||||
this.Details = createEventDetails(data);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
export function ImageViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Tag = data.Tag;
|
||||
this.Repository = data.Repository;
|
||||
this.Created = data.Created;
|
||||
this.Checked = false;
|
||||
this.RepoTags = data.RepoTags;
|
||||
if ((!this.RepoTags || this.RepoTags.length === 0) && data.RepoDigests) {
|
||||
this.RepoTags = [];
|
||||
for (var i = 0; i < data.RepoDigests.length; i++) {
|
||||
var digest = data.RepoDigests[i];
|
||||
var repository = digest.substring(0, digest.indexOf('@'));
|
||||
this.RepoTags.push(repository + ':<none>');
|
||||
}
|
||||
}
|
||||
|
||||
this.Size = data.Size;
|
||||
this.Used = data.Used;
|
||||
|
||||
if (data.Portainer && data.Portainer.Agent && data.Portainer.Agent.NodeName) {
|
||||
this.NodeName = data.Portainer.Agent.NodeName;
|
||||
}
|
||||
this.Labels = data.Labels;
|
||||
}
|
||||
|
||||
export function ImageBuildModel(data) {
|
||||
this.hasError = false;
|
||||
var buildLogs = [];
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var line = data[i];
|
||||
|
||||
if (line.stream) {
|
||||
line = line.stream.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
||||
buildLogs.push(line);
|
||||
}
|
||||
|
||||
if (line.errorDetail) {
|
||||
buildLogs.push(line.errorDetail.message);
|
||||
this.hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.buildLogs = buildLogs;
|
||||
}
|
47
app/docker/models/image.ts
Normal file
47
app/docker/models/image.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { ImageSummary } from 'docker-types/generated/1.41';
|
||||
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
|
||||
export type ImageId = ImageSummary['Id'];
|
||||
export type ImageName = string;
|
||||
|
||||
/**
|
||||
* Partial copy of ImageSummary
|
||||
*/
|
||||
export class ImageViewModel {
|
||||
Id: ImageId;
|
||||
|
||||
Created: ImageSummary['Created'];
|
||||
|
||||
RepoTags: ImageSummary['RepoTags'];
|
||||
|
||||
Size: ImageSummary['Size'];
|
||||
|
||||
Labels: ImageSummary['Labels'];
|
||||
|
||||
// internal
|
||||
|
||||
NodeName: string;
|
||||
|
||||
Used: boolean = false;
|
||||
|
||||
constructor(data: PortainerResponse<ImageSummary>, used: boolean = false) {
|
||||
this.Id = data.Id;
|
||||
// this.Tag = data.Tag; // doesn't seem to be used?
|
||||
// this.Repository = data.Repository; // doesn't seem to be used?
|
||||
this.Created = data.Created;
|
||||
this.RepoTags = data.RepoTags;
|
||||
if ((!this.RepoTags || this.RepoTags.length === 0) && data.RepoDigests) {
|
||||
this.RepoTags = [];
|
||||
data.RepoDigests.forEach((digest) => {
|
||||
const repository = digest.substring(0, digest.indexOf('@'));
|
||||
this.RepoTags.push(`${repository}:<none>`);
|
||||
});
|
||||
}
|
||||
|
||||
this.Size = data.Size;
|
||||
this.NodeName = data.Portainer?.Agent?.NodeName || '';
|
||||
this.Labels = data.Labels;
|
||||
this.Used = used;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
export function ImageDetailsViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Tag = data.Tag;
|
||||
this.Parent = data.Parent;
|
||||
this.Repository = data.Repository;
|
||||
this.Created = data.Created;
|
||||
this.Checked = false;
|
||||
this.RepoTags = data.RepoTags;
|
||||
this.Size = data.Size;
|
||||
this.DockerVersion = data.DockerVersion;
|
||||
this.Os = data.Os;
|
||||
this.Architecture = data.Architecture;
|
||||
this.Author = data.Author;
|
||||
this.Command = data.Config.Cmd;
|
||||
|
||||
let config = {};
|
||||
if (data.Config) {
|
||||
config = data.Config; // this is part of OCI images-spec
|
||||
} else if (data.ContainerConfig != null) {
|
||||
config = data.ContainerConfig; // not OCI ; has been removed in Docker 26 (API v1.45) along with .Container
|
||||
}
|
||||
this.Entrypoint = config.Entrypoint ? config.Entrypoint : '';
|
||||
this.ExposedPorts = config.ExposedPorts ? Object.keys(config.ExposedPorts) : [];
|
||||
this.Volumes = config.Volumes ? Object.keys(config.Volumes) : [];
|
||||
this.Env = config.Env ? config.Env : [];
|
||||
this.Labels = config.Labels;
|
||||
}
|
70
app/docker/models/imageDetails.ts
Normal file
70
app/docker/models/imageDetails.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { ImageInspect } from 'docker-types/generated/1.41';
|
||||
|
||||
type ImageInspectConfig = NonNullable<ImageInspect['Config']>;
|
||||
|
||||
export class ImageDetailsViewModel {
|
||||
Id: ImageInspect['Id'];
|
||||
|
||||
Parent: ImageInspect['Parent'];
|
||||
|
||||
Created: ImageInspect['Created'];
|
||||
|
||||
RepoTags: ImageInspect['RepoTags'];
|
||||
|
||||
Size: ImageInspect['Size'];
|
||||
|
||||
DockerVersion: ImageInspect['DockerVersion'];
|
||||
|
||||
Os: ImageInspect['Os'];
|
||||
|
||||
Architecture: ImageInspect['Architecture'];
|
||||
|
||||
Author: ImageInspect['Author'];
|
||||
|
||||
// Config sub fields
|
||||
|
||||
Command: ImageInspectConfig['Cmd'];
|
||||
|
||||
Entrypoint: Required<ImageInspectConfig['Entrypoint']>;
|
||||
|
||||
ExposedPorts: Required<ImageInspectConfig['ExposedPorts']>;
|
||||
|
||||
Volumes: Required<ImageInspectConfig>['Volumes'];
|
||||
|
||||
Env: Required<ImageInspectConfig>['Env'];
|
||||
|
||||
Labels: ImageInspectConfig['Labels'];
|
||||
|
||||
// computed fields
|
||||
|
||||
Used: boolean = false;
|
||||
|
||||
constructor(data: ImageInspect) {
|
||||
this.Id = data.Id;
|
||||
// this.Tag = data.Tag; // doesn't seem to be used?
|
||||
this.Parent = data.Parent;
|
||||
this.Created = data.Created;
|
||||
// this.Repository = data.Repository; // doesn't seem to be used?
|
||||
this.RepoTags = data.RepoTags;
|
||||
this.Size = data.Size;
|
||||
this.DockerVersion = data.DockerVersion;
|
||||
this.Os = data.Os;
|
||||
this.Architecture = data.Architecture;
|
||||
this.Author = data.Author;
|
||||
this.Command = data.Config?.Cmd;
|
||||
|
||||
let config: ImageInspect['Config'] = {};
|
||||
if (data.Config) {
|
||||
config = data.Config; // this is part of OCI images-spec
|
||||
} else if (data.ContainerConfig) {
|
||||
config = data.ContainerConfig; // not OCI ; has been removed in Docker 26 (API v1.45) along with .Container
|
||||
}
|
||||
this.Entrypoint = config.Entrypoint ?? [''];
|
||||
this.ExposedPorts = config.ExposedPorts
|
||||
? Object.keys(config.ExposedPorts)
|
||||
: [];
|
||||
this.Volumes = config.Volumes ? Object.keys(config.Volumes) : [];
|
||||
this.Env = config.Env ?? [];
|
||||
this.Labels = config.Labels;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
export function ImageLayerViewModel(order, data) {
|
||||
this.Order = order;
|
||||
this.Id = data.Id;
|
||||
this.Created = data.Created;
|
||||
this.CreatedBy = data.CreatedBy;
|
||||
this.Size = data.Size;
|
||||
this.Comment = data.Comment;
|
||||
this.Tags = data.Tags;
|
||||
}
|
27
app/docker/models/imageLayer.ts
Normal file
27
app/docker/models/imageLayer.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { ImageLayer } from '@/react/docker/proxy/queries/images/useImageHistory';
|
||||
|
||||
export class ImageLayerViewModel implements ImageLayer {
|
||||
Id: ImageLayer['Id'];
|
||||
|
||||
Created: ImageLayer['Created'];
|
||||
|
||||
CreatedBy: ImageLayer['CreatedBy'];
|
||||
|
||||
Size: ImageLayer['Size'];
|
||||
|
||||
Comment: ImageLayer['Comment'];
|
||||
|
||||
Tags: ImageLayer['Tags'];
|
||||
|
||||
constructor(
|
||||
public Order: number,
|
||||
data: ImageLayer
|
||||
) {
|
||||
this.Id = data.Id;
|
||||
this.Created = data.Created;
|
||||
this.CreatedBy = data.CreatedBy;
|
||||
this.Size = data.Size;
|
||||
this.Comment = data.Comment;
|
||||
this.Tags = data.Tags;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,20 @@ import { IPAM, Network, NetworkContainer } from 'docker-types/generated/1.41';
|
|||
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
||||
import { PortainerMetadata } from '@/react/docker/types';
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
|
||||
// TODO later: aggregate NetworkViewModel and DockerNetwork types
|
||||
//
|
||||
// type MacvlanNetwork = {
|
||||
// ConfigFrom?: { Network: string };
|
||||
// ConfigOnly?: boolean;
|
||||
// };
|
||||
//
|
||||
// type NetworkViewModel = Network & {
|
||||
// StackName?: string;
|
||||
// NodeName?: string;
|
||||
// ResourceControl?: ResourceControlViewModel;
|
||||
// } & MacvlanNetwork;
|
||||
|
||||
export class NetworkViewModel implements IResource {
|
||||
Id: string;
|
||||
|
@ -38,8 +51,7 @@ export class NetworkViewModel implements IResource {
|
|||
ResourceControl?: ResourceControlViewModel;
|
||||
|
||||
constructor(
|
||||
data: Network & {
|
||||
Portainer?: PortainerMetadata;
|
||||
data: PortainerResponse<Network> & {
|
||||
ConfigFrom?: { Network: string };
|
||||
ConfigOnly?: boolean;
|
||||
}
|
||||
|
|
|
@ -10,8 +10,6 @@ import {
|
|||
ResourceObject,
|
||||
} from 'docker-types/generated/1.41';
|
||||
|
||||
import { WithRequiredProperty } from '@/types';
|
||||
|
||||
export class NodeViewModel {
|
||||
Model: Node;
|
||||
|
||||
|
@ -55,7 +53,7 @@ export class NodeViewModel {
|
|||
|
||||
Status: NodeStatus['State'];
|
||||
|
||||
Addr: WithRequiredProperty<NodeStatus, 'Addr'>['Addr'] = '';
|
||||
Addr: Required<NodeStatus>['Addr'] = '';
|
||||
|
||||
Leader: ManagerStatus['Leader'];
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// This model is based on https://github.com/moby/moby/blob/0ac25dfc751fa4304ab45afd5cd8705c2235d101/api/types/plugin.go#L8-L31
|
||||
// instead of the official documentation.
|
||||
// See: https://github.com/moby/moby/issues/34241
|
||||
export function PluginViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Enabled = data.Enabled;
|
||||
this.Config = data.Config;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Secret } from 'docker-types/generated/1.41';
|
||||
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
import { PortainerMetadata } from '@/react/docker/types';
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
||||
|
||||
export class SecretViewModel implements IResource {
|
||||
|
@ -19,7 +19,7 @@ export class SecretViewModel implements IResource {
|
|||
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
|
||||
constructor(data: Secret & { Portainer?: PortainerMetadata }) {
|
||||
constructor(data: PortainerResponse<Secret>) {
|
||||
this.Id = data.ID || '';
|
||||
this.CreatedAt = data.CreatedAt || '';
|
||||
this.UpdatedAt = data.UpdatedAt || '';
|
||||
|
|
|
@ -9,15 +9,13 @@ import {
|
|||
} from 'docker-types/generated/1.41';
|
||||
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
import { PortainerMetadata } from '@/react/docker/types';
|
||||
import { WithRequiredProperty } from '@/types';
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
|
||||
import { TaskViewModel } from './task';
|
||||
|
||||
type ContainerSpec = WithRequiredProperty<
|
||||
TaskSpec,
|
||||
'ContainerSpec'
|
||||
>['ContainerSpec'];
|
||||
type ContainerSpec = Required<TaskSpec>['ContainerSpec'];
|
||||
|
||||
export type ServiceId = string;
|
||||
|
||||
export class ServiceViewModel {
|
||||
Model: Service;
|
||||
|
@ -140,7 +138,7 @@ export class ServiceViewModel {
|
|||
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
|
||||
constructor(data: Service & { Portainer?: PortainerMetadata }) {
|
||||
constructor(data: PortainerResponse<Service>) {
|
||||
this.Model = data;
|
||||
this.Id = data.ID || '';
|
||||
this.Tasks = [];
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export function SwarmViewModel(data) {
|
||||
this.Id = data.ID;
|
||||
}
|
|
@ -1,25 +1,27 @@
|
|||
import { Task, TaskSpec, TaskState } from 'docker-types/generated/1.41';
|
||||
import { Task } from 'docker-types/generated/1.41';
|
||||
|
||||
import { DeepPick } from '@/types/deepPick';
|
||||
|
||||
export class TaskViewModel {
|
||||
Id: string;
|
||||
Id: NonNullable<Task['ID']>;
|
||||
|
||||
Created: string;
|
||||
Created: NonNullable<Task['CreatedAt']>;
|
||||
|
||||
Updated: string;
|
||||
Updated: NonNullable<Task['UpdatedAt']>;
|
||||
|
||||
Slot: number;
|
||||
Slot: NonNullable<Task['Slot']>;
|
||||
|
||||
Spec?: TaskSpec;
|
||||
Spec?: Task['Spec'];
|
||||
|
||||
Status: Task['Status'];
|
||||
Status?: Task['Status'];
|
||||
|
||||
DesiredState: TaskState;
|
||||
DesiredState: NonNullable<Task['DesiredState']>;
|
||||
|
||||
ServiceId: string;
|
||||
ServiceId: NonNullable<Task['ServiceID']>;
|
||||
|
||||
NodeId: string;
|
||||
NodeId: NonNullable<Task['NodeID']>;
|
||||
|
||||
ContainerId: string = '';
|
||||
ContainerId: DeepPick<Task, 'Status.ContainerStatus.ContainerID'>;
|
||||
|
||||
constructor(data: Task) {
|
||||
this.Id = data.ID || '';
|
||||
|
|
|
@ -2,32 +2,32 @@ import { Volume } from 'docker-types/generated/1.41';
|
|||
|
||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
||||
import { PortainerMetadata } from '@/react/docker/types';
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
|
||||
export class VolumeViewModel implements IResource {
|
||||
Id: string;
|
||||
Id: Volume['Name'];
|
||||
|
||||
CreatedAt: string | undefined;
|
||||
CreatedAt?: Volume['CreatedAt'];
|
||||
|
||||
Driver: string;
|
||||
Driver: Volume['Driver'];
|
||||
|
||||
Options: Record<string, string>;
|
||||
Options: Volume['Options'];
|
||||
|
||||
Labels: Record<string, string>;
|
||||
Labels: Volume['Labels'];
|
||||
|
||||
StackName?: string;
|
||||
Mountpoint: Volume['Mountpoint'];
|
||||
|
||||
Mountpoint: string;
|
||||
// Portainer properties
|
||||
|
||||
ResourceId?: string;
|
||||
|
||||
NodeName?: string;
|
||||
|
||||
StackName?: string;
|
||||
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
|
||||
constructor(
|
||||
data: Volume & { Portainer?: PortainerMetadata; ResourceID?: string }
|
||||
) {
|
||||
constructor(data: PortainerResponse<Volume> & { ResourceID?: string }) {
|
||||
this.Id = data.Name;
|
||||
this.CreatedAt = data.CreatedAt;
|
||||
this.Driver = data.Driver;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue