mirror of https://github.com/portainer/portainer
feat(app): limit the docker API version supported by the frontend (#11855)
parent
4ba16f1b04
commit
6a8e6734f3
|
@ -122,6 +122,7 @@ func (handler *Handler) executeServiceWebhook(
|
|||
_ = rc.Close()
|
||||
}(rc)
|
||||
}
|
||||
|
||||
_, err = dockerClient.ServiceUpdate(context.Background(), resourceID, service.Version, service.Spec, serviceUpdateOptions)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -20,11 +20,16 @@ type postDockerfileRequest struct {
|
|||
}
|
||||
|
||||
// buildOperation inspects the "Content-Type" header to determine if it needs to alter the request.
|
||||
//
|
||||
// If the value of the header is empty, it means that a Dockerfile is posted via upload, the function
|
||||
// will extract the file content from the request body, tar it, and rewrite the body.
|
||||
// !! THIS IS ONLY TRUE WHEN THE UPLOADED DOCKERFILE FILE HAS NO EXTENSION (the generated file.type in the frontend will be empty)
|
||||
// If the Dockerfile is named like Dockerfile.yaml or has an internal type, a non-empty Content-Type header will be generated
|
||||
//
|
||||
// If the value of the header contains "application/json", it means that the content of a Dockerfile is posted
|
||||
// in the request payload as JSON, the function will create a new file called Dockerfile inside a tar archive and
|
||||
// rewrite the body of the request.
|
||||
//
|
||||
// In any other case, it will leave the request unaltered.
|
||||
func buildOperation(request *http.Request) error {
|
||||
contentTypeHeader := request.Header.Get("Content-Type")
|
||||
|
|
|
@ -84,11 +84,25 @@ func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, er
|
|||
return transport.ProxyDockerRequest(request)
|
||||
}
|
||||
|
||||
var prefixProxyFuncMap = map[string]func(*Transport, *http.Request, string) (*http.Response, error){
|
||||
"configs": (*Transport).proxyConfigRequest,
|
||||
"containers": (*Transport).proxyContainerRequest,
|
||||
"services": (*Transport).proxyServiceRequest,
|
||||
"volumes": (*Transport).proxyVolumeRequest,
|
||||
"networks": (*Transport).proxyNetworkRequest,
|
||||
"secrets": (*Transport).proxySecretRequest,
|
||||
"swarm": (*Transport).proxySwarmRequest,
|
||||
"nodes": (*Transport).proxyNodeRequest,
|
||||
"tasks": (*Transport).proxyTaskRequest,
|
||||
"build": (*Transport).proxyBuildRequest,
|
||||
"images": (*Transport).proxyImageRequest,
|
||||
"v2": (*Transport).proxyAgentRequest,
|
||||
}
|
||||
|
||||
// ProxyDockerRequest intercepts a Docker API request and apply logic based
|
||||
// on the requested operation.
|
||||
func (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Response, error) {
|
||||
requestPath := apiVersionRe.ReplaceAllString(request.URL.Path, "")
|
||||
request.URL.Path = requestPath
|
||||
unversionedPath := apiVersionRe.ReplaceAllString(request.URL.Path, "")
|
||||
|
||||
if transport.endpoint.Type == portainer.AgentOnDockerEnvironment || transport.endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
||||
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
||||
|
@ -100,34 +114,12 @@ func (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Res
|
|||
request.Header.Set(portainer.PortainerAgentSignatureHeader, signature)
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(requestPath, "/configs"):
|
||||
return transport.proxyConfigRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/containers"):
|
||||
return transport.proxyContainerRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/services"):
|
||||
return transport.proxyServiceRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/volumes"):
|
||||
return transport.proxyVolumeRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/networks"):
|
||||
return transport.proxyNetworkRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/secrets"):
|
||||
return transport.proxySecretRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/swarm"):
|
||||
return transport.proxySwarmRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/nodes"):
|
||||
return transport.proxyNodeRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/tasks"):
|
||||
return transport.proxyTaskRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/build"):
|
||||
return transport.proxyBuildRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/images"):
|
||||
return transport.proxyImageRequest(request)
|
||||
case strings.HasPrefix(requestPath, "/v2"):
|
||||
return transport.proxyAgentRequest(request)
|
||||
default:
|
||||
return transport.executeDockerRequest(request)
|
||||
prefix := strings.Split(strings.TrimPrefix(unversionedPath, "/"), "/")[0]
|
||||
|
||||
if proxyFunc := prefixProxyFuncMap[prefix]; proxyFunc != nil {
|
||||
return proxyFunc(transport, request, unversionedPath)
|
||||
}
|
||||
return transport.executeDockerRequest(request)
|
||||
}
|
||||
|
||||
func (transport *Transport) executeDockerRequest(request *http.Request) (*http.Response, error) {
|
||||
|
@ -144,8 +136,8 @@ func (transport *Transport) executeDockerRequest(request *http.Request) (*http.R
|
|||
return response, err
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyAgentRequest(r *http.Request) (*http.Response, error) {
|
||||
requestPath := strings.TrimPrefix(r.URL.Path, "/v2")
|
||||
func (transport *Transport) proxyAgentRequest(r *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := strings.TrimPrefix(unversionedPath, "/v2")
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(requestPath, "/browse"):
|
||||
|
@ -203,8 +195,10 @@ func (transport *Transport) proxyAgentRequest(r *http.Request) (*http.Response,
|
|||
return transport.executeDockerRequest(r)
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyConfigRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxyConfigRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/configs/create":
|
||||
return transport.decorateGenericResourceCreationOperation(request, configObjectIdentifier, portainer.ConfigResourceControl)
|
||||
|
||||
|
@ -225,8 +219,10 @@ func (transport *Transport) proxyConfigRequest(request *http.Request) (*http.Res
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyContainerRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxyContainerRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/containers/create":
|
||||
return transport.decorateContainerCreationOperation(request, containerObjectIdentifier, portainer.ContainerResourceControl)
|
||||
|
||||
|
@ -261,8 +257,10 @@ func (transport *Transport) proxyContainerRequest(request *http.Request) (*http.
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyServiceRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxyServiceRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/services/create":
|
||||
return transport.decorateServiceCreationOperation(request)
|
||||
|
||||
|
@ -292,8 +290,10 @@ func (transport *Transport) proxyServiceRequest(request *http.Request) (*http.Re
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyVolumeRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxyVolumeRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/volumes/create":
|
||||
return transport.decorateVolumeResourceCreationOperation(request, portainer.VolumeResourceControl)
|
||||
|
||||
|
@ -309,8 +309,10 @@ func (transport *Transport) proxyVolumeRequest(request *http.Request) (*http.Res
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyNetworkRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxyNetworkRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/networks/create":
|
||||
return transport.decorateGenericResourceCreationOperation(request, networkObjectIdentifier, portainer.NetworkResourceControl)
|
||||
|
||||
|
@ -330,8 +332,10 @@ func (transport *Transport) proxyNetworkRequest(request *http.Request) (*http.Re
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxySecretRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxySecretRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/secrets/create":
|
||||
return transport.decorateGenericResourceCreationOperation(request, secretObjectIdentifier, portainer.SecretResourceControl)
|
||||
|
||||
|
@ -351,8 +355,8 @@ func (transport *Transport) proxySecretRequest(request *http.Request) (*http.Res
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyNodeRequest(request *http.Request) (*http.Response, error) {
|
||||
requestPath := request.URL.Path
|
||||
func (transport *Transport) proxyNodeRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
// assume /nodes/{id}
|
||||
if path.Base(requestPath) != "nodes" {
|
||||
|
@ -362,8 +366,10 @@ func (transport *Transport) proxyNodeRequest(request *http.Request) (*http.Respo
|
|||
return transport.executeDockerRequest(request)
|
||||
}
|
||||
|
||||
func (transport *Transport) proxySwarmRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxySwarmRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/swarm":
|
||||
return transport.rewriteOperation(request, swarmInspectOperation)
|
||||
default:
|
||||
|
@ -372,8 +378,10 @@ func (transport *Transport) proxySwarmRequest(request *http.Request) (*http.Resp
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyTaskRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxyTaskRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/tasks":
|
||||
return transport.rewriteOperation(request, transport.taskListOperation)
|
||||
default:
|
||||
|
@ -382,7 +390,7 @@ func (transport *Transport) proxyTaskRequest(request *http.Request) (*http.Respo
|
|||
}
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyBuildRequest(request *http.Request) (*http.Response, error) {
|
||||
func (transport *Transport) proxyBuildRequest(request *http.Request, _ string) (*http.Response, error) {
|
||||
err := transport.updateDefaultGitBranch(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -408,8 +416,10 @@ func (transport *Transport) updateDefaultGitBranch(request *http.Request) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (transport *Transport) proxyImageRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
func (transport *Transport) proxyImageRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||
requestPath := unversionedPath
|
||||
|
||||
switch requestPath {
|
||||
case "/images/create":
|
||||
return transport.replaceRegistryAuthenticationHeader(request)
|
||||
default:
|
||||
|
|
|
@ -5,7 +5,6 @@ function ImageHelperFactory() {
|
|||
return {
|
||||
isValidTag,
|
||||
createImageConfigForContainer,
|
||||
getImagesNamesForDownload,
|
||||
removeDigestFromRepository,
|
||||
imageContainsURL,
|
||||
};
|
||||
|
@ -14,20 +13,6 @@ function ImageHelperFactory() {
|
|||
return tag.match(/^(?![\.\-])([a-zA-Z0-9\_\.\-])+$/g);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array<{tags: Array<string>; id: string;}>} images
|
||||
* @returns {{names: string[]}}}
|
||||
*/
|
||||
function getImagesNamesForDownload(images) {
|
||||
var names = images.map(function (image) {
|
||||
return image.tags[0] !== '<none>:<none>' ? image.tags[0] : image.id;
|
||||
});
|
||||
return {
|
||||
names,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {PorImageRegistryModel} registry
|
||||
|
|
|
@ -3,14 +3,6 @@ angular.module('portainer.docker').factory('VolumeHelper', [
|
|||
'use strict';
|
||||
var helper = {};
|
||||
|
||||
helper.createDriverOptions = function (optionArray) {
|
||||
var options = {};
|
||||
optionArray.forEach(function (option) {
|
||||
options[option.name] = option.value;
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
helper.isVolumeUsedByAService = function (volume, services) {
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
var service = services[i];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import { API_ENDPOINT_ENDPOINTS } from '@/constants';
|
||||
import { jsonObjectsToArrayHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('Build', [
|
||||
'$resource',
|
||||
function BuildFactory($resource) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/build',
|
||||
{},
|
||||
{
|
||||
buildImage: {
|
||||
method: 'POST',
|
||||
ignoreLoadingBar: true,
|
||||
transformResponse: jsonObjectsToArrayHandler,
|
||||
isArray: true,
|
||||
headers: { 'Content-Type': 'application/x-tar' },
|
||||
},
|
||||
buildImageOverride: {
|
||||
method: 'POST',
|
||||
ignoreLoadingBar: true,
|
||||
transformResponse: jsonObjectsToArrayHandler,
|
||||
isArray: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,14 +0,0 @@
|
|||
angular.module('portainer.docker').factory('Commit', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
function CommitFactory($resource, API_ENDPOINT_ENDPOINTS) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:environmentId/docker/commit',
|
||||
{},
|
||||
{
|
||||
commitContainer: { method: 'POST', params: { container: '@id', repo: '@repo' }, ignoreLoadingBar: true },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,19 +0,0 @@
|
|||
angular.module('portainer.docker').factory('Config', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
function ConfigFactory($resource, API_ENDPOINT_ENDPOINTS) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:environmentId/docker/configs/:id/:action',
|
||||
{
|
||||
environmentId: '@environmentId',
|
||||
},
|
||||
{
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
query: { method: 'GET', isArray: true },
|
||||
create: { method: 'POST', params: { action: 'create' }, ignoreLoadingBar: true },
|
||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,73 +0,0 @@
|
|||
import { genericHandler, logsHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('Container', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
function ContainerFactory($resource, API_ENDPOINT_ENDPOINTS) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:environmentId/docker/containers/:id/:action',
|
||||
{
|
||||
name: '@name',
|
||||
environmentId: '@environmentId',
|
||||
},
|
||||
{
|
||||
query: {
|
||||
method: 'GET',
|
||||
params: { all: 0, action: 'json', filters: '@filters' },
|
||||
isArray: true,
|
||||
},
|
||||
get: {
|
||||
method: 'GET',
|
||||
params: { action: 'json' },
|
||||
},
|
||||
logs: {
|
||||
method: 'GET',
|
||||
params: { id: '@id', action: 'logs' },
|
||||
ignoreLoadingBar: true,
|
||||
transformResponse: logsHandler,
|
||||
},
|
||||
stats: {
|
||||
method: 'GET',
|
||||
params: { id: '@id', stream: false, action: 'stats' },
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
top: {
|
||||
method: 'GET',
|
||||
params: { id: '@id', action: 'top' },
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
create: {
|
||||
method: 'POST',
|
||||
params: { action: 'create' },
|
||||
transformResponse: genericHandler,
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
exec: {
|
||||
method: 'POST',
|
||||
params: { id: '@id', action: 'exec' },
|
||||
transformResponse: genericHandler,
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
inspect: {
|
||||
method: 'GET',
|
||||
params: { id: '@id', action: 'json' },
|
||||
},
|
||||
update: {
|
||||
method: 'POST',
|
||||
params: { id: '@id', action: 'update' },
|
||||
},
|
||||
prune: {
|
||||
method: 'POST',
|
||||
params: { action: 'prune', filters: '@filters' },
|
||||
},
|
||||
resize: {
|
||||
method: 'POST',
|
||||
params: { id: '@id', action: 'resize', h: '@height', w: '@width' },
|
||||
transformResponse: genericHandler,
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,23 +0,0 @@
|
|||
import { genericHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('Exec', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
function ExecFactory($resource, API_ENDPOINT_ENDPOINTS) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:environmentId/docker/exec/:id/:action',
|
||||
{
|
||||
environmentId: '@environmentId',
|
||||
},
|
||||
{
|
||||
resize: {
|
||||
method: 'POST',
|
||||
params: { id: '@id', action: 'resize', h: '@height', w: '@width' },
|
||||
transformResponse: genericHandler,
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,57 +0,0 @@
|
|||
import { deleteImageHandler, jsonObjectsToArrayHandler } from './response/handlers';
|
||||
import { imageGetResponse } from './response/image';
|
||||
|
||||
angular.module('portainer.docker').factory('Image', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
'HttpRequestHelper',
|
||||
function ImageFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper) {
|
||||
'use strict';
|
||||
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/images/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
query: { method: 'GET', params: { all: 0, action: 'json' }, isArray: true },
|
||||
get: { method: 'GET', params: { action: 'json' } },
|
||||
search: { method: 'GET', params: { action: 'search' } },
|
||||
history: { method: 'GET', params: { action: 'history' }, isArray: true },
|
||||
insert: { method: 'POST', params: { id: '@id', action: 'insert' } },
|
||||
tag: { method: 'POST', params: { id: '@id', action: 'tag', force: 0, repo: '@repo' }, ignoreLoadingBar: true },
|
||||
inspect: { method: 'GET', params: { id: '@id', action: 'json' } },
|
||||
push: {
|
||||
method: 'POST',
|
||||
params: { action: 'push', id: '@imageName' },
|
||||
isArray: true,
|
||||
transformResponse: jsonObjectsToArrayHandler,
|
||||
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
create: {
|
||||
method: 'POST',
|
||||
params: { action: 'create', fromImage: '@fromImage' },
|
||||
isArray: true,
|
||||
transformResponse: jsonObjectsToArrayHandler,
|
||||
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
download: {
|
||||
method: 'GET',
|
||||
params: { action: 'get', names: '@names' },
|
||||
transformResponse: imageGetResponse,
|
||||
responseType: 'blob',
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
remove: {
|
||||
method: 'DELETE',
|
||||
params: { id: '@id', force: '@force' },
|
||||
isArray: true,
|
||||
transformResponse: deleteImageHandler,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,44 +0,0 @@
|
|||
import { genericHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('Network', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function NetworkFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/networks/:id/:action',
|
||||
{
|
||||
id: '@id',
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
query: {
|
||||
method: 'GET',
|
||||
isArray: true,
|
||||
},
|
||||
get: {
|
||||
method: 'GET',
|
||||
},
|
||||
create: {
|
||||
method: 'POST',
|
||||
params: { action: 'create' },
|
||||
transformResponse: genericHandler,
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
remove: {
|
||||
method: 'DELETE',
|
||||
transformResponse: genericHandler,
|
||||
},
|
||||
connect: {
|
||||
method: 'POST',
|
||||
params: { action: 'connect' },
|
||||
},
|
||||
disconnect: {
|
||||
method: 'POST',
|
||||
params: { action: 'disconnect' },
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,20 +0,0 @@
|
|||
angular.module('portainer.docker').factory('Node', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function NodeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/nodes/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
query: { method: 'GET', isArray: true },
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
update: { method: 'POST', params: { id: '@id', action: 'update', version: '@version' } },
|
||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,17 +0,0 @@
|
|||
angular.module('portainer.docker').factory('Plugin', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function PluginFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/plugins/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
query: { method: 'GET', isArray: true },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,81 +0,0 @@
|
|||
function isJSONArray(jsonString) {
|
||||
return Object.prototype.toString.call(jsonString) === '[object Array]';
|
||||
}
|
||||
|
||||
function isJSON(jsonString) {
|
||||
try {
|
||||
var o = JSON.parse(jsonString);
|
||||
if (o && typeof o === 'object') {
|
||||
return o;
|
||||
}
|
||||
} catch (e) {
|
||||
//empty
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The Docker API often returns a list of JSON object.
|
||||
// This handler wrap the JSON objects in an array.
|
||||
// Used by the API in: Image push, Image create, Events query.
|
||||
export function jsonObjectsToArrayHandler(data) {
|
||||
// catching empty data helps the function not to fail and prevents unwanted error message to user.
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
var str = '[' + data.replace(/\n/g, ' ').replace(/\}\s*\{/g, '}, {') + ']';
|
||||
return angular.fromJson(str);
|
||||
}
|
||||
|
||||
// The Docker API often returns an empty string or a valid JSON object on success (Docker 1.9 -> Docker 1.12).
|
||||
// On error, it returns either an error message as a string (Docker < 1.12) or a JSON object with the field message
|
||||
// container the error (Docker = 1.12)
|
||||
// This handler ensure a valid JSON object is returned in any case.
|
||||
// Used by the API in: container deletion, network deletion, network creation, volume creation,
|
||||
// container exec, exec resize.
|
||||
export function genericHandler(data) {
|
||||
var response = {};
|
||||
// No data is returned when deletion is successful (Docker 1.9 -> 1.12)
|
||||
if (!data) {
|
||||
return response;
|
||||
}
|
||||
// A string is returned on failure (Docker < 1.12)
|
||||
else if (!isJSON(data)) {
|
||||
response.message = data;
|
||||
}
|
||||
// Docker 1.12 returns a valid JSON object when an error occurs
|
||||
else {
|
||||
response = angular.fromJson(data);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// The Docker API returns the logs as a single string.
|
||||
// This handler wraps the data in a JSON object under the "logs" property.
|
||||
export function logsHandler(data) {
|
||||
return {
|
||||
logs: data,
|
||||
};
|
||||
}
|
||||
|
||||
// Image delete API returns an array on success (Docker 1.9 -> Docker 1.12).
|
||||
// On error, it returns either an error message as a string (Docker < 1.12) or a JSON object with the field message
|
||||
// container the error (Docker = 1.12).
|
||||
// This handler returns the original array on success or a newly created array containing
|
||||
// only one JSON object with the field message filled with the error message on failure.
|
||||
export function deleteImageHandler(data) {
|
||||
// A string is returned on failure (Docker < 1.12)
|
||||
var response = [];
|
||||
if (!isJSON(data)) {
|
||||
response.push({ message: data });
|
||||
}
|
||||
// A JSON object is returned on failure (Docker = 1.12)
|
||||
else if (!isJSONArray(data)) {
|
||||
var json = angular.fromJson(data);
|
||||
response.push(json);
|
||||
}
|
||||
// An array is returned on success (Docker 1.9 -> 1.12)
|
||||
else {
|
||||
response = angular.fromJson(data);
|
||||
}
|
||||
return response;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// The get action of the Image service returns a file.
|
||||
// ngResource will transform it as an array of chars.
|
||||
// This functions simply creates a response object and assign
|
||||
// the data to a field.
|
||||
export function imageGetResponse(data) {
|
||||
var response = {};
|
||||
response.file = data;
|
||||
return response;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
angular.module('portainer.docker').factory('Secret', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function SecretFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/secrets/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
query: { method: 'GET', isArray: true },
|
||||
create: { method: 'POST', params: { action: 'create' }, ignoreLoadingBar: true },
|
||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,45 +0,0 @@
|
|||
import { logsHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('Service', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
'HttpRequestHelper',
|
||||
function ServiceFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/services/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
query: { method: 'GET', isArray: true, params: { filters: '@filters' } },
|
||||
create: {
|
||||
method: 'POST',
|
||||
params: { action: 'create' },
|
||||
headers: {
|
||||
'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader,
|
||||
version: '1.29',
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
},
|
||||
update: {
|
||||
method: 'POST',
|
||||
params: { id: '@id', action: 'update', version: '@version', rollback: '@rollback' },
|
||||
headers: {
|
||||
'X-Registry-Auth': (config) => btoa(JSON.stringify({ registryId: config.data.registryId })),
|
||||
version: '1.29',
|
||||
},
|
||||
},
|
||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
||||
logs: {
|
||||
method: 'GET',
|
||||
params: { id: '@id', action: 'logs' },
|
||||
ignoreLoadingBar: true,
|
||||
transformResponse: logsHandler,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,17 +0,0 @@
|
|||
angular.module('portainer.docker').factory('Swarm', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function SwarmFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/swarm',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
get: { method: 'GET' },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,32 +0,0 @@
|
|||
import { jsonObjectsToArrayHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('System', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function SystemFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/:action/:subAction',
|
||||
{
|
||||
name: '@name',
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
info: {
|
||||
method: 'GET',
|
||||
params: { action: 'info' },
|
||||
},
|
||||
version: { method: 'GET', params: { action: 'version' } },
|
||||
events: {
|
||||
method: 'GET',
|
||||
params: { action: 'events', since: '@since', until: '@until' },
|
||||
isArray: true,
|
||||
transformResponse: jsonObjectsToArrayHandler,
|
||||
},
|
||||
auth: { method: 'POST', params: { action: 'auth' } },
|
||||
dataUsage: { method: 'GET', params: { action: 'system', subAction: 'df' } },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,26 +0,0 @@
|
|||
import { logsHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('Task', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function TaskFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/tasks/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
query: { method: 'GET', isArray: true, params: { filters: '@filters' } },
|
||||
logs: {
|
||||
method: 'GET',
|
||||
params: { id: '@id', action: 'logs' },
|
||||
ignoreLoadingBar: true,
|
||||
transformResponse: logsHandler,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,37 +0,0 @@
|
|||
import { genericHandler } from './response/handlers';
|
||||
|
||||
angular.module('portainer.docker').factory('Volume', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_ENDPOINTS',
|
||||
'EndpointProvider',
|
||||
function VolumeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||
'use strict';
|
||||
|
||||
function addVolumeNameToHeader(config) {
|
||||
return config.data.Name || '';
|
||||
}
|
||||
|
||||
return $resource(
|
||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/volumes/:id/:action',
|
||||
{
|
||||
endpointId: EndpointProvider.endpointID,
|
||||
},
|
||||
{
|
||||
query: { method: 'GET' },
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
create: {
|
||||
method: 'POST',
|
||||
params: { action: 'create' },
|
||||
transformResponse: genericHandler,
|
||||
ignoreLoadingBar: true,
|
||||
headers: { 'X-Portainer-VolumeName': addVolumeNameToHeader },
|
||||
},
|
||||
remove: {
|
||||
method: 'DELETE',
|
||||
transformResponse: genericHandler,
|
||||
params: { id: '@id' },
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -1,91 +1,65 @@
|
|||
import { ImageBuildModel } from '../models/image';
|
||||
import {
|
||||
buildImageFromDockerfileContent,
|
||||
buildImageFromDockerfileContentAndFiles,
|
||||
buildImageFromURL,
|
||||
buildImageFromUpload,
|
||||
} from '@/react/docker/images/queries/useBuildImageMutation';
|
||||
|
||||
angular.module('portainer.docker').factory('BuildService', [
|
||||
'$q',
|
||||
'Build',
|
||||
'FileUploadService',
|
||||
function BuildServiceFactory($q, Build, FileUploadService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
import { ImageBuildModel } from '../models/build';
|
||||
|
||||
service.buildImageFromUpload = function (endpointID, names, file, path) {
|
||||
var deferred = $q.defer();
|
||||
angular.module('portainer.docker').factory('BuildService', BuildServiceFactory);
|
||||
|
||||
FileUploadService.buildImage(endpointID, names, file, path)
|
||||
.then(function success(response) {
|
||||
var model = new ImageBuildModel(response.data);
|
||||
deferred.resolve(model);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
/* @ngInject */
|
||||
function BuildServiceFactory(AngularToReact) {
|
||||
const { useAxios } = AngularToReact;
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
return {
|
||||
buildImageFromUpload: useAxios(buildImageFromUploadAngularJS), // build image
|
||||
buildImageFromURL: useAxios(buildImageFromURLAngularJS), // build image
|
||||
buildImageFromDockerfileContent: useAxios(buildImageFromDockerfileContentAngularJS), // build image
|
||||
buildImageFromDockerfileContentAndFiles: useAxios(buildImageFromDockerfileContentAndFilesAngularJS), // build image
|
||||
};
|
||||
|
||||
service.buildImageFromURL = function (endpointId, names, url, path) {
|
||||
var params = {
|
||||
endpointId,
|
||||
t: names,
|
||||
remote: url,
|
||||
dockerfile: path,
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {string[]} names
|
||||
* @param {File} file
|
||||
* @param {string} path
|
||||
*/
|
||||
async function buildImageFromUploadAngularJS(environmentId, names, file, path) {
|
||||
const data = await buildImageFromUpload(environmentId, names, file, path);
|
||||
return new ImageBuildModel(data);
|
||||
}
|
||||
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {string[]} names
|
||||
* @param {string} url
|
||||
* @param {string} path
|
||||
*/
|
||||
async function buildImageFromURLAngularJS(environmentId, names, url, path) {
|
||||
const data = await buildImageFromURL(environmentId, names, url, path);
|
||||
return new ImageBuildModel(data);
|
||||
}
|
||||
|
||||
Build.buildImage(params, {})
|
||||
.$promise.then(function success(data) {
|
||||
var model = new ImageBuildModel(data);
|
||||
deferred.resolve(model);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {string[]} names
|
||||
* @param {string} content
|
||||
*/
|
||||
async function buildImageFromDockerfileContentAngularJS(environmentId, names, content) {
|
||||
const data = await buildImageFromDockerfileContent(environmentId, names, content);
|
||||
return new ImageBuildModel(data);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.buildImageFromDockerfileContent = function (endpointId, names, content) {
|
||||
var params = {
|
||||
endpointId,
|
||||
t: names,
|
||||
};
|
||||
var payload = {
|
||||
content: content,
|
||||
};
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
Build.buildImageOverride(params, payload)
|
||||
.$promise.then(function success(data) {
|
||||
var model = new ImageBuildModel(data);
|
||||
deferred.resolve(model);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.buildImageFromDockerfileContentAndFiles = function (endpointID, names, content, files) {
|
||||
var dockerfile = new Blob([content], { type: 'text/plain' });
|
||||
var uploadFiles = [dockerfile].concat(files);
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
FileUploadService.buildImageFromFiles(endpointID, names, uploadFiles)
|
||||
.then(function success(response) {
|
||||
var model = new ImageBuildModel(response.data);
|
||||
deferred.resolve(model);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {string[]} names
|
||||
* @param {string} content
|
||||
* @param {File[]} files
|
||||
*/
|
||||
async function buildImageFromDockerfileContentAndFilesAngularJS(environmentId, names, content, files) {
|
||||
const data = await buildImageFromDockerfileContentAndFiles(environmentId, names, content, files);
|
||||
return new ImageBuildModel(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,37 @@
|
|||
import { getConfig } from '@/react/docker/configs/queries/useConfig';
|
||||
import { getConfigs } from '@/react/docker/configs/queries/useConfigs';
|
||||
|
||||
import { deleteConfig } from '@/react/docker/configs/queries/useDeleteConfigMutation';
|
||||
import { createConfig } from '@/react/docker/configs/queries/useCreateConfigMutation';
|
||||
import { ConfigViewModel } from '../models/config';
|
||||
|
||||
angular.module('portainer.docker').factory('ConfigService', [
|
||||
'$q',
|
||||
'Config',
|
||||
function ConfigServiceFactory($q, Config) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('ConfigService', ConfigServiceFactory);
|
||||
|
||||
service.config = function (environmentId, configId) {
|
||||
var deferred = $q.defer();
|
||||
/* @ngInspect */
|
||||
function ConfigServiceFactory(AngularToReact) {
|
||||
const { useAxios } = AngularToReact;
|
||||
|
||||
Config.get({ id: configId, environmentId })
|
||||
.$promise.then(function success(data) {
|
||||
var config = new ConfigViewModel(data);
|
||||
deferred.resolve(config);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve config details', err: err });
|
||||
});
|
||||
return {
|
||||
configs: useAxios(listConfigsAngularJS), // config list + service create + service edit
|
||||
config: useAxios(getConfigAngularJS), // config create + config edit
|
||||
remove: useAxios(deleteConfig), // config list + config edit
|
||||
create: useAxios(createConfig), // config create
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
*/
|
||||
async function listConfigsAngularJS(environmentId) {
|
||||
const data = await getConfigs(environmentId);
|
||||
return data.map((c) => new ConfigViewModel(c));
|
||||
}
|
||||
|
||||
service.configs = function (environmentId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Config.query({ environmentId })
|
||||
.$promise.then(function success(data) {
|
||||
var configs = data.map(function (item) {
|
||||
return new ConfigViewModel(item);
|
||||
});
|
||||
deferred.resolve(configs);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve configs', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function (environmentId, configId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Config.remove({ environmentId, id: configId })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove config', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.create = function (environmentId, config) {
|
||||
return Config.create({ environmentId }, config).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {ConfigId} configId
|
||||
*/
|
||||
async function getConfigAngularJS(environmentId, configId) {
|
||||
const data = await getConfig(environmentId, configId);
|
||||
return new ConfigViewModel(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,178 +9,110 @@ import {
|
|||
startContainer,
|
||||
stopContainer,
|
||||
recreateContainer,
|
||||
getContainerLogs,
|
||||
} from '@/react/docker/containers/containers.service';
|
||||
import { ContainerDetailsViewModel, ContainerStatsViewModel, ContainerViewModel } from '../models/container';
|
||||
import { getContainers } from '@/react/docker/containers/queries/useContainers';
|
||||
import { getContainer } from '@/react/docker/containers/queries/useContainer';
|
||||
import { resizeTTY } from '@/react/docker/containers/queries/useContainerResizeTTYMutation';
|
||||
import { updateContainer } from '@/react/docker/containers/queries/useUpdateContainer';
|
||||
import { createExec } from '@/react/docker/containers/queries/useCreateExecMutation';
|
||||
import { containerStats } from '@/react/docker/containers/queries/useContainerStats';
|
||||
import { containerTop } from '@/react/docker/containers/queries/useContainerTop';
|
||||
|
||||
import { ContainerDetailsViewModel } from '../models/containerDetails';
|
||||
import { ContainerStatsViewModel } from '../models/containerStats';
|
||||
import { formatLogs } from '../helpers/logHelper';
|
||||
|
||||
angular.module('portainer.docker').factory('ContainerService', ContainerServiceFactory);
|
||||
|
||||
/* @ngInject */
|
||||
function ContainerServiceFactory($q, Container, $timeout) {
|
||||
const service = {
|
||||
killContainer,
|
||||
pauseContainer,
|
||||
renameContainer,
|
||||
restartContainer,
|
||||
resumeContainer,
|
||||
startContainer,
|
||||
stopContainer,
|
||||
recreateContainer,
|
||||
remove: removeContainer,
|
||||
updateRestartPolicy,
|
||||
updateLimits,
|
||||
function ContainerServiceFactory(AngularToReact) {
|
||||
const { useAxios } = AngularToReact;
|
||||
|
||||
return {
|
||||
killContainer: useAxios(killContainer), // container edit
|
||||
pauseContainer: useAxios(pauseContainer), // container edit
|
||||
renameContainer: useAxios(renameContainer), // container edit
|
||||
restartContainer: useAxios(restartContainer), // container edit
|
||||
resumeContainer: useAxios(resumeContainer), // container edit
|
||||
startContainer: useAxios(startContainer), // container edit
|
||||
stopContainer: useAxios(stopContainer), // container edit
|
||||
recreateContainer: useAxios(recreateContainer), // container edit
|
||||
remove: useAxios(removeContainer), // container edit
|
||||
container: useAxios(getContainerAngularJS), // container console + container edit + container stats
|
||||
containers: useAxios(getContainers), // dashboard + services list + service edit + voluem edit + stackservice + stack create + stack edit
|
||||
resizeTTY: useAxios(resizeTTYAngularJS), // container console
|
||||
updateRestartPolicy: useAxios(updateRestartPolicyAngularJS), // container edit
|
||||
createExec: useAxios(createExec), // container console
|
||||
containerStats: useAxios(containerStatsAngularJS), // container stats
|
||||
containerTop: useAxios(containerTop), // container stats
|
||||
inspect: useAxios(getContainer), // container inspect
|
||||
logs: useAxios(containerLogsAngularJS), // container logs
|
||||
};
|
||||
|
||||
service.container = function (environmentId, id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Container.get({ environmentId, id })
|
||||
.$promise.then(function success(data) {
|
||||
var container = new ContainerDetailsViewModel(data);
|
||||
deferred.resolve(container);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve container information', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.containers = function (environmentId, all, filters) {
|
||||
var deferred = $q.defer();
|
||||
Container.query({ environmentId, all, filters })
|
||||
.$promise.then(function success(data) {
|
||||
var containers = data.map(function (item) {
|
||||
return new ContainerViewModel(item);
|
||||
});
|
||||
deferred.resolve(containers);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve containers', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.resizeTTY = function (environmentId, id, width, height, timeout) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$timeout(function () {
|
||||
Container.resize({}, { environmentId, id, width, height })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: 'Unable to resize tty of container ' + id, err: data.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to resize tty of container ' + id, err: err });
|
||||
});
|
||||
}, timeout);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function updateRestartPolicy(environmentId, id, restartPolicy, maximumRetryCounts) {
|
||||
return Container.update({ environmentId, id }, { RestartPolicy: { Name: restartPolicy, MaximumRetryCount: maximumRetryCounts } }).$promise;
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {ContainerId} id
|
||||
* @param {*} param2
|
||||
*/
|
||||
async function getContainerAngularJS(environmentId, id, { nodeName } = {}) {
|
||||
const data = await getContainer(environmentId, id, { nodeName });
|
||||
return new ContainerDetailsViewModel(data);
|
||||
}
|
||||
|
||||
function updateLimits(environmentId, id, config) {
|
||||
return Container.update(
|
||||
{ environmentId, id },
|
||||
{
|
||||
// MemorySwap: must be set
|
||||
// -1: non limits, 0: treated as unset(cause update error).
|
||||
MemoryReservation: config.HostConfig.MemoryReservation,
|
||||
Memory: config.HostConfig.Memory,
|
||||
MemorySwap: -1,
|
||||
NanoCpus: config.HostConfig.NanoCpus,
|
||||
}
|
||||
).$promise;
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {string} containerId
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
* @param timeout DEPRECATED: Previously used in pure AJS implementation
|
||||
*/
|
||||
async function resizeTTYAngularJS(environmentId, containerId, width, height) {
|
||||
return resizeTTY(environmentId, containerId, { width, height });
|
||||
}
|
||||
|
||||
service.createContainer = function (environmentId, configuration) {
|
||||
var deferred = $q.defer();
|
||||
Container.create({ environmentId }, configuration)
|
||||
.$promise.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create container', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {ContainerId} id
|
||||
* @param {RestartPolicy['Name']} restartPolicy
|
||||
* @param {RestartPolicy['MaximumRetryCount']} maximumRetryCounts
|
||||
*/
|
||||
async function updateRestartPolicyAngularJS(environmentId, id, restartPolicy, maximumRetryCounts) {
|
||||
return updateContainer(environmentId, id, {
|
||||
RestartPolicy: {
|
||||
Name: restartPolicy,
|
||||
MaximumRetryCount: maximumRetryCounts,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
service.createExec = function (environmentId, execConfig) {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {ContainerId} id
|
||||
*/
|
||||
async function containerStatsAngularJS(environmentId, id) {
|
||||
const data = await containerStats(environmentId, id);
|
||||
return new ContainerStatsViewModel(data);
|
||||
}
|
||||
|
||||
Container.exec({ environmentId }, execConfig)
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message, err: data.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.logs = function (environmentId, id, stdout, stderr, timestamps, since, tail, stripHeaders) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var parameters = {
|
||||
id: id,
|
||||
stdout: stdout || 0,
|
||||
stderr: stderr || 0,
|
||||
timestamps: timestamps || 0,
|
||||
since: since || 0,
|
||||
tail: tail || 'all',
|
||||
environmentId,
|
||||
};
|
||||
|
||||
Container.logs(parameters)
|
||||
.$promise.then(function success(data) {
|
||||
var logs = formatLogs(data.logs, { stripHeaders, withTimestamps: !!timestamps });
|
||||
deferred.resolve(logs);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.containerStats = function (environmentId, id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Container.stats({ environmentId, id })
|
||||
.$promise.then(function success(data) {
|
||||
var containerStats = new ContainerStatsViewModel(data);
|
||||
deferred.resolve(containerStats);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.containerTop = function (environmentId, id) {
|
||||
return Container.top({ environmentId, id }).$promise;
|
||||
};
|
||||
|
||||
service.inspect = function (environmentId, id) {
|
||||
return Container.inspect({ environmentId, id }).$promise;
|
||||
};
|
||||
|
||||
service.prune = function (environmentId, filters) {
|
||||
return Container.prune({ environmentId, filters }).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {Containerid} id
|
||||
* @param {boolean?} stdout
|
||||
* @param {boolean?} stderr
|
||||
* @param {boolean?} timestamps
|
||||
* @param {number?} since
|
||||
* @param {number?} tail
|
||||
* @param {boolean?} stripHeaders
|
||||
*/
|
||||
async function containerLogsAngularJS(environmentId, id, stdout = false, stderr = false, timestamps = false, since = 0, tail = 'all', stripHeaders) {
|
||||
const data = await getContainerLogs(environmentId, id, {
|
||||
since,
|
||||
stderr,
|
||||
stdout,
|
||||
tail,
|
||||
timestamps,
|
||||
});
|
||||
return formatLogs(data, { stripHeaders, withTimestamps: !!timestamps });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,23 @@
|
|||
angular.module('portainer.docker').factory('ExecService', [
|
||||
'$q',
|
||||
'$timeout',
|
||||
'Exec',
|
||||
function ExecServiceFactory($q, $timeout, Exec) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
import { resizeTTY } from '@/react/docker/proxy/queries/useExecResizeTTYMutation';
|
||||
|
||||
service.resizeTTY = function (environmentId, execId, width, height, timeout) {
|
||||
var deferred = $q.defer();
|
||||
angular.module('portainer.docker').factory('ExecService', ExecServiceFactory);
|
||||
|
||||
$timeout(function () {
|
||||
Exec.resize({ environmentId }, { id: execId, height: height, width: width })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: 'Unable to resize tty of exec', err: data.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to resize tty of exec', err: err });
|
||||
});
|
||||
}, timeout);
|
||||
/* @ngInject */
|
||||
function ExecServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
return {
|
||||
resizeTTY: useAxios(injectEnvironmentId(resizeTTYAngularJS)),
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {string} execId
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
* @param timeout DEPRECATED: Previously used in pure AJS implementation
|
||||
*/
|
||||
async function resizeTTYAngularJS(environmentId, execId, width, height) {
|
||||
return resizeTTY(environmentId, execId, { width, height });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,208 +1,91 @@
|
|||
import _ from 'lodash';
|
||||
import { groupBy } from 'lodash';
|
||||
|
||||
import { getUniqueTagListFromImages } from '@/react/docker/images/utils';
|
||||
import { getImage } from '@/react/docker/proxy/queries/images/useImage';
|
||||
import { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { getImages } from '@/react/docker/proxy/queries/images/useImages';
|
||||
import { getContainers } from '@/react/docker/containers/queries/useContainers';
|
||||
import { getImageHistory } from '@/react/docker/proxy/queries/images/useImageHistory';
|
||||
import { pullImage } from '@/react/docker/images/queries/usePullImageMutation';
|
||||
import { pushImage } from '@/react/docker/images/queries/usePushImageMutation';
|
||||
import { removeImage } from '@/react/docker/proxy/queries/images/useRemoveImageMutation';
|
||||
import { tagImage } from '@/react/docker/proxy/queries/images/useTagImageMutation';
|
||||
import { downloadImages } from '@/react/docker/proxy/queries/images/useDownloadImages';
|
||||
import { uploadImages } from '@/react/docker/proxy/queries/images/useUploadImageMutation';
|
||||
|
||||
import { ImageViewModel } from '../models/image';
|
||||
import { ImageDetailsViewModel } from '../models/imageDetails';
|
||||
import { ImageLayerViewModel } from '../models/imageLayer';
|
||||
|
||||
angular.module('portainer.docker').factory('ImageService', [
|
||||
'$q',
|
||||
'Image',
|
||||
'ImageHelper',
|
||||
'RegistryService',
|
||||
'HttpRequestHelper',
|
||||
'ContainerService',
|
||||
'FileUploadService',
|
||||
function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper, ContainerService, FileUploadService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('ImageService', ImageServiceFactory);
|
||||
|
||||
service.image = function (imageId) {
|
||||
var deferred = $q.defer();
|
||||
Image.get({ id: imageId })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
var image = new ImageDetailsViewModel(data);
|
||||
deferred.resolve(image);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve image details', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
/* @ngInject */
|
||||
function ImageServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
service.images = function ({ environmentId, withUsage } = {}) {
|
||||
var deferred = $q.defer();
|
||||
return {
|
||||
image: useAxios(injectEnvironmentId(imageAngularJS)), // container console + image edit
|
||||
images: useAxios(injectEnvironmentId(imagesAngularJS)), // por image registry controller + dashboard + service edit
|
||||
history: useAxios(injectEnvironmentId(historyAngularJS)), // image edit
|
||||
pushImage: useAxios(injectEnvironmentId(pushImageAngularJS)), // image edit
|
||||
pullImage: useAxios(injectEnvironmentId(pullImageAngularJS)), // images list + image edit + templates list
|
||||
tagImage: useAxios(injectEnvironmentId(tagImage)), // image edit + image import
|
||||
downloadImages: useAxios(injectEnvironmentId(downloadImages)), // image list + image edit
|
||||
uploadImage: useAxios(injectEnvironmentId(uploadImages)), // image import
|
||||
deleteImage: useAxios(injectEnvironmentId(removeImage)), // image list + image edit
|
||||
getUniqueTagListFromImages, // por image registry controller + service edit
|
||||
};
|
||||
|
||||
$q.all({
|
||||
containers: withUsage ? ContainerService.containers(environmentId, 1) : [],
|
||||
images: Image.query({}).$promise,
|
||||
})
|
||||
.then(function success(data) {
|
||||
var containers = data.containers;
|
||||
const containerByImageId = _.groupBy(containers, 'ImageID');
|
||||
async function imageAngularJS(environmentId, imageId) {
|
||||
const image = await getImage(environmentId, imageId);
|
||||
return new ImageDetailsViewModel(image);
|
||||
}
|
||||
|
||||
var images = data.images.map(function (item) {
|
||||
item.Used = !!containerByImageId[item.Id] && containerByImageId[item.Id].length > 0;
|
||||
return new ImageViewModel(item);
|
||||
});
|
||||
|
||||
deferred.resolve(images);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve images', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.history = function (imageId) {
|
||||
var deferred = $q.defer();
|
||||
Image.history({ id: imageId })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
var layers = [];
|
||||
var order = data.length;
|
||||
angular.forEach(data, function (imageLayer) {
|
||||
layers.push(new ImageLayerViewModel(order, imageLayer));
|
||||
order--;
|
||||
});
|
||||
deferred.resolve(layers);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve image details', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.pushImage = pushImage;
|
||||
/**
|
||||
*
|
||||
* @param {PorImageRegistryModel} registryModel
|
||||
*/
|
||||
function pushImage(registryModel) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : '';
|
||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||
|
||||
const imageConfiguration = ImageHelper.createImageConfigForContainer(registryModel);
|
||||
|
||||
Image.push({ imageName: imageConfiguration.fromImage })
|
||||
.$promise.then(function success(data) {
|
||||
if (data[data.length - 1].error) {
|
||||
deferred.reject({ msg: data[data.length - 1].error });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to push image tag', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
async function imagesAngularJS(environmentId, withUsage) {
|
||||
try {
|
||||
const [containers, images] = await Promise.all([withUsage ? getContainers(environmentId) : [], getImages(environmentId)]);
|
||||
const containerByImageId = groupBy(containers, 'ImageID');
|
||||
return images.map((item) => new ImageViewModel(item, !!containerByImageId[item.Id] && containerByImageId[item.Id].length > 0));
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e, 'Unable to retrieve images');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PULL IMAGE
|
||||
*/
|
||||
|
||||
function pullImageAndIgnoreErrors(imageConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Image.create({}, imageConfiguration)
|
||||
.$promise.catch(() => {
|
||||
// left empty to ignore errors
|
||||
})
|
||||
.finally(function final() {
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
async function historyAngularJS(environmentId, imageId) {
|
||||
try {
|
||||
const layers = await getImageHistory(environmentId, imageId);
|
||||
return layers.reverse().map((layer, idx) => new ImageLayerViewModel(idx, layer));
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e, 'Unable to retrieve image history');
|
||||
}
|
||||
}
|
||||
|
||||
function pullImageAndAcknowledgeErrors(imageConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* type PorImageRegistryModel = {
|
||||
* UseRegistry: bool;
|
||||
* Registry?: Registry;
|
||||
* Image: string;
|
||||
* }
|
||||
*/
|
||||
|
||||
Image.create({}, imageConfiguration)
|
||||
.$promise.then(function success(data) {
|
||||
var err = data.length > 0 && data[data.length - 1].message;
|
||||
if (err) {
|
||||
var detail = data[data.length - 1];
|
||||
deferred.reject({ msg: detail.message });
|
||||
} else {
|
||||
deferred.resolve(data);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to pull image', err: err });
|
||||
});
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Autofilled by AngularToReact
|
||||
* @param {PorImageRegistryModel} registryModel
|
||||
*/
|
||||
async function pushImageAngularJS(environmentId, registryModel) {
|
||||
const { UseRegistry, Registry, Image } = registryModel;
|
||||
const registry = UseRegistry ? Registry : undefined;
|
||||
return pushImage({ environmentId, image: Image, registry });
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
service.pullImage = pullImage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {PorImageRegistryModel} registry
|
||||
* @param {bool} ignoreErrors
|
||||
*/
|
||||
function pullImage(registry, ignoreErrors) {
|
||||
var authenticationDetails = registry.Registry.Authentication ? RegistryService.encodedCredentials(registry.Registry) : '';
|
||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||
|
||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(registry);
|
||||
|
||||
if (ignoreErrors) {
|
||||
return pullImageAndIgnoreErrors(imageConfiguration);
|
||||
}
|
||||
return pullImageAndAcknowledgeErrors(imageConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* ! PULL IMAGE
|
||||
*/
|
||||
|
||||
service.tagImage = function (id, image) {
|
||||
return Image.tag({ id: id, repo: image }).$promise;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array<{tags: Array<string>; id: string;}>} images
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
service.downloadImages = function (images) {
|
||||
var names = ImageHelper.getImagesNamesForDownload(images);
|
||||
return Image.download(names).$promise;
|
||||
};
|
||||
|
||||
service.uploadImage = function (file) {
|
||||
return FileUploadService.loadImages(file);
|
||||
};
|
||||
|
||||
service.deleteImage = function (id, forceRemoval) {
|
||||
var deferred = $q.defer();
|
||||
Image.remove({ id: id, force: forceRemoval })
|
||||
.$promise.then(function success(data) {
|
||||
if (data[0].message) {
|
||||
deferred.reject({ msg: data[0].message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove image', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.getUniqueTagListFromImages = getUniqueTagListFromImages;
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Autofilled by AngularToReact
|
||||
* @param {PorImageRegistryModel} registryModel
|
||||
* @param {string?} nodeName
|
||||
*/
|
||||
async function pullImageAngularJS(environmentId, registryModel, nodeName) {
|
||||
const { UseRegistry, Registry, Image } = registryModel;
|
||||
const registry = UseRegistry ? Registry : undefined;
|
||||
return pullImage({ environmentId, image: Image, nodeName, registry });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,91 +1,57 @@
|
|||
import { createNetwork } from '@/react/docker/networks/queries/useCreateNetworkMutation';
|
||||
import { getNetwork } from '@/react/docker/networks/queries/useNetwork';
|
||||
import { getNetworks } from '@/react/docker/networks/queries/useNetworks';
|
||||
import { deleteNetwork } from '@/react/docker/networks/queries/useDeleteNetworkMutation';
|
||||
import { disconnectContainer } from '@/react/docker/networks/queries/useDisconnectContainerMutation';
|
||||
import { connectContainer } from '@/react/docker/networks/queries/useConnectContainerMutation';
|
||||
|
||||
import { NetworkViewModel } from '../models/network';
|
||||
|
||||
angular.module('portainer.docker').factory('NetworkService', [
|
||||
'$q',
|
||||
'Network',
|
||||
function NetworkServiceFactory($q, Network) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('NetworkService', NetworkServiceFactory);
|
||||
|
||||
service.create = function (networkConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
/* @ngInject */
|
||||
function NetworkServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
Network.create(networkConfiguration)
|
||||
.$promise.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create network', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
return {
|
||||
create: useAxios(injectEnvironmentId(createNetwork)), // create network
|
||||
network: useAxios(injectEnvironmentId(networkAngularJS)), // service edit
|
||||
networks: useAxios(injectEnvironmentId(networksAngularJS)), // macvlan form + container edit + dashboard + service create + service edit + custom templates list + templates list
|
||||
remove: useAxios(injectEnvironmentId(deleteNetwork)), // networks list
|
||||
disconnectContainer: useAxios(injectEnvironmentId(disconnectContainer)), // container edit
|
||||
connectContainer: useAxios(injectEnvironmentId(connectContainerAngularJS)), // container edit
|
||||
};
|
||||
|
||||
service.network = function (id) {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId filled by AngularToReact
|
||||
* @param {NetworkId} networkId
|
||||
* @param {string?} nodeName
|
||||
* @returns NetworkViewModel
|
||||
*/
|
||||
async function networkAngularJS(environmentId, networkId, nodeName) {
|
||||
const data = await getNetwork(environmentId, networkId, { nodeName });
|
||||
return new NetworkViewModel(data);
|
||||
}
|
||||
|
||||
Network.get({ id: id })
|
||||
.$promise.then(function success(data) {
|
||||
var network = new NetworkViewModel(data);
|
||||
deferred.resolve(network);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve network details', err: err });
|
||||
});
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId filled by AngularToReact
|
||||
* @param {boolean?} localNetworks
|
||||
* @param {boolean?} swarmNetworks
|
||||
* @param {boolean?} swarmAttachableNetworks
|
||||
* @param {*} filters
|
||||
* @returns NetworkViewModel[]
|
||||
*/
|
||||
async function networksAngularJS(environmentId, local, swarm, swarmAttachable, filters) {
|
||||
const data = await getNetworks(environmentId, { local, swarm, swarmAttachable, filters });
|
||||
return data.map((n) => new NetworkViewModel(n));
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.networks = function (localNetworks, swarmNetworks, swarmAttachableNetworks, filters) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Network.query({ filters: filters })
|
||||
.$promise.then(function success(data) {
|
||||
var networks = data;
|
||||
var filteredNetworks = networks
|
||||
.filter(function (network) {
|
||||
if (localNetworks && network.Scope === 'local') {
|
||||
return network;
|
||||
}
|
||||
if (swarmNetworks && network.Scope === 'swarm') {
|
||||
return network;
|
||||
}
|
||||
if (swarmAttachableNetworks && network.Scope === 'swarm' && network.Attachable === true) {
|
||||
return network;
|
||||
}
|
||||
})
|
||||
.map(function (item) {
|
||||
return new NetworkViewModel(item);
|
||||
});
|
||||
|
||||
deferred.resolve(filteredNetworks);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve networks', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function (id) {
|
||||
return Network.remove({ id: id }).$promise;
|
||||
};
|
||||
|
||||
service.disconnectContainer = function (networkId, containerId, force) {
|
||||
return Network.disconnect({ id: networkId }, { Container: containerId, Force: force }).$promise;
|
||||
};
|
||||
|
||||
service.connectContainer = function (networkId, containerId, aliases) {
|
||||
var payload = {
|
||||
Container: containerId,
|
||||
};
|
||||
if (aliases) {
|
||||
payload.EndpointConfig = {
|
||||
Aliases: aliases,
|
||||
};
|
||||
}
|
||||
return Network.connect({ id: networkId }, payload).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId filled by AngularToReact
|
||||
* @param {NetworkId} networkId
|
||||
* @param {ContainerId} containerId
|
||||
*/
|
||||
async function connectContainerAngularJS(environmentId, networkId, containerId) {
|
||||
return connectContainer({ environmentId, containerId, networkId });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,73 +1,43 @@
|
|||
import { getNode } from '@/react/docker/proxy/queries/nodes/useNode';
|
||||
import { getNodes } from '@/react/docker/proxy/queries/nodes/useNodes';
|
||||
import { updateNode } from '@/react/docker/proxy/queries/nodes/useUpdateNodeMutation';
|
||||
|
||||
import { NodeViewModel } from '../models/node';
|
||||
|
||||
angular.module('portainer.docker').factory('NodeService', [
|
||||
'$q',
|
||||
'Node',
|
||||
function NodeServiceFactory($q, Node) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('NodeService', NodeServiceFactory);
|
||||
|
||||
service.nodes = nodes;
|
||||
service.node = node;
|
||||
service.updateNode = updateNode;
|
||||
service.getActiveManager = getActiveManager;
|
||||
/* @ngInject */
|
||||
function NodeServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
function node(id) {
|
||||
var deferred = $q.defer();
|
||||
Node.get({ id: id })
|
||||
.$promise.then(function onNodeLoaded(rawNode) {
|
||||
var node = new NodeViewModel(rawNode);
|
||||
return deferred.resolve(node);
|
||||
})
|
||||
.catch(function onFailed(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve node', err: err });
|
||||
});
|
||||
return {
|
||||
nodes: useAxios(injectEnvironmentId(nodesAngularJS)), // macvlan form + services list + service create + service edit + swarm visualizer + stack edit
|
||||
node: useAxios(injectEnvironmentId(nodeAngularJS)), // node browser + node details
|
||||
updateNode: useAxios(injectEnvironmentId(updateNodeAngularJS)), // swarm node details panel
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {NodeId} id
|
||||
*/
|
||||
async function nodeAngularJS(environmentId, id) {
|
||||
const data = await getNode(environmentId, id);
|
||||
return new NodeViewModel(data);
|
||||
}
|
||||
|
||||
function nodes() {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
*/
|
||||
async function nodesAngularJS(environmentId) {
|
||||
const data = await getNodes(environmentId);
|
||||
return data.map((n) => new NodeViewModel(n));
|
||||
}
|
||||
|
||||
Node.query({})
|
||||
.$promise.then(function success(data) {
|
||||
var nodes = data.map(function (item) {
|
||||
return new NodeViewModel(item);
|
||||
});
|
||||
deferred.resolve(nodes);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve nodes', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function updateNode(node) {
|
||||
return Node.update({ id: node.Id, version: node.Version }, node).$promise;
|
||||
}
|
||||
|
||||
function getActiveManager() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
service
|
||||
.nodes()
|
||||
.then(function success(data) {
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
var node = data[i];
|
||||
if (node.Role === 'manager' && node.Availability === 'active' && node.Status === 'ready' && node.Addr !== '0.0.0.0') {
|
||||
deferred.resolve(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve nodes', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {NodeSpec & { Id: string; Version: number }} nodeConfig
|
||||
*/
|
||||
async function updateNodeAngularJS(environmentId, nodeConfig) {
|
||||
return updateNode(environmentId, nodeConfig.Id, nodeConfig, nodeConfig.Version);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,76 +1,51 @@
|
|||
import _ from 'lodash-es';
|
||||
import { PluginViewModel } from '../models/plugin';
|
||||
import { isFulfilled } from '@/portainer/helpers/promise-utils';
|
||||
import { getInfo } from '@/react/docker/proxy/queries/useInfo';
|
||||
import { aggregateData, getPlugins } from '@/react/docker/proxy/queries/useServicePlugins';
|
||||
|
||||
angular.module('portainer.docker').factory('PluginService', [
|
||||
'$q',
|
||||
'Plugin',
|
||||
'SystemService',
|
||||
function PluginServiceFactory($q, Plugin, SystemService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('PluginService', PluginServiceFactory);
|
||||
|
||||
service.plugins = function () {
|
||||
var deferred = $q.defer();
|
||||
var plugins = [];
|
||||
/* @ngInject */
|
||||
function PluginServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
Plugin.query({})
|
||||
.$promise.then(function success(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var plugin = new PluginViewModel(data[i]);
|
||||
plugins.push(plugin);
|
||||
}
|
||||
})
|
||||
.finally(function final() {
|
||||
deferred.resolve(plugins);
|
||||
});
|
||||
return {
|
||||
volumePlugins: useAxios(injectEnvironmentId(volumePlugins)), // volume create
|
||||
networkPlugins: useAxios(injectEnvironmentId(networksPlugins)), // network create
|
||||
loggingPlugins: useAxios(injectEnvironmentId(loggingPlugins)), // service create + service edit
|
||||
};
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {boolean} systemOnly
|
||||
*/
|
||||
async function volumePlugins(environmentId, systemOnly) {
|
||||
const { systemPluginsData, pluginsData } = await getAllPlugins(environmentId);
|
||||
return aggregateData(systemPluginsData, pluginsData, systemOnly, 'Volume');
|
||||
}
|
||||
|
||||
function servicePlugins(systemOnly, pluginType, pluginVersion) {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {boolean} systemOnly
|
||||
*/
|
||||
async function networksPlugins(environmentId, systemOnly) {
|
||||
const { systemPluginsData, pluginsData } = await getAllPlugins(environmentId);
|
||||
return aggregateData(systemPluginsData, pluginsData, systemOnly, 'Network');
|
||||
}
|
||||
|
||||
$q.all({
|
||||
system: SystemService.plugins(),
|
||||
plugins: systemOnly ? [] : service.plugins(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
var aggregatedPlugins = [];
|
||||
var systemPlugins = data.system;
|
||||
var plugins = data.plugins;
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {boolean} systemOnly
|
||||
*/
|
||||
async function loggingPlugins(environmentId, systemOnly) {
|
||||
const { systemPluginsData, pluginsData } = await getAllPlugins(environmentId);
|
||||
return aggregateData(systemPluginsData, pluginsData, systemOnly, 'Log');
|
||||
}
|
||||
|
||||
if (systemPlugins[pluginType]) {
|
||||
aggregatedPlugins = aggregatedPlugins.concat(systemPlugins[pluginType]);
|
||||
}
|
||||
async function getAllPlugins(environmentId) {
|
||||
const [system, plugins] = await Promise.allSettled([getInfo(environmentId), getPlugins(environmentId)]);
|
||||
const systemPluginsData = isFulfilled(system) ? system.value.Plugins : undefined;
|
||||
const pluginsData = isFulfilled(plugins) ? plugins.value : undefined;
|
||||
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
var plugin = plugins[i];
|
||||
if (plugin.Enabled && _.includes(plugin.Config.Interface.Types, pluginVersion)) {
|
||||
aggregatedPlugins.push(plugin.Name);
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve(aggregatedPlugins);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: err.msg, err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
service.volumePlugins = function (systemOnly) {
|
||||
return servicePlugins(systemOnly, 'Volume', 'docker.volumedriver/1.0');
|
||||
};
|
||||
|
||||
service.networkPlugins = function (systemOnly) {
|
||||
return servicePlugins(systemOnly, 'Network', 'docker.networkdriver/1.0');
|
||||
};
|
||||
|
||||
service.loggingPlugins = function (systemOnly) {
|
||||
return servicePlugins(systemOnly, 'Log', 'docker.logdriver/1.0');
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
return { systemPluginsData, pluginsData };
|
||||
}
|
||||
|
|
|
@ -1,66 +1,37 @@
|
|||
import { getSecret } from '@/react/docker/proxy/queries/secrets/useSecret';
|
||||
import { getSecrets } from '@/react/docker/proxy/queries/secrets/useSecrets';
|
||||
import { removeSecret } from '@/react/docker/proxy/queries/secrets/useRemoveSecretMutation';
|
||||
import { createSecret } from '@/react/docker/proxy/queries/secrets/useCreateSecretMutation';
|
||||
|
||||
import { SecretViewModel } from '../models/secret';
|
||||
|
||||
angular.module('portainer.docker').factory('SecretService', [
|
||||
'$q',
|
||||
'Secret',
|
||||
function SecretServiceFactory($q, Secret) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('SecretService', SecretServiceFactory);
|
||||
|
||||
service.secret = function (secretId) {
|
||||
var deferred = $q.defer();
|
||||
/* @ngInject */
|
||||
function SecretServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
Secret.get({ id: secretId })
|
||||
.$promise.then(function success(data) {
|
||||
var secret = new SecretViewModel(data);
|
||||
deferred.resolve(secret);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve secret details', err: err });
|
||||
});
|
||||
return {
|
||||
secret: useAxios(injectEnvironmentId(secretAngularJS)), // secret edit
|
||||
secrets: useAxios(injectEnvironmentId(secretsAngularJS)), // secret list + service create + service edit
|
||||
remove: useAxios(injectEnvironmentId(removeSecret)), // secret list + secret edit
|
||||
create: useAxios(injectEnvironmentId(createSecret)), // secret create
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {SecretId} id
|
||||
*/
|
||||
async function secretAngularJS(environmentId, id) {
|
||||
const data = await getSecret(environmentId, id);
|
||||
return new SecretViewModel(data);
|
||||
}
|
||||
|
||||
service.secrets = function () {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Secret.query({})
|
||||
.$promise.then(function success(data) {
|
||||
var secrets = data.map(function (item) {
|
||||
return new SecretViewModel(item);
|
||||
});
|
||||
deferred.resolve(secrets);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve secrets', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.remove = function (secretId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Secret.remove({ id: secretId })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove secret', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.create = function (secretConfig) {
|
||||
return Secret.create(secretConfig).$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
*/
|
||||
async function secretsAngularJS(environmentId) {
|
||||
const data = await getSecrets(environmentId);
|
||||
return data.map((s) => new SecretViewModel(s));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,100 +1,97 @@
|
|||
import { formatLogs } from '../helpers/logHelper';
|
||||
import { removeService } from '@/react/docker/services/ListView/ServicesDatatable/useRemoveServicesMutation';
|
||||
import { createService } from '@/react/docker/services/queries/useCreateServiceMutation';
|
||||
import { getService } from '@/react/docker/services/queries/useService';
|
||||
import { getServices } from '@/react/docker/services/queries/useServices';
|
||||
import { updateService } from '@/react/docker/services/queries/useUpdateServiceMutation';
|
||||
import { getServiceLogs } from '@/react/docker/services/queries/useServiceLogs';
|
||||
|
||||
import { ServiceViewModel } from '../models/service';
|
||||
import { formatLogs } from '../helpers/logHelper';
|
||||
|
||||
angular.module('portainer.docker').factory('ServiceService', [
|
||||
'$q',
|
||||
'Service',
|
||||
function ServiceServiceFactory($q, Service) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('ServiceService', ServiceServiceFactory);
|
||||
|
||||
service.services = function (filters) {
|
||||
var deferred = $q.defer();
|
||||
/* @ngInject */
|
||||
function ServiceServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
Service.query({ filters: filters ? filters : {} })
|
||||
.$promise.then(function success(data) {
|
||||
var services = data.map(function (item) {
|
||||
return new ServiceViewModel(item);
|
||||
});
|
||||
deferred.resolve(services);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve services', err: err });
|
||||
});
|
||||
return {
|
||||
services: useAxios(injectEnvironmentId(getServicesAngularJS)), // dashboard + service list + swarm visualizer + volume list + stackservice + stack edit
|
||||
service: useAxios(injectEnvironmentId(getServiceAngularJS)), // service edit + task edit
|
||||
remove: useAxios(injectEnvironmentId(removeServiceAngularJS)), // service edit
|
||||
update: useAxios(injectEnvironmentId(updateServiceAngularJS)), // service edit
|
||||
create: useAxios(injectEnvironmentId(createServiceAngularJS)), // service create
|
||||
logs: useAxios(injectEnvironmentId(serviceLogsAngularJS)), // service logs
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {*} filters
|
||||
*/
|
||||
async function getServicesAngularJS(environmentId, filters) {
|
||||
const data = await getServices(environmentId, filters);
|
||||
return data.map((s) => new ServiceViewModel(s));
|
||||
}
|
||||
|
||||
service.service = function (id) {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {ServiceId} serviceId
|
||||
*/
|
||||
async function getServiceAngularJS(environmentId, serviceId) {
|
||||
const data = await getService(environmentId, serviceId);
|
||||
return new ServiceViewModel(data);
|
||||
}
|
||||
|
||||
Service.get({ id: id })
|
||||
.$promise.then(function success(data) {
|
||||
var service = new ServiceViewModel(data);
|
||||
deferred.resolve(service);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve service details', err: err });
|
||||
});
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {ServiceViewModel} service
|
||||
*/
|
||||
async function removeServiceAngularJS(environmentId, service) {
|
||||
return removeService(environmentId, service.Id);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {ServiceViewModel} service
|
||||
* @param {ServiceUpdateConfig} config
|
||||
* @param {string?} rollback
|
||||
*/
|
||||
async function updateServiceAngularJS(environmentId, service, config, rollback) {
|
||||
return updateService({
|
||||
environmentId,
|
||||
config,
|
||||
serviceId: service.Id,
|
||||
version: service.Version,
|
||||
registryId: config.registryId,
|
||||
rollback,
|
||||
});
|
||||
}
|
||||
|
||||
service.remove = function (service) {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {Service} config
|
||||
* @param {RegistryId} registryId
|
||||
*/
|
||||
async function createServiceAngularJS(environmentId, config, registryId) {
|
||||
return createService({ environmentId, config, registryId });
|
||||
}
|
||||
|
||||
Service.remove({ id: service.Id })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message, err: data.message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove service', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.update = function (serv, config, rollback) {
|
||||
return service.service(serv.Id).then((data) => {
|
||||
const params = {
|
||||
id: serv.Id,
|
||||
version: data.Version,
|
||||
};
|
||||
if (rollback) {
|
||||
params.rollback = rollback;
|
||||
}
|
||||
return Service.update(params, config).$promise;
|
||||
});
|
||||
};
|
||||
|
||||
service.logs = function (id, stdout, stderr, timestamps, since, tail) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var parameters = {
|
||||
id: id,
|
||||
stdout: stdout || 0,
|
||||
stderr: stderr || 0,
|
||||
timestamps: timestamps || 0,
|
||||
since: since || 0,
|
||||
tail: tail || 'all',
|
||||
};
|
||||
|
||||
Service.logs(parameters)
|
||||
.$promise.then(function success(data) {
|
||||
var logs = formatLogs(data.logs, { stripHeaders: true, withTimestamps: !!timestamps });
|
||||
deferred.resolve(logs);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {ServiceId} id
|
||||
* @param {boolean?} stdout
|
||||
* @param {boolean?} stderr
|
||||
* @param {boolean?} timestamps
|
||||
* @param {number?} since
|
||||
* @param {number?} tail
|
||||
*/
|
||||
async function serviceLogsAngularJS(environmentId, id, stdout = false, stderr = false, timestamps = false, since = 0, tail = 'all') {
|
||||
const data = await getServiceLogs(environmentId, id, {
|
||||
since,
|
||||
stderr,
|
||||
stdout,
|
||||
tail,
|
||||
timestamps,
|
||||
});
|
||||
return formatLogs(data, { stripHeaders: true, withTimestamps: !!timestamps });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
import { SwarmViewModel } from '../models/swarm';
|
||||
import { getSwarm } from '@/react/docker/proxy/queries/useSwarm';
|
||||
|
||||
angular.module('portainer.docker').factory('SwarmService', [
|
||||
'$q',
|
||||
'Swarm',
|
||||
function SwarmServiceFactory($q, Swarm) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('SwarmService', SwarmServiceFactory);
|
||||
|
||||
service.swarm = function (endpointId) {
|
||||
var deferred = $q.defer();
|
||||
/* @ngInject */
|
||||
function SwarmServiceFactory(AngularToReact) {
|
||||
const { useAxios } = AngularToReact;
|
||||
|
||||
Swarm.get(endpointId ? { endpointId } : undefined)
|
||||
.$promise.then(function success(data) {
|
||||
var swarm = new SwarmViewModel(data);
|
||||
deferred.resolve(swarm);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve Swarm details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
return {
|
||||
swarm: useAxios(getSwarm), // stack service
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,59 +1,28 @@
|
|||
import { ping } from '@/react/docker/proxy/queries/usePing';
|
||||
import { getInfo } from '@/react/docker/proxy/queries/useInfo';
|
||||
import { getVersion } from '@/react/docker/proxy/queries/useVersion';
|
||||
import { getEvents } from '@/react/docker/proxy/queries/useEvents';
|
||||
import { EventViewModel } from '../models/event';
|
||||
import { ping } from './ping';
|
||||
|
||||
angular.module('portainer.docker').factory('SystemService', [
|
||||
'$q',
|
||||
'System',
|
||||
function SystemServiceFactory($q, System) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('SystemService', SystemServiceFactory);
|
||||
|
||||
service.plugins = function () {
|
||||
var deferred = $q.defer();
|
||||
System.info({})
|
||||
.$promise.then(function success(data) {
|
||||
var plugins = data.Plugins;
|
||||
deferred.resolve(plugins);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve plugins information from system', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
/* @ngInject */
|
||||
function SystemServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
service.info = function () {
|
||||
return System.info({}).$promise;
|
||||
};
|
||||
return {
|
||||
info: useAxios(injectEnvironmentId(getInfo)), // dashboard + docker host view + docker host browser + swarm inspect views + stateManager (update endpoint state)
|
||||
ping: useAxios(ping), // docker/__module onEnter abstract /docker subpath
|
||||
version: useAxios(injectEnvironmentId(getVersion)), // docker host view + swarm inspect view + stateManager (update endpoint state)
|
||||
events: useAxios(injectEnvironmentId(eventsAngularJS)), // events list
|
||||
};
|
||||
|
||||
service.ping = function (endpointId) {
|
||||
return ping(endpointId);
|
||||
};
|
||||
|
||||
service.version = function () {
|
||||
return System.version({}).$promise;
|
||||
};
|
||||
|
||||
service.events = function (from, to) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
System.events({ since: from, until: to })
|
||||
.$promise.then(function success(data) {
|
||||
var events = data.map(function (item) {
|
||||
return new EventViewModel(item);
|
||||
});
|
||||
deferred.resolve(events);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve engine events', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.dataUsage = function () {
|
||||
return System.dataUsage().$promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {{since: string; until: string;}} param1
|
||||
*/
|
||||
async function eventsAngularJS(environmentId, { since, until }) {
|
||||
const data = await getEvents(environmentId, { since, until });
|
||||
return data.map((e) => new EventViewModel(e));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,57 @@
|
|||
import { formatLogs } from '../helpers/logHelper';
|
||||
import { getTask } from '@/react/docker/tasks/queries/useTask';
|
||||
import { getTasks } from '@/react/docker/proxy/queries/tasks/useTasks';
|
||||
import { getTaskLogs } from '@/react/docker/tasks/queries/useTaskLogs';
|
||||
|
||||
import { TaskViewModel } from '../models/task';
|
||||
import { formatLogs } from '../helpers/logHelper';
|
||||
|
||||
angular.module('portainer.docker').factory('TaskService', [
|
||||
'$q',
|
||||
'Task',
|
||||
function TaskServiceFactory($q, Task) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('TaskService', TaskServiceFactory);
|
||||
|
||||
service.task = function (id) {
|
||||
var deferred = $q.defer();
|
||||
/* @ngInject */
|
||||
function TaskServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
Task.get({ id: id })
|
||||
.$promise.then(function success(data) {
|
||||
var task = new TaskViewModel(data);
|
||||
deferred.resolve(task);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve task details', err: err });
|
||||
});
|
||||
return {
|
||||
task: useAxios(injectEnvironmentId(taskAngularJS)), // task edit
|
||||
tasks: useAxios(injectEnvironmentId(tasksAngularJS)), // services list + service edit + swarm visualizer + stack edit
|
||||
logs: useAxios(injectEnvironmentId(taskLogsAngularJS)), // task logs
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {TaskId} id
|
||||
*/
|
||||
async function taskAngularJS(environmentId, id) {
|
||||
const data = await getTask(environmentId, id);
|
||||
return new TaskViewModel(data);
|
||||
}
|
||||
|
||||
service.tasks = function (filters) {
|
||||
var deferred = $q.defer();
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {*} filters
|
||||
*/
|
||||
async function tasksAngularJS(environmentId, filters) {
|
||||
const data = await getTasks(environmentId, filters);
|
||||
return data.map((t) => new TaskViewModel(t));
|
||||
}
|
||||
|
||||
Task.query({ filters: filters ? filters : {} })
|
||||
.$promise.then(function success(data) {
|
||||
var tasks = data.map(function (item) {
|
||||
return new TaskViewModel(item);
|
||||
});
|
||||
deferred.resolve(tasks);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve tasks', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.logs = function (id, stdout, stderr, timestamps, since, tail) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var parameters = {
|
||||
id: id,
|
||||
stdout: stdout || 0,
|
||||
stderr: stderr || 0,
|
||||
timestamps: timestamps || 0,
|
||||
since: since || 0,
|
||||
tail: tail || 'all',
|
||||
};
|
||||
|
||||
Task.logs(parameters)
|
||||
.$promise.then(function success(data) {
|
||||
var logs = formatLogs(data.logs, { stripHeaders: true, withTimestamps: !!timestamps });
|
||||
deferred.resolve(logs);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId
|
||||
* @param {TaskId} id
|
||||
* @param {boolean?} stdout
|
||||
* @param {boolean?} stderr
|
||||
* @param {boolean?} timestamps
|
||||
* @param {number?} since
|
||||
* @param {number?} tail
|
||||
*/
|
||||
async function taskLogsAngularJS(environmentId, id, stdout = false, stderr = false, timestamps = false, since = 0, tail = 'all') {
|
||||
const data = await getTaskLogs(environmentId, id, {
|
||||
since,
|
||||
stderr,
|
||||
stdout,
|
||||
tail,
|
||||
timestamps,
|
||||
});
|
||||
return formatLogs(data, { stripHeaders: true, withTimestamps: !!timestamps });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,97 +1,79 @@
|
|||
import { getVolumes } from '@/react/docker/volumes/queries/useVolumes';
|
||||
import { getVolume } from '@/react/docker/volumes/queries/useVolume';
|
||||
import { removeVolume } from '@/react/docker/volumes/queries/useRemoveVolumeMutation';
|
||||
import { createVolume } from '@/react/docker/volumes/queries/useCreateVolumeMutation';
|
||||
|
||||
import { VolumeViewModel } from '../models/volume';
|
||||
|
||||
angular.module('portainer.docker').factory('VolumeService', [
|
||||
'$q',
|
||||
'Volume',
|
||||
'VolumeHelper',
|
||||
function VolumeServiceFactory($q, Volume, VolumeHelper) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.docker').factory('VolumeService', VolumeServiceFactory);
|
||||
|
||||
service.volumes = function (params) {
|
||||
var deferred = $q.defer();
|
||||
Volume.query(params)
|
||||
.$promise.then(function success(data) {
|
||||
var volumes = data.Volumes || [];
|
||||
volumes = volumes.map(function (item) {
|
||||
return new VolumeViewModel(item);
|
||||
});
|
||||
deferred.resolve(volumes);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve volumes', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
/* @ngInject */
|
||||
function VolumeServiceFactory(AngularToReact) {
|
||||
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
|
||||
return {
|
||||
volumes: useAxios(injectEnvironmentId(volumesAngularJS)), // dashboard + service create + service edit + volume list
|
||||
volume: useAxios(injectEnvironmentId(volumeAngularJS)), // volume edit
|
||||
getVolumes: useAxios(injectEnvironmentId(getVolumesAngularJS)), // template list
|
||||
remove: useAxios(injectEnvironmentId(removeAngularJS)), // volume list + volume edit
|
||||
createVolume: useAxios(injectEnvironmentId(createAngularJS)), // volume create
|
||||
createVolumeConfiguration, // volume create
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {Filters} filters
|
||||
*/
|
||||
async function volumesAngularJS(environmentId, filters) {
|
||||
const data = await getVolumes(environmentId, filters);
|
||||
return data.map((v) => new VolumeViewModel(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {string} id
|
||||
*/
|
||||
async function volumeAngularJS(environmentId, id) {
|
||||
const data = await getVolume(environmentId, id);
|
||||
return new VolumeViewModel(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
*/
|
||||
async function getVolumesAngularJS(environmentId) {
|
||||
return getVolumes(environmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {string} name
|
||||
* @param {string?} nodeName
|
||||
*/
|
||||
async function removeAngularJS(environmentId, name, nodeName) {
|
||||
return removeVolume(environmentId, name, { nodeName });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} driver
|
||||
* @param {{name: string; value: string;}[]} driverOptions
|
||||
*/
|
||||
function createVolumeConfiguration(name, driver, driverOptions) {
|
||||
return {
|
||||
Name: name,
|
||||
Driver: driver,
|
||||
DriverOpts: driverOptions.reduce((res, { name, value }) => ({ ...res, [name]: value }), {}),
|
||||
};
|
||||
}
|
||||
|
||||
service.volume = function (id) {
|
||||
var deferred = $q.defer();
|
||||
Volume.get({ id: id })
|
||||
.$promise.then(function success(data) {
|
||||
var volume = new VolumeViewModel(data);
|
||||
deferred.resolve(volume);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve volume details', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.getVolumes = function () {
|
||||
return Volume.query({}).$promise;
|
||||
};
|
||||
|
||||
service.remove = function (volume) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Volume.remove({ id: volume.Id })
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message, err: data.message });
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to remove volume', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createVolumeConfiguration = function (name, driver, driverOptions) {
|
||||
var volumeConfiguration = {
|
||||
Name: name,
|
||||
Driver: driver,
|
||||
DriverOpts: VolumeHelper.createDriverOptions(driverOptions),
|
||||
};
|
||||
return volumeConfiguration;
|
||||
};
|
||||
|
||||
service.createVolume = function (volumeConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
Volume.create(volumeConfiguration)
|
||||
.$promise.then(function success(data) {
|
||||
if (data.message) {
|
||||
deferred.reject({ msg: data.message });
|
||||
} else {
|
||||
var volume = new VolumeViewModel(data);
|
||||
deferred.resolve(volume);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create volume', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createVolumes = function (volumeConfigurations) {
|
||||
var createVolumeQueries = volumeConfigurations.map(function (volumeConfiguration) {
|
||||
return service.createVolume(volumeConfiguration);
|
||||
});
|
||||
return $q.all(createVolumeQueries);
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
/**
|
||||
* @param {EnvironmentId} environmentId Injected
|
||||
* @param {VolumeConfiguration} volumeConfiguration
|
||||
* @param {string?} nodeName
|
||||
*/
|
||||
async function createAngularJS(environmentId, volumeConfiguration, nodeName) {
|
||||
const data = await createVolume(environmentId, volumeConfiguration, { nodeName });
|
||||
return new VolumeViewModel(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,28 +9,12 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [
|
|||
'ContainerService',
|
||||
'ImageService',
|
||||
'Notifications',
|
||||
'ContainerHelper',
|
||||
'ExecService',
|
||||
'HttpRequestHelper',
|
||||
'LocalStorage',
|
||||
'CONSOLE_COMMANDS_LABEL_PREFIX',
|
||||
'SidebarService',
|
||||
'endpoint',
|
||||
function (
|
||||
$scope,
|
||||
$state,
|
||||
$transition$,
|
||||
ContainerService,
|
||||
ImageService,
|
||||
Notifications,
|
||||
ContainerHelper,
|
||||
ExecService,
|
||||
HttpRequestHelper,
|
||||
LocalStorage,
|
||||
CONSOLE_COMMANDS_LABEL_PREFIX,
|
||||
SidebarService,
|
||||
endpoint
|
||||
) {
|
||||
function ($scope, $state, $transition$, ContainerService, ImageService, Notifications, ExecService, HttpRequestHelper, CONSOLE_COMMANDS_LABEL_PREFIX, SidebarService, endpoint) {
|
||||
var socket, term;
|
||||
|
||||
let states = Object.freeze({
|
||||
|
@ -97,7 +81,6 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [
|
|||
$scope.state = states.connecting;
|
||||
var command = $scope.formValues.isCustomCommand ? $scope.formValues.customCommand : $scope.formValues.command;
|
||||
var execConfig = {
|
||||
id: $transition$.params().id,
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
|
@ -106,7 +89,7 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [
|
|||
Cmd: commandStringToArray(command),
|
||||
};
|
||||
|
||||
ContainerService.createExec(endpoint.Id, execConfig)
|
||||
ContainerService.createExec(endpoint.Id, $transition$.params().id, execConfig)
|
||||
.then(function success(data) {
|
||||
const params = {
|
||||
endpointId: $state.params.endpointId,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { confirmContainerDeletion } from '@/react/docker/containers/common/confi
|
|||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { ResourceControlType } from '@/react/portainer/access-control/types';
|
||||
import { confirmContainerRecreation } from '@/react/docker/containers/ItemView/ConfirmRecreationModal';
|
||||
import { commitContainer } from '@/react/docker/proxy/queries/useCommitContainerMutation';
|
||||
|
||||
angular.module('portainer.docker').controller('ContainerController', [
|
||||
'$q',
|
||||
|
@ -13,38 +14,14 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
'$transition$',
|
||||
'$filter',
|
||||
'$async',
|
||||
'Commit',
|
||||
'ContainerHelper',
|
||||
'ContainerService',
|
||||
'ImageHelper',
|
||||
'NetworkService',
|
||||
'Notifications',
|
||||
'ResourceControlService',
|
||||
'RegistryService',
|
||||
'ImageService',
|
||||
'HttpRequestHelper',
|
||||
'Authentication',
|
||||
'endpoint',
|
||||
function (
|
||||
$q,
|
||||
$scope,
|
||||
$state,
|
||||
$transition$,
|
||||
$filter,
|
||||
$async,
|
||||
Commit,
|
||||
ContainerHelper,
|
||||
ContainerService,
|
||||
ImageHelper,
|
||||
NetworkService,
|
||||
Notifications,
|
||||
ResourceControlService,
|
||||
RegistryService,
|
||||
ImageService,
|
||||
HttpRequestHelper,
|
||||
Authentication,
|
||||
endpoint
|
||||
) {
|
||||
function ($q, $scope, $state, $transition$, $filter, $async, ContainerService, ImageHelper, NetworkService, Notifications, HttpRequestHelper, Authentication, endpoint) {
|
||||
$scope.resourceType = ResourceControlType.Container;
|
||||
$scope.endpoint = endpoint;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
|
@ -227,7 +204,7 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
|
||||
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
|
||||
$scope.state.leaveNetworkInProgress = true;
|
||||
NetworkService.disconnectContainer(networkId, container.Id, false)
|
||||
NetworkService.disconnectContainer(networkId, container.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Container left network', container.Id);
|
||||
$state.reload();
|
||||
|
@ -260,7 +237,7 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
const registryModel = $scope.config.RegistryModel;
|
||||
const imageConfig = ImageHelper.createImageConfigForContainer(registryModel);
|
||||
try {
|
||||
await Commit.commitContainer({ environmentId: endpoint.Id }, { id: $transition$.params().id, repo: imageConfig.fromImage }).$promise;
|
||||
await commitContainer(endpoint.Id, { container: $transition$.params().id, repo: imageConfig.fromImage });
|
||||
Notifications.success('Image created', $transition$.params().id);
|
||||
$state.reload();
|
||||
} catch (err) {
|
||||
|
|
|
@ -6,10 +6,10 @@ angular.module('portainer.docker').controller('EventsController', [
|
|||
'SystemService',
|
||||
function ($scope, Notifications, SystemService) {
|
||||
function initView() {
|
||||
var from = moment().subtract(24, 'hour').unix();
|
||||
var to = moment().unix();
|
||||
const since = moment().subtract(24, 'hour').unix();
|
||||
const until = moment().unix();
|
||||
|
||||
SystemService.events(from, to)
|
||||
SystemService.events({ since, until })
|
||||
.then(function success(data) {
|
||||
$scope.events = data;
|
||||
})
|
||||
|
|
|
@ -35,15 +35,10 @@ angular.module('portainer.docker').controller('ImagesController', [
|
|||
const registryModel = $scope.formValues.RegistryModel;
|
||||
|
||||
var nodeName = $scope.formValues.NodeName;
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
ImageService.pullImage(registryModel, false)
|
||||
.then(function success(data) {
|
||||
var err = data[data.length - 1].errorDetail;
|
||||
if (err) {
|
||||
return Notifications.error('Failure', err, 'Unable to pull image');
|
||||
}
|
||||
ImageService.pullImage(registryModel, nodeName)
|
||||
.then(function success() {
|
||||
Notifications.success('Image successfully pulled', registryModel.Image);
|
||||
$state.reload();
|
||||
})
|
||||
|
|
|
@ -230,11 +230,8 @@ angular.module('portainer.docker').controller('CreateNetworkController', [
|
|||
}
|
||||
|
||||
function createNetwork(context) {
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName);
|
||||
HttpRequestHelper.setPortainerAgentManagerOperation(context.managerOperation);
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
NetworkService.create(context.networkConfiguration)
|
||||
NetworkService.create(context.networkConfiguration, { nodeName: context.nodeName, agentManagerOperation: context.managerOperation })
|
||||
.then(function success(data) {
|
||||
const userId = context.userDetails.ID;
|
||||
const accessControlData = context.accessControlData;
|
||||
|
|
|
@ -14,7 +14,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
'$scope',
|
||||
'$state',
|
||||
'$timeout',
|
||||
'Service',
|
||||
'ServiceService',
|
||||
'ServiceHelper',
|
||||
'ConfigService',
|
||||
'ConfigHelper',
|
||||
|
@ -29,8 +29,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
'Notifications',
|
||||
'FormValidator',
|
||||
'PluginService',
|
||||
'RegistryService',
|
||||
'HttpRequestHelper',
|
||||
'NodeService',
|
||||
'WebhookService',
|
||||
'endpoint',
|
||||
|
@ -39,7 +37,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
$scope,
|
||||
$state,
|
||||
$timeout,
|
||||
Service,
|
||||
ServiceService,
|
||||
ServiceHelper,
|
||||
ConfigService,
|
||||
ConfigHelper,
|
||||
|
@ -54,8 +52,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
Notifications,
|
||||
FormValidator,
|
||||
PluginService,
|
||||
RegistryService,
|
||||
HttpRequestHelper,
|
||||
NodeService,
|
||||
WebhookService,
|
||||
endpoint
|
||||
|
@ -523,11 +519,9 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
|
||||
function createNewService(config, accessControlData) {
|
||||
const registryModel = $scope.formValues.RegistryModel;
|
||||
var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : '';
|
||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||
|
||||
Service.create(config)
|
||||
.$promise.then(function success(data) {
|
||||
ServiceService.create(config, registryModel.Registry.Authentication ? registryModel.Registry.Id : 0)
|
||||
.then(function success(data) {
|
||||
const serviceId = data.ID;
|
||||
const resourceControl = data.Portainer.ResourceControl;
|
||||
const userId = Authentication.getUserDetails().ID;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { VolumesNFSFormData } from '../../../components/volumesNFSForm/volumesNF
|
|||
import { VolumesCIFSFormData } from '../../../components/volumesCIFSForm/volumesCifsFormModel';
|
||||
|
||||
angular.module('portainer.docker').controller('CreateVolumeController', [
|
||||
'$q',
|
||||
'$scope',
|
||||
'$state',
|
||||
'VolumeService',
|
||||
|
@ -12,9 +11,8 @@ angular.module('portainer.docker').controller('CreateVolumeController', [
|
|||
'Authentication',
|
||||
'Notifications',
|
||||
'FormValidator',
|
||||
'HttpRequestHelper',
|
||||
'endpoint',
|
||||
function ($q, $scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator, HttpRequestHelper, endpoint) {
|
||||
function ($scope, $state, VolumeService, PluginService, ResourceControlService, Authentication, Notifications, FormValidator, endpoint) {
|
||||
$scope.endpoint = endpoint;
|
||||
|
||||
$scope.formValues = {
|
||||
|
@ -126,10 +124,9 @@ angular.module('portainer.docker').controller('CreateVolumeController', [
|
|||
}
|
||||
|
||||
var nodeName = $scope.formValues.NodeName;
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
VolumeService.createVolume(volumeConfiguration)
|
||||
VolumeService.createVolume(volumeConfiguration, nodeName)
|
||||
.then(function success(data) {
|
||||
const userId = userDetails.ID;
|
||||
const resourceControl = data.ResourceControl;
|
||||
|
|
|
@ -21,7 +21,7 @@ angular.module('portainer.docker').controller('VolumeController', [
|
|||
$scope.removeVolume = function removeVolume() {
|
||||
confirmDelete('Do you want to remove this volume?').then((confirmed) => {
|
||||
if (confirmed) {
|
||||
VolumeService.remove($scope.volume)
|
||||
VolumeService.remove($scope.volume.Id)
|
||||
.then(function success() {
|
||||
Notifications.success('Volume successfully removed', $transition$.params().id);
|
||||
$state.go('docker.volumes', {});
|
||||
|
|
|
@ -8,14 +8,12 @@ angular.module('portainer.docker').controller('VolumesController', [
|
|||
'ServiceService',
|
||||
'VolumeHelper',
|
||||
'Notifications',
|
||||
'HttpRequestHelper',
|
||||
'Authentication',
|
||||
'endpoint',
|
||||
function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, Authentication, endpoint) {
|
||||
function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, Authentication, endpoint) {
|
||||
$scope.removeAction = async function (selectedItems) {
|
||||
async function doRemove(volume) {
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(volume.NodeName);
|
||||
return VolumeService.remove(volume)
|
||||
return VolumeService.remove(volume.Id, volume.NodeName)
|
||||
.then(function success() {
|
||||
Notifications.success('Volume successfully removed', volume.Id);
|
||||
var index = $scope.volumes.indexOf(volume);
|
||||
|
@ -36,8 +34,8 @@ angular.module('portainer.docker').controller('VolumesController', [
|
|||
var endpointRole = $scope.applicationState.endpoint.mode.role;
|
||||
|
||||
$q.all({
|
||||
attached: VolumeService.volumes({ filters: { dangling: ['false'] } }),
|
||||
dangling: VolumeService.volumes({ filters: { dangling: ['true'] } }),
|
||||
attached: VolumeService.volumes({ dangling: ['false'] }),
|
||||
dangling: VolumeService.volumes({ dangling: ['true'] }),
|
||||
services: endpointProvider === 'DOCKER_SWARM_MODE' && endpointRole === 'MANAGER' ? ServiceService.services() : [],
|
||||
})
|
||||
.then(function success(data) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import {
|
||||
MaxDockerAPIVersionKey,
|
||||
MaxDockerAPIVersionType,
|
||||
} from './portainer/services/dockerMaxApiVersion';
|
||||
|
||||
export * from 'axios';
|
||||
|
||||
declare module 'axios' {
|
||||
interface CreateAxiosDefaults {
|
||||
/**
|
||||
* require to define a default max Docker API Version when creating an axios instance
|
||||
*/
|
||||
[MaxDockerAPIVersionKey]: MaxDockerAPIVersionType;
|
||||
}
|
||||
|
||||
interface AxiosRequestConfig {
|
||||
/**
|
||||
* represents the maximum Docker API version supported for the request
|
||||
*
|
||||
* the default will be used when not specified in the request config
|
||||
*/
|
||||
[MaxDockerAPIVersionKey]?: MaxDockerAPIVersionType;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { rawResponse } from 'Kubernetes/rest/response/transform';
|
||||
import { logsHandler } from 'Docker/rest/response/handlers';
|
||||
|
||||
angular.module('portainer.kubernetes').factory('KubernetesPods', [
|
||||
'$resource',
|
||||
|
@ -48,3 +47,11 @@ angular.module('portainer.kubernetes').factory('KubernetesPods', [
|
|||
};
|
||||
},
|
||||
]);
|
||||
|
||||
// The Docker API returns the logs as a single string.
|
||||
// This handler wraps the data in a JSON object under the "logs" property.
|
||||
function logsHandler(data) {
|
||||
return {
|
||||
logs: data,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { EndpointProviderInterface } from './endpointProvider';
|
||||
|
||||
// see async.js
|
||||
type AsyncInterface = (
|
||||
asyncFunc: AsyncFunction,
|
||||
...args: unknown[]
|
||||
) => Promise<unknown>;
|
||||
|
||||
type AsyncFunction = (...params: unknown[]) => Promise<unknown>;
|
||||
type AxiosFunction = (
|
||||
environmentId: EnvironmentId | undefined,
|
||||
...params: unknown[]
|
||||
) => Promise<unknown>;
|
||||
|
||||
/* @ngInject */
|
||||
export function AngularToReact(
|
||||
EndpointProvider: EndpointProviderInterface,
|
||||
$async: AsyncInterface
|
||||
) {
|
||||
return { useAxios, injectEnvironmentId };
|
||||
|
||||
/**
|
||||
* Wraps the async axios function with `$async` to ensures the request runs inside the AngularJS digest cycle
|
||||
*
|
||||
* See `$async` (async.js) implementation and notes
|
||||
*
|
||||
* See `AngularToReact.injectEnvironmentId` to solve `environmentId` injection for services functions relying
|
||||
* on `EndpointProvider.endpointID()` in their `$resource()` definition
|
||||
*
|
||||
* @example
|
||||
* **Old AngularJS service**
|
||||
* ```
|
||||
* // file:: AngularJS service.js
|
||||
*
|
||||
* // ngInject
|
||||
* function ServiceServiceFactory($q, Service) {
|
||||
* return { getService };
|
||||
*
|
||||
* // the original signature doesn't have environmentId passed to it
|
||||
* // it relies on EndpointProvider in $resource() definition
|
||||
* // we will inject it on refactor
|
||||
* // the function uses $q, which internally triggers a redraw of the UI when it resolves/rejects
|
||||
* function getService(serviceId) {
|
||||
* var deferred = $q.defer();
|
||||
* [...]
|
||||
* return deferred.promise;
|
||||
* };
|
||||
*
|
||||
* // the original signature has environmentId passed to it
|
||||
* // it doesn't rely on EndpointProvider in $resource() definition
|
||||
* // we won't inject environmentId on refactor
|
||||
* // the function uses $q, which internally triggers a redraw of the UI when it resolves/rejects
|
||||
* function listServices(environmentId) {
|
||||
* var deferred = $q.defer();
|
||||
* [...]
|
||||
* return deferred.promise;
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* **New format**
|
||||
* ```
|
||||
* // file:: '@/react/.../useService.ts'
|
||||
* // this function has `environmentId` as first parameter, which doesn't match the old AngularJS service signature
|
||||
* export async function getService(environmentId: EnvironmentId, serviceId: ServiceId) {
|
||||
* // axios.get()
|
||||
* }
|
||||
* // file:: '@/react/.../useServices.ts'
|
||||
* // this function has `environmentId` as first parameter, which matches the old AngularJS service signature
|
||||
* export async function listServices(environmentId: EnvironmentId, serviceId: ServiceId) {
|
||||
* // axios.get()
|
||||
* }
|
||||
* // file:: AngularJS service.js
|
||||
* import { getService } from '@/react/.../useService.ts';
|
||||
* import { listServices } from '@/react/.../useServices.ts';
|
||||
*
|
||||
* // ngInject
|
||||
* function ServiceServiceFactory(AngularToReact) {
|
||||
* const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||
* return {
|
||||
* // ask to inject environmentId to maintain the old signature
|
||||
* getService: useAxios(injectEnvironmentId(getService)),
|
||||
* // do not ask to inject environmentId as it was already in the old signature
|
||||
* // and is already passed by the caller
|
||||
* listServices: useAxios(listServices),
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function useAxios(axiosFunc: AxiosFunction) {
|
||||
return (...params: unknown[]) =>
|
||||
$async(axiosFunc as AsyncFunction, ...params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the Axios function taking `endpointId` as first param to expose the old service format.
|
||||
*
|
||||
* Leverage injected `EndpointProvider` that was used in the rest file - `$resource()` definition.
|
||||
*
|
||||
* The axios function params **MUST** match the old AngularJS-service ones to use this helper without changing the service calls
|
||||
*
|
||||
* Should be used in conjunction with `AngularToReact.useAxios`
|
||||
*
|
||||
* @example
|
||||
* See `AngularToReact.useAxios`
|
||||
*
|
||||
* @param {(environmentId: EnvironmentId, ...params: unknown[]) => Promise<unknown>} axiosFunc Axios function taking `environmentId` as first param
|
||||
* @returns a function with the old AngularJS signature
|
||||
*/
|
||||
function injectEnvironmentId(axiosFunc: AxiosFunction) {
|
||||
return async (...params: unknown[]) =>
|
||||
axiosFunc(EndpointProvider.endpointID(), ...params);
|
||||
}
|
||||
}
|
|
@ -54,11 +54,11 @@ angular.module('portainer.app').factory('StackService', [
|
|||
SwarmService.swarm(targetEndpointId)
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
if (swarm.Id === stack.SwarmId) {
|
||||
if (swarm.ID === stack.SwarmId) {
|
||||
deferred.reject({ msg: 'Target environment is located in the same Swarm cluster as the current environment', err: null });
|
||||
return;
|
||||
}
|
||||
return Stack.migrate({ id: stack.Id, endpointId: stack.EndpointId }, { EndpointID: targetEndpointId, SwarmID: swarm.Id, Name: newName }).$promise;
|
||||
return Stack.migrate({ id: stack.Id, endpointId: stack.EndpointId }, { EndpointID: targetEndpointId, SwarmID: swarm.ID, Name: newName }).$promise;
|
||||
})
|
||||
.then(function success() {
|
||||
deferred.resolve();
|
||||
|
@ -182,10 +182,10 @@ angular.module('portainer.app').factory('StackService', [
|
|||
service.swarmStacks = function (endpointId, includeExternalStacks, filters = {}) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
SwarmService.swarm(endpointId)
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
filters = { SwarmID: swarm.Id, ...filters };
|
||||
filters = { SwarmID: swarm.ID, ...filters };
|
||||
|
||||
return $q.all({
|
||||
stacks: Stack.query({ filters: filters }).$promise,
|
||||
|
@ -239,10 +239,10 @@ angular.module('portainer.app').factory('StackService', [
|
|||
var deferred = $q.defer();
|
||||
|
||||
if (stack.Type == 1) {
|
||||
SwarmService.swarm()
|
||||
SwarmService.swarm(endpointId)
|
||||
.then(function success(data) {
|
||||
const swarm = data;
|
||||
return Stack.associate({ id: stack.Id, endpointId: endpointId, swarmId: swarm.Id, orphanedRunning }).$promise;
|
||||
return Stack.associate({ id: stack.Id, endpointId: endpointId, swarmId: swarm.ID, orphanedRunning }).$promise;
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
|
@ -305,10 +305,10 @@ angular.module('portainer.app').factory('StackService', [
|
|||
service.createSwarmStackFromFileUpload = function (name, stackFile, env, endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
SwarmService.swarm(endpointId)
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
return FileUploadService.createSwarmStack(name, swarm.Id, stackFile, env, endpointId);
|
||||
return FileUploadService.createSwarmStack(name, swarm.ID, stackFile, env, endpointId);
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data.data);
|
||||
|
@ -335,7 +335,7 @@ angular.module('portainer.app').factory('StackService', [
|
|||
.then(function success(swarm) {
|
||||
var payload = {
|
||||
Name: name,
|
||||
SwarmID: swarm.Id,
|
||||
SwarmID: swarm.ID,
|
||||
StackFileContent: stackFileContent,
|
||||
Env: env,
|
||||
};
|
||||
|
@ -376,12 +376,12 @@ angular.module('portainer.app').factory('StackService', [
|
|||
service.createSwarmStackFromGitRepository = function (name, repositoryOptions, env, endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
SwarmService.swarm(endpointId)
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
var payload = {
|
||||
Name: name,
|
||||
SwarmID: swarm.Id,
|
||||
SwarmID: swarm.ID,
|
||||
RepositoryURL: repositoryOptions.RepositoryURL,
|
||||
RepositoryReferenceName: repositoryOptions.RepositoryReferenceName,
|
||||
ComposeFile: repositoryOptions.ComposeFilePathInRepository,
|
||||
|
|
|
@ -21,6 +21,8 @@ import {
|
|||
portainerAgentManagerOperation,
|
||||
portainerAgentTargetHeader,
|
||||
} from './http-request.helper';
|
||||
import { dockerMaxAPIVersionInterceptor } from './dockerMaxApiVersionInterceptor';
|
||||
import { MAX_DOCKER_API_VERSION } from './dockerMaxApiVersion';
|
||||
|
||||
const portainerCacheHeader = 'X-Portainer-Cache';
|
||||
|
||||
|
@ -48,7 +50,10 @@ function headerInterpreter(
|
|||
return 'not enough headers';
|
||||
}
|
||||
|
||||
const axios = Axios.create({ baseURL: 'api' });
|
||||
const axios = Axios.create({
|
||||
baseURL: 'api',
|
||||
maxDockerAPIVersion: MAX_DOCKER_API_VERSION,
|
||||
});
|
||||
axios.interceptors.request.use((req) => {
|
||||
dispatchCacheRefreshEventIfNeeded(req);
|
||||
return req;
|
||||
|
@ -118,13 +123,14 @@ export function agentInterceptor(config: InternalAxiosRequestConfig) {
|
|||
return newConfig;
|
||||
}
|
||||
|
||||
axios.interceptors.request.use(dockerMaxAPIVersionInterceptor);
|
||||
axios.interceptors.request.use(agentInterceptor);
|
||||
|
||||
axios.interceptors.response.use(undefined, (error) => {
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
!error.config.url.includes('/v2/') &&
|
||||
!error.config.url.includes('/api/v4/') &&
|
||||
!error.config.url.includes('/v2/') && // docker proxy through agent
|
||||
!error.config.url.includes('/api/v4/') && // gitlab proxy
|
||||
isTransitionRequiresAuthentication()
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -188,6 +194,12 @@ export function defaultErrorParser(axiosError: AxiosError<unknown>) {
|
|||
const error = new Error(message);
|
||||
return { error, details };
|
||||
}
|
||||
if (isArrayResponse(axiosError.response?.data)) {
|
||||
const message = axiosError.response?.data[0].message || '';
|
||||
const details = axiosError.response?.data[0].details || message;
|
||||
const error = new Error(message);
|
||||
return { error, details };
|
||||
}
|
||||
|
||||
const details = axiosError.response?.data
|
||||
? axiosError.response?.data.toString()
|
||||
|
@ -196,6 +208,16 @@ export function defaultErrorParser(axiosError: AxiosError<unknown>) {
|
|||
return { error, details };
|
||||
}
|
||||
|
||||
// handle jsonObjectsToArrayHandler transformation
|
||||
function isArrayResponse(data: unknown): data is DefaultAxiosErrorType[] {
|
||||
return (
|
||||
!!data &&
|
||||
Array.isArray(data) &&
|
||||
'message' in data[0] &&
|
||||
typeof data[0].message === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export function isDefaultResponse(
|
||||
data: unknown
|
||||
): data is DefaultAxiosErrorType {
|
||||
|
@ -249,3 +271,18 @@ export function json2formData(json: Record<string, unknown>) {
|
|||
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Docker API often returns a list of JSON object.
|
||||
* This handler wrap the JSON objects in an array.
|
||||
* @param data Raw docker API response (stream of objects in a single string)
|
||||
* @returns An array of parsed objects
|
||||
*/
|
||||
export function jsonObjectsToArrayHandler(data: string): unknown[] {
|
||||
// catching empty data helps the function not to fail and prevents unwanted error message to user.
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
const str = `[${data.replace(/\n/g, ' ').replace(/\}\s*\{/g, '}, {')}]`;
|
||||
return JSON.parse(str);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// Key used in axios types definitions
|
||||
export const MaxDockerAPIVersionKey = 'maxDockerAPIVersion' as const;
|
||||
|
||||
export type DockerAPIVersionType = number;
|
||||
|
||||
// this is the version we are using with the generated API types
|
||||
export const MAX_DOCKER_API_VERSION: DockerAPIVersionType = 1.41;
|
||||
|
||||
// https://docs.docker.com/engine/api/#api-version-matrix
|
||||
// Docker 26 = API 1.45
|
||||
export const LATEST_DOCKER_API_VERSION: DockerAPIVersionType = 1.45;
|
|
@ -0,0 +1,64 @@
|
|||
import { SystemVersion } from 'docker-types/generated/1.41';
|
||||
import Axios, { InternalAxiosRequestConfig } from 'axios';
|
||||
import { setupCache, buildMemoryStorage } from 'axios-cache-interceptor';
|
||||
|
||||
import { buildDockerProxyUrl } from '@/react/docker/proxy/queries/buildDockerProxyUrl';
|
||||
|
||||
import PortainerError from '../error';
|
||||
|
||||
import { MAX_DOCKER_API_VERSION } from './dockerMaxApiVersion';
|
||||
|
||||
const envVersionAxios = Axios.create({
|
||||
baseURL: 'api',
|
||||
maxDockerAPIVersion: MAX_DOCKER_API_VERSION,
|
||||
});
|
||||
|
||||
// setup a cache for the intermediary request sent by the interceptor
|
||||
const envVersionCache = buildMemoryStorage();
|
||||
setupCache(envVersionAxios, {
|
||||
storage: envVersionCache,
|
||||
ttl: 5 * 60 * 1000,
|
||||
methods: ['get'],
|
||||
});
|
||||
|
||||
export async function dockerMaxAPIVersionInterceptor(
|
||||
rawConfig: InternalAxiosRequestConfig
|
||||
) {
|
||||
try {
|
||||
const config = rawConfig;
|
||||
const found = config.url?.match(
|
||||
/endpoints\/(?<environmentId>\d+)\/docker\//
|
||||
);
|
||||
|
||||
if (found && found.groups) {
|
||||
const { environmentId } = found.groups;
|
||||
const envId = parseInt(environmentId, 10);
|
||||
|
||||
// if we cannot parse the env ID, don't send a request that will fail,
|
||||
// exit the interceptor and let the original request config pass through
|
||||
if (Number.isNaN(envId)) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const { data } = await envVersionAxios.get<SystemVersion>(
|
||||
buildDockerProxyUrl(envId, 'version')
|
||||
);
|
||||
|
||||
const apiVersion = parseFloat(data.ApiVersion ?? '0');
|
||||
const { maxDockerAPIVersion } = config;
|
||||
|
||||
if (apiVersion > maxDockerAPIVersion) {
|
||||
config.url = config.url?.replace(
|
||||
/docker/,
|
||||
`docker/v${maxDockerAPIVersion}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
} catch (err) {
|
||||
throw new PortainerError(
|
||||
'An error occurred while trying to limit request to the maximum supported Docker API version',
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { ping } from '@/docker/services/ping';
|
||||
import { ping } from '@/react/docker/proxy/queries/usePing';
|
||||
import { environmentStore } from '@/react/hooks/current-environment-store';
|
||||
import {
|
||||
Environment,
|
||||
|
|
|
@ -1,238 +1,209 @@
|
|||
import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models';
|
||||
import { genericHandler, jsonObjectsToArrayHandler } from '../../docker/rest/response/handlers';
|
||||
|
||||
angular.module('portainer.app').factory('FileUploadService', [
|
||||
'$q',
|
||||
'Upload',
|
||||
'EndpointProvider',
|
||||
function FileUploadFactory($q, Upload, EndpointProvider) {
|
||||
'use strict';
|
||||
angular.module('portainer.app').factory('FileUploadService', FileUploadFactory);
|
||||
|
||||
var service = {};
|
||||
/* @ngInject */
|
||||
function FileUploadFactory($q, Upload) {
|
||||
var service = {
|
||||
// createSchedule, // edge jobs service
|
||||
// uploadBackup, // backup service
|
||||
// createSwarmStack, // stack service
|
||||
// createComposeStack, // stack service
|
||||
// createEdgeStack, // edge stack service
|
||||
// createCustomTemplate, // custom template service
|
||||
// configureRegistry, // registry service
|
||||
// createEndpoint, // endpoint service
|
||||
// createAzureEndpoint, // endpoint service
|
||||
// uploadLDAPTLSFiles, // auth settings controller
|
||||
// uploadTLSFilesForEndpoint, // endpoint service
|
||||
// uploadOwnershipVoucher, // import device controller
|
||||
};
|
||||
|
||||
function uploadFile(url, file) {
|
||||
return Upload.upload({ url: url, data: { file: file } });
|
||||
function uploadFile(url, file) {
|
||||
return Upload.upload({ url: url, data: { file: file } });
|
||||
}
|
||||
|
||||
service.createSchedule = function (payload) {
|
||||
return Upload.upload({
|
||||
url: 'api/edge_jobs/create/file',
|
||||
data: {
|
||||
file: payload.File,
|
||||
Name: payload.Name,
|
||||
CronExpression: payload.CronExpression,
|
||||
Image: payload.Image,
|
||||
Endpoints: Upload.json(payload.Endpoints),
|
||||
RetryCount: payload.RetryCount,
|
||||
RetryInterval: payload.RetryInterval,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
service.uploadBackup = function (file, password) {
|
||||
return Upload.upload({
|
||||
url: 'api/restore',
|
||||
data: {
|
||||
file,
|
||||
password,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
service.createSwarmStack = function (stackName, swarmId, file, env, endpointId, webhook) {
|
||||
return Upload.upload({
|
||||
url: `api/stacks/create/swarm/file?endpointId=${endpointId}`,
|
||||
data: {
|
||||
file: file,
|
||||
Name: stackName,
|
||||
SwarmID: swarmId,
|
||||
Env: Upload.json(env),
|
||||
Webhook: webhook,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createComposeStack = function (stackName, file, env, endpointId, webhook) {
|
||||
return Upload.upload({
|
||||
url: `api/stacks/create/standalone/file?endpointId=${endpointId}`,
|
||||
data: {
|
||||
file: file,
|
||||
Name: stackName,
|
||||
Env: Upload.json(env),
|
||||
Webhook: webhook,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createEdgeStack = function createEdgeStack({ EdgeGroups, Registries, envVars, staggerConfig, ...payload }, file, dryrun) {
|
||||
return Upload.upload({
|
||||
url: `api/edge_stacks/create/file?dryrun=${dryrun}`,
|
||||
data: {
|
||||
file,
|
||||
EdgeGroups: Upload.json(EdgeGroups),
|
||||
Registries: Upload.json(Registries),
|
||||
EnvVars: Upload.json(envVars),
|
||||
StaggerConfig: Upload.json(staggerConfig),
|
||||
...payload,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createCustomTemplate = function createCustomTemplate(data) {
|
||||
return Upload.upload({
|
||||
url: 'api/custom_templates/create/file',
|
||||
data,
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.configureRegistry = function (registryId, registryManagementConfigurationModel) {
|
||||
return Upload.upload({
|
||||
url: 'api/registries/' + registryId + '/configure',
|
||||
data: registryManagementConfigurationModel,
|
||||
});
|
||||
};
|
||||
|
||||
service.createEndpoint = function (
|
||||
name,
|
||||
creationType,
|
||||
URL,
|
||||
PublicURL,
|
||||
groupID,
|
||||
tagIds,
|
||||
TLS,
|
||||
TLSSkipVerify,
|
||||
TLSSkipClientVerify,
|
||||
TLSCAFile,
|
||||
TLSCertFile,
|
||||
TLSKeyFile,
|
||||
checkinInterval,
|
||||
EdgePingInterval,
|
||||
EdgeSnapshotInterval,
|
||||
EdgeCommandInterval
|
||||
) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
Name: name,
|
||||
EndpointCreationType: creationType,
|
||||
URL: URL,
|
||||
PublicURL: PublicURL,
|
||||
GroupID: groupID,
|
||||
TagIds: Upload.json(tagIds),
|
||||
TLS: TLS,
|
||||
TLSSkipVerify: TLSSkipVerify,
|
||||
TLSSkipClientVerify: TLSSkipClientVerify,
|
||||
TLSCACertFile: TLSCAFile,
|
||||
TLSCertFile: TLSCertFile,
|
||||
TLSKeyFile: TLSKeyFile,
|
||||
CheckinInterval: checkinInterval,
|
||||
EdgePingInterval: EdgePingInterval,
|
||||
EdgeSnapshotInterval: EdgeSnapshotInterval,
|
||||
EdgeCommandInterval: EdgeCommandInterval,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createAzureEndpoint = function (name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
Name: name,
|
||||
EndpointCreationType: PortainerEndpointCreationTypes.AzureEnvironment,
|
||||
GroupID: groupId,
|
||||
TagIds: Upload.json(tagIds),
|
||||
AzureApplicationID: applicationId,
|
||||
AzureTenantID: tenantId,
|
||||
AzureAuthenticationKey: authenticationKey,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.uploadLDAPTLSFiles = function (TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var queue = [];
|
||||
|
||||
if (TLSCAFile) {
|
||||
queue.push(uploadFile('api/upload/tls/ca?folder=ldap', TLSCAFile));
|
||||
}
|
||||
if (TLSCertFile) {
|
||||
queue.push(uploadFile('api/upload/tls/cert?folder=ldap', TLSCertFile));
|
||||
}
|
||||
if (TLSKeyFile) {
|
||||
queue.push(uploadFile('api/upload/tls/key?folder=ldap', TLSKeyFile));
|
||||
}
|
||||
|
||||
service.buildImage = function (endpointID, names, file, path) {
|
||||
return Upload.http({
|
||||
url: `api/endpoints/${endpointID}/docker/build`,
|
||||
headers: {
|
||||
'Content-Type': file.type,
|
||||
},
|
||||
data: file,
|
||||
params: {
|
||||
t: names,
|
||||
dockerfile: path,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
transformResponse: function (data) {
|
||||
return jsonObjectsToArrayHandler(data);
|
||||
},
|
||||
});
|
||||
};
|
||||
return $q.all(queue);
|
||||
};
|
||||
|
||||
service.buildImageFromFiles = function (endpointID, names, files) {
|
||||
return Upload.upload({
|
||||
url: `api/endpoints/${endpointID}/docker/build`,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
data: { file: files },
|
||||
params: {
|
||||
t: names,
|
||||
},
|
||||
transformResponse: function (data) {
|
||||
return jsonObjectsToArrayHandler(data);
|
||||
},
|
||||
});
|
||||
};
|
||||
service.uploadTLSFilesForEndpoint = function (endpointID, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var queue = [];
|
||||
|
||||
service.loadImages = function (file) {
|
||||
var endpointID = EndpointProvider.endpointID();
|
||||
return Upload.http({
|
||||
url: 'api/endpoints/' + endpointID + '/docker/images/load',
|
||||
headers: {
|
||||
'Content-Type': file.type,
|
||||
},
|
||||
data: file,
|
||||
ignoreLoadingBar: true,
|
||||
transformResponse: genericHandler,
|
||||
});
|
||||
};
|
||||
if (TLSCAFile) {
|
||||
queue.push(uploadFile('api/upload/tls/ca?folder=' + endpointID, TLSCAFile));
|
||||
}
|
||||
if (TLSCertFile) {
|
||||
queue.push(uploadFile('api/upload/tls/cert?folder=' + endpointID, TLSCertFile));
|
||||
}
|
||||
if (TLSKeyFile) {
|
||||
queue.push(uploadFile('api/upload/tls/key?folder=' + endpointID, TLSKeyFile));
|
||||
}
|
||||
|
||||
service.createSchedule = function (payload) {
|
||||
return Upload.upload({
|
||||
url: 'api/edge_jobs/create/file',
|
||||
data: {
|
||||
file: payload.File,
|
||||
Name: payload.Name,
|
||||
CronExpression: payload.CronExpression,
|
||||
Image: payload.Image,
|
||||
Endpoints: Upload.json(payload.Endpoints),
|
||||
RetryCount: payload.RetryCount,
|
||||
RetryInterval: payload.RetryInterval,
|
||||
},
|
||||
});
|
||||
};
|
||||
return $q.all(queue);
|
||||
};
|
||||
|
||||
service.uploadBackup = function (file, password) {
|
||||
return Upload.upload({
|
||||
url: 'api/restore',
|
||||
data: {
|
||||
file,
|
||||
password,
|
||||
},
|
||||
});
|
||||
};
|
||||
service.uploadOwnershipVoucher = function (voucherFile) {
|
||||
return Upload.upload({
|
||||
url: 'api/fdo/register',
|
||||
data: {
|
||||
voucher: voucherFile,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createSwarmStack = function (stackName, swarmId, file, env, endpointId) {
|
||||
return Upload.upload({
|
||||
url: `api/stacks/create/swarm/file?endpointId=${endpointId}`,
|
||||
data: {
|
||||
file: file,
|
||||
Name: stackName,
|
||||
SwarmID: swarmId,
|
||||
Env: Upload.json(env),
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createComposeStack = function (stackName, file, env, endpointId) {
|
||||
return Upload.upload({
|
||||
url: `api/stacks/create/standalone/file?endpointId=${endpointId}`,
|
||||
data: {
|
||||
file: file,
|
||||
Name: stackName,
|
||||
Env: Upload.json(env),
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createEdgeStack = function createEdgeStack({ EdgeGroups, envVars, ...payload }, file) {
|
||||
return Upload.upload({
|
||||
url: `api/edge_stacks/create/file`,
|
||||
data: {
|
||||
file,
|
||||
EdgeGroups: Upload.json(EdgeGroups),
|
||||
EnvVars: Upload.json(envVars),
|
||||
...payload,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createCustomTemplate = function createCustomTemplate(data) {
|
||||
return Upload.upload({
|
||||
url: 'api/custom_templates/create/file',
|
||||
data,
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.configureRegistry = function (registryId, registryManagementConfigurationModel) {
|
||||
return Upload.upload({
|
||||
url: 'api/registries/' + registryId + '/configure',
|
||||
data: registryManagementConfigurationModel,
|
||||
});
|
||||
};
|
||||
|
||||
service.createEndpoint = function (
|
||||
name,
|
||||
creationType,
|
||||
URL,
|
||||
PublicURL,
|
||||
groupID,
|
||||
tagIds,
|
||||
TLS,
|
||||
TLSSkipVerify,
|
||||
TLSSkipClientVerify,
|
||||
TLSCAFile,
|
||||
TLSCertFile,
|
||||
TLSKeyFile,
|
||||
checkinInterval
|
||||
) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
Name: name,
|
||||
EndpointCreationType: creationType,
|
||||
URL: URL,
|
||||
PublicURL: PublicURL,
|
||||
GroupID: groupID,
|
||||
TagIds: Upload.json(tagIds),
|
||||
TLS: TLS,
|
||||
TLSSkipVerify: TLSSkipVerify,
|
||||
TLSSkipClientVerify: TLSSkipClientVerify,
|
||||
TLSCACertFile: TLSCAFile,
|
||||
TLSCertFile: TLSCertFile,
|
||||
TLSKeyFile: TLSKeyFile,
|
||||
CheckinInterval: checkinInterval,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.createAzureEndpoint = function (name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
Name: name,
|
||||
EndpointCreationType: PortainerEndpointCreationTypes.AzureEnvironment,
|
||||
GroupID: groupId,
|
||||
TagIds: Upload.json(tagIds),
|
||||
AzureApplicationID: applicationId,
|
||||
AzureTenantID: tenantId,
|
||||
AzureAuthenticationKey: authenticationKey,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
service.uploadLDAPTLSFiles = function (TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var queue = [];
|
||||
|
||||
if (TLSCAFile) {
|
||||
queue.push(uploadFile('api/upload/tls/ca?folder=ldap', TLSCAFile));
|
||||
}
|
||||
if (TLSCertFile) {
|
||||
queue.push(uploadFile('api/upload/tls/cert?folder=ldap', TLSCertFile));
|
||||
}
|
||||
if (TLSKeyFile) {
|
||||
queue.push(uploadFile('api/upload/tls/key?folder=ldap', TLSKeyFile));
|
||||
}
|
||||
|
||||
return $q.all(queue);
|
||||
};
|
||||
|
||||
service.uploadTLSFilesForEndpoint = function (endpointID, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var queue = [];
|
||||
|
||||
if (TLSCAFile) {
|
||||
queue.push(uploadFile('api/upload/tls/ca?folder=' + endpointID, TLSCAFile));
|
||||
}
|
||||
if (TLSCertFile) {
|
||||
queue.push(uploadFile('api/upload/tls/cert?folder=' + endpointID, TLSCertFile));
|
||||
}
|
||||
if (TLSKeyFile) {
|
||||
queue.push(uploadFile('api/upload/tls/key?folder=' + endpointID, TLSKeyFile));
|
||||
}
|
||||
|
||||
return $q.all(queue);
|
||||
};
|
||||
|
||||
service.uploadOwnershipVoucher = function (voucherFile) {
|
||||
return Upload.upload({
|
||||
url: 'api/fdo/register',
|
||||
data: {
|
||||
voucher: voucherFile,
|
||||
},
|
||||
ignoreLoadingBar: true,
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
return service;
|
||||
}
|
||||
|
|
|
@ -4,9 +4,11 @@ import { apiServicesModule } from './api';
|
|||
import { Notifications } from './notifications';
|
||||
import { HttpRequestHelperAngular } from './http-request.helper';
|
||||
import { EndpointProvider } from './endpointProvider';
|
||||
import { AngularToReact } from './angularToReact';
|
||||
|
||||
export default angular
|
||||
.module('portainer.app.services', [apiServicesModule])
|
||||
.factory('Notifications', Notifications)
|
||||
.factory('EndpointProvider', EndpointProvider)
|
||||
.factory('HttpRequestHelper', HttpRequestHelperAngular).name;
|
||||
.factory('HttpRequestHelper', HttpRequestHelperAngular)
|
||||
.factory('AngularToReact', AngularToReact).name;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { AccessControlFormData } from '@/react/portainer/access-control/types';
|
||||
import { PortainerMetadata } from '@/react/docker/types';
|
||||
import { PortainerResponse } from '@/react/docker/types';
|
||||
|
||||
import { PortMapping } from './container-instances/CreateView/PortsMappingField';
|
||||
|
||||
|
@ -47,14 +47,13 @@ interface ContainerGroupProperties {
|
|||
osType: OS;
|
||||
}
|
||||
|
||||
export type ContainerGroup = {
|
||||
export type ContainerGroup = PortainerResponse<{
|
||||
id: string;
|
||||
name: string;
|
||||
location: string;
|
||||
type: string;
|
||||
properties: ContainerGroupProperties;
|
||||
Portainer?: PortainerMetadata;
|
||||
};
|
||||
}>;
|
||||
|
||||
export interface Subscription {
|
||||
subscriptionId: string;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Box } from 'lucide-react';
|
||||
|
||||
import { DockerContainer } from '@/react/docker/containers/types';
|
||||
import { ContainerListViewModel } from '@/react/docker/containers/types';
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import { createStore } from '@/react/docker/containers/ListView/ContainersDatatable/datatable-store';
|
||||
import { useColumns } from '@/react/docker/containers/ListView/ContainersDatatable/columns';
|
||||
|
@ -20,7 +20,7 @@ import {
|
|||
import { TableSettingsProvider } from '@@/datatables/useTableSettings';
|
||||
import { useTableState } from '@@/datatables/useTableState';
|
||||
|
||||
import { useContainers } from '../../../docker/containers/queries/containers';
|
||||
import { useContainers } from '../../../docker/containers/queries/useContainers';
|
||||
import { RowProvider } from '../../../docker/containers/ListView/ContainersDatatable/RowContext';
|
||||
|
||||
const storageKey = 'stack-containers';
|
||||
|
@ -71,7 +71,7 @@ export function StackContainersDatatable({ environment, stackName }: Props) {
|
|||
data-cy="stack-containers-datatable"
|
||||
renderTableSettings={(tableInstance) => (
|
||||
<>
|
||||
<ColumnVisibilityMenu<DockerContainer>
|
||||
<ColumnVisibilityMenu<ContainerListViewModel>
|
||||
table={tableInstance}
|
||||
onChange={(hiddenColumns) => {
|
||||
tableState.setHiddenColumns(hiddenColumns);
|
||||
|
|
|
@ -4,7 +4,7 @@ import axios, { parseAxiosError } from '@/portainer/services/axios';
|
|||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { queryKeys } from '../queries/utils';
|
||||
import { buildDockerUrl } from '../queries/utils/root';
|
||||
import { buildDockerUrl } from '../queries/utils/buildDockerUrl';
|
||||
|
||||
interface DashboardResponse {
|
||||
containers: {
|
||||
|
@ -29,7 +29,7 @@ export function useDashboard(envId: EnvironmentId) {
|
|||
queryFn: async () => {
|
||||
try {
|
||||
const res = await axios.get<DashboardResponse>(
|
||||
`${buildDockerUrl(envId)}/dashboard`
|
||||
buildDockerUrl(envId, 'dashboard')
|
||||
);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import axios, {
|
|||
} from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildUrl } from '../../proxy/queries/build-url';
|
||||
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
|
||||
|
||||
export function useApiVersion(environmentId: EnvironmentId) {
|
||||
return useQuery(['environment', environmentId, 'agent', 'ping'], () =>
|
||||
|
@ -16,7 +16,9 @@ export function useApiVersion(environmentId: EnvironmentId) {
|
|||
|
||||
async function getApiVersion(environmentId: EnvironmentId) {
|
||||
try {
|
||||
const { headers } = await axios.get(buildUrl(environmentId, 'ping'));
|
||||
const { headers } = await axios.get(
|
||||
buildDockerProxyUrl(environmentId, 'ping')
|
||||
);
|
||||
return parseInt(headers['portainer-agent-api-version'], 10) || 1;
|
||||
} catch (error) {
|
||||
// 404 - agent is up - set version to 1
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { Config } from 'docker-types/generated/1.41';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
|
||||
import { DockerConfig } from '../types';
|
||||
|
||||
export async function getConfig(
|
||||
environmentId: EnvironmentId,
|
||||
configId: DockerConfig['Id']
|
||||
) {
|
||||
try {
|
||||
const { data } = await axios.get<Config>(
|
||||
buildDockerProxyUrl(environmentId, 'configs', configId)
|
||||
);
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e, 'Unable to retrieve config');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { Config } from 'docker-types/generated/1.41';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
|
||||
|
||||
export async function getConfigs(environmentId: EnvironmentId) {
|
||||
try {
|
||||
const { data } = await axios.get<Config[]>(
|
||||
buildDockerProxyUrl(environmentId, 'configs')
|
||||
);
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e, 'Unable to retrieve configs');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { ConfigSpec } from 'docker-types/generated/1.41';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
|
||||
import { PortainerResponse } from '../../types';
|
||||
|
||||
export async function createConfig(
|
||||
environmentId: EnvironmentId,
|
||||
config: ConfigSpec
|
||||
) {
|
||||
try {
|
||||
const { data } = await axios.post<PortainerResponse<{ Id: string }>>(
|
||||
buildDockerProxyUrl(environmentId, 'configs', 'create'),
|
||||
config
|
||||
);
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e, 'Unable to create config');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { DockerConfig } from '../types';
|
||||
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
|
||||
|
||||
export async function deleteConfig(
|
||||
environmentId: EnvironmentId,
|
||||
id: DockerConfig['Id']
|
||||
) {
|
||||
try {
|
||||
await axios.delete(buildDockerProxyUrl(environmentId, 'configs', id));
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e, 'Unable to delete config');
|
||||
}
|
||||
}
|
|
@ -3,13 +3,13 @@ import { ResourceControlOwnership } from '@/react/portainer/access-control/types
|
|||
import { UserId } from '@/portainer/users/types';
|
||||
import { getDefaultImageConfig } from '@/react/portainer/registries/utils/getImageConfig';
|
||||
|
||||
import { ContainerResponse } from '../../queries/container';
|
||||
import { ContainerDetailsResponse } from '../../queries/useContainer';
|
||||
|
||||
import { toViewModel as toPortsMappingViewModel } from './PortsMappingField.viewModel';
|
||||
import { Values } from './BaseForm';
|
||||
|
||||
export function toViewModel(
|
||||
config: ContainerResponse,
|
||||
config: ContainerDetailsResponse,
|
||||
isPureAdmin: boolean,
|
||||
currentUserId: UserId,
|
||||
nodeName: string,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ContainerJSON } from '@/react/docker/containers/queries/container';
|
||||
import { ContainerDetailsJSON } from '@/react/docker/containers/queries/useContainer';
|
||||
|
||||
import { capabilities } from './types';
|
||||
import { Values } from './CapabilitiesTab';
|
||||
|
||||
export function toViewModel(config: ContainerJSON): Values {
|
||||
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||
const { CapAdd, CapDrop } = getDefaults(config);
|
||||
|
||||
const missingCaps = capabilities
|
||||
|
@ -15,7 +15,7 @@ export function toViewModel(config: ContainerJSON): Values {
|
|||
|
||||
return [...CapAdd, ...missingCaps];
|
||||
|
||||
function getDefaults(config: ContainerJSON) {
|
||||
function getDefaults(config: ContainerDetailsJSON) {
|
||||
return {
|
||||
CapAdd: config.HostConfig?.CapAdd || [],
|
||||
CapDrop: config.HostConfig?.CapDrop || [],
|
||||
|
|
|
@ -2,7 +2,7 @@ import { HostConfig } from 'docker-types/generated/1.41';
|
|||
|
||||
import { commandArrayToString } from '@/docker/helpers/containers';
|
||||
|
||||
import { ContainerJSON } from '../../queries/container';
|
||||
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||
|
||||
import { ConsoleConfig, ConsoleSetting } from './ConsoleSettings';
|
||||
import { LogConfig } from './LoggerConfig';
|
||||
|
@ -19,7 +19,7 @@ export function getDefaultViewModel(): Values {
|
|||
};
|
||||
}
|
||||
|
||||
export function toViewModel(config: ContainerJSON): Values {
|
||||
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||
if (!config.Config) {
|
||||
return getDefaultViewModel();
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { InformationPanel } from '@@/InformationPanel';
|
|||
import { TextTip } from '@@/Tip/TextTip';
|
||||
import { HelpLink } from '@@/HelpLink';
|
||||
|
||||
import { useContainers } from '../queries/containers';
|
||||
import { useContainers } from '../queries/useContainers';
|
||||
import { useSystemLimits, useIsWindows } from '../../proxy/queries/useInfo';
|
||||
|
||||
import { useCreateOrReplaceMutation } from './useCreateMutation';
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { parseArrayOfStrings } from '@@/form-components/EnvironmentVariablesFieldset/utils';
|
||||
|
||||
import { ContainerJSON } from '../../queries/container';
|
||||
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||
|
||||
export function getDefaultViewModel() {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function toViewModel(container: ContainerJSON) {
|
||||
export function toViewModel(container: ContainerDetailsJSON) {
|
||||
return parseArrayOfStrings(container.Config?.Env);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ContainerJSON } from '../../queries/container';
|
||||
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||
|
||||
import { Values } from './types';
|
||||
|
||||
export function toViewModel(config: ContainerJSON): Values {
|
||||
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||
if (!config || !config.Config || !config.Config.Labels) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|||
|
||||
import { Option, PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||
|
||||
import { useContainers } from '../../queries/containers';
|
||||
import { useContainers } from '../../queries/useContainers';
|
||||
import { ContainerStatus } from '../../types';
|
||||
|
||||
export function ContainerSelector({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { DockerNetwork } from '@/react/docker/networks/types';
|
||||
|
||||
import { ContainerJSON } from '../../queries/container';
|
||||
import { DockerContainer } from '../../types';
|
||||
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||
import { ContainerListViewModel } from '../../types';
|
||||
|
||||
import { CONTAINER_MODE, Values } from './types';
|
||||
|
||||
|
@ -22,9 +22,9 @@ export function getDefaultViewModel(isWindows: boolean) {
|
|||
}
|
||||
|
||||
export function toViewModel(
|
||||
config: ContainerJSON,
|
||||
config: ContainerDetailsJSON,
|
||||
networks: Array<DockerNetwork>,
|
||||
runningContainers: Array<DockerContainer> = []
|
||||
runningContainers: Array<ContainerListViewModel> = []
|
||||
): Values {
|
||||
const dns = config.HostConfig?.Dns;
|
||||
const [primaryDns = '', secondaryDns = ''] = dns || [];
|
||||
|
@ -62,9 +62,9 @@ export function toViewModel(
|
|||
}
|
||||
|
||||
function getNetworkMode(
|
||||
config: ContainerJSON,
|
||||
config: ContainerDetailsJSON,
|
||||
networks: Array<DockerNetwork>,
|
||||
runningContainers: Array<DockerContainer> = []
|
||||
runningContainers: Array<ContainerListViewModel> = []
|
||||
) {
|
||||
let networkMode = config.HostConfig?.NetworkMode || '';
|
||||
if (!networkMode) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useCurrentStateAndParams } from '@uirouter/react';
|
|||
import { useState } from 'react';
|
||||
import { FormikHelpers } from 'formik/dist/types';
|
||||
|
||||
import { invalidateContainer } from '@/react/docker/containers/queries/container';
|
||||
import { invalidateContainer } from '@/react/docker/containers/queries/useContainer';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { mutationOptions, withError } from '@/react-tools/react-query';
|
||||
import { useSystemLimits } from '@/react/docker/proxy/queries/useInfo';
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ContainerJSON } from '../../queries/container';
|
||||
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||
|
||||
import { toDevicesViewModel } from './DevicesField';
|
||||
import { gpuFieldsetUtils } from './GpuFieldset';
|
||||
import { toViewModelCpu, toViewModelMemory } from './memory-utils';
|
||||
import { Values } from './ResourcesTab';
|
||||
|
||||
export function toViewModel(config: ContainerJSON): Values {
|
||||
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||
return {
|
||||
runtime: {
|
||||
privileged: config.HostConfig?.Privileged || false,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ContainerJSON } from '../../queries/container';
|
||||
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||
|
||||
import { RestartPolicy } from './types';
|
||||
|
||||
export function toViewModel(config: ContainerJSON): RestartPolicy {
|
||||
export function toViewModel(config: ContainerDetailsJSON): RestartPolicy {
|
||||
switch (config.HostConfig?.RestartPolicy?.Name) {
|
||||
case 'always':
|
||||
return RestartPolicy.Always;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ContainerJSON } from '../../queries/container';
|
||||
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||
|
||||
import { VolumeType, Values } from './types';
|
||||
|
||||
export function toViewModel(config: ContainerJSON): Values {
|
||||
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||
return Object.values(config.Mounts || {}).map((mount) => ({
|
||||
type: (mount.Type || 'volume') as VolumeType,
|
||||
name: mount.Name || mount.Source || '',
|
||||
|
|
|
@ -31,13 +31,13 @@ import {
|
|||
renameContainer,
|
||||
startContainer,
|
||||
stopContainer,
|
||||
urlBuilder,
|
||||
} from '../containers.service';
|
||||
import { PortainerResponse } from '../../types';
|
||||
import { connectContainer } from '../../networks/queries/useConnectContainer';
|
||||
import { DockerContainer } from '../types';
|
||||
import { connectContainer } from '../../networks/queries/useConnectContainerMutation';
|
||||
import { ContainerListViewModel } from '../types';
|
||||
import { queryKeys } from '../queries/query-keys';
|
||||
import { addNodeHeader } from '../../proxy/addNodeHeader';
|
||||
import { withAgentTargetHeader } from '../../proxy/queries/utils';
|
||||
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
|
||||
|
||||
import { CreateContainerRequest } from './types';
|
||||
import { Values } from './useInitialValues';
|
||||
|
@ -75,7 +75,7 @@ interface CreateOptions {
|
|||
}
|
||||
|
||||
interface ReplaceOptions extends CreateOptions {
|
||||
oldContainer: DockerContainer;
|
||||
oldContainer: ContainerListViewModel;
|
||||
extraNetworks: Array<ExtraNetwork>;
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ async function replace({
|
|||
async function renameAndCreate(
|
||||
environmentId: EnvironmentId,
|
||||
name: string,
|
||||
oldContainer: DockerContainer,
|
||||
oldContainer: ContainerListViewModel,
|
||||
config: CreateContainerRequest,
|
||||
nodeName?: string
|
||||
) {
|
||||
|
@ -234,7 +234,7 @@ async function applyContainerSettings(
|
|||
async function createAndStart(
|
||||
environmentId: EnvironmentId,
|
||||
config: CreateContainerRequest,
|
||||
name: string,
|
||||
name?: string,
|
||||
nodeName?: string
|
||||
) {
|
||||
let containerId = '';
|
||||
|
@ -290,12 +290,10 @@ async function createContainer(
|
|||
{ nodeName }: { nodeName?: string } = {}
|
||||
) {
|
||||
try {
|
||||
const headers = addNodeHeader(nodeName);
|
||||
|
||||
const { data } = await axios.post<
|
||||
PortainerResponse<{ Id: string; Warnings: Array<string> }>
|
||||
>(urlBuilder(environmentId, undefined, 'create'), config, {
|
||||
headers,
|
||||
>(buildDockerProxyUrl(environmentId, 'containers', 'create'), config, {
|
||||
headers: { ...withAgentTargetHeader(nodeName) },
|
||||
params: { name },
|
||||
});
|
||||
|
||||
|
@ -349,7 +347,7 @@ function connectToExtraNetworks(
|
|||
|
||||
function stopContainerIfNeeded(
|
||||
environmentId: EnvironmentId,
|
||||
container: DockerContainer,
|
||||
container: ContainerListViewModel,
|
||||
nodeName?: string
|
||||
) {
|
||||
if (container.State !== 'running' || !container.Id) {
|
||||
|
|
|
@ -43,8 +43,8 @@ import { useEnvironmentRegistries } from '@/react/portainer/environments/queries
|
|||
import { EnvVarValues } from '@@/form-components/EnvironmentVariablesFieldset';
|
||||
|
||||
import { useNetworksForSelector } from '../components/NetworkSelector';
|
||||
import { useContainers } from '../queries/containers';
|
||||
import { useContainer } from '../queries/container';
|
||||
import { useContainers } from '../queries/useContainers';
|
||||
import { useContainer } from '../queries/useContainer';
|
||||
|
||||
export interface Values extends BaseFormValues {
|
||||
commands: CommandsTabValues;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { SchemaOf, object, string } from 'yup';
|
|||
import { useRouter } from '@uirouter/react';
|
||||
|
||||
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||
import { useConnectContainerMutation } from '@/react/docker/networks/queries/useConnectContainer';
|
||||
import { useConnectContainerMutation } from '@/react/docker/networks/queries/useConnectContainerMutation';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue