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.Close()
|
||||||
}(rc)
|
}(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = dockerClient.ServiceUpdate(context.Background(), resourceID, service.Version, service.Spec, serviceUpdateOptions)
|
_, err = dockerClient.ServiceUpdate(context.Background(), resourceID, service.Version, service.Spec, serviceUpdateOptions)
|
||||||
|
|
||||||
if err != nil {
|
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.
|
// 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
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// rewrite the body of the request.
|
||||||
|
//
|
||||||
// In any other case, it will leave the request unaltered.
|
// In any other case, it will leave the request unaltered.
|
||||||
func buildOperation(request *http.Request) error {
|
func buildOperation(request *http.Request) error {
|
||||||
contentTypeHeader := request.Header.Get("Content-Type")
|
contentTypeHeader := request.Header.Get("Content-Type")
|
||||||
|
|
|
@ -84,11 +84,25 @@ func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, er
|
||||||
return transport.ProxyDockerRequest(request)
|
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
|
// ProxyDockerRequest intercepts a Docker API request and apply logic based
|
||||||
// on the requested operation.
|
// on the requested operation.
|
||||||
func (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Response, error) {
|
||||||
requestPath := apiVersionRe.ReplaceAllString(request.URL.Path, "")
|
unversionedPath := apiVersionRe.ReplaceAllString(request.URL.Path, "")
|
||||||
request.URL.Path = requestPath
|
|
||||||
|
|
||||||
if transport.endpoint.Type == portainer.AgentOnDockerEnvironment || transport.endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
if transport.endpoint.Type == portainer.AgentOnDockerEnvironment || transport.endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
||||||
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
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)
|
request.Header.Set(portainer.PortainerAgentSignatureHeader, signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
prefix := strings.Split(strings.TrimPrefix(unversionedPath, "/"), "/")[0]
|
||||||
case strings.HasPrefix(requestPath, "/configs"):
|
|
||||||
return transport.proxyConfigRequest(request)
|
if proxyFunc := prefixProxyFuncMap[prefix]; proxyFunc != nil {
|
||||||
case strings.HasPrefix(requestPath, "/containers"):
|
return proxyFunc(transport, request, unversionedPath)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) executeDockerRequest(request *http.Request) (*http.Response, error) {
|
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
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) proxyAgentRequest(r *http.Request) (*http.Response, error) {
|
func (transport *Transport) proxyAgentRequest(r *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
requestPath := strings.TrimPrefix(r.URL.Path, "/v2")
|
requestPath := strings.TrimPrefix(unversionedPath, "/v2")
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(requestPath, "/browse"):
|
case strings.HasPrefix(requestPath, "/browse"):
|
||||||
|
@ -203,8 +195,10 @@ func (transport *Transport) proxyAgentRequest(r *http.Request) (*http.Response,
|
||||||
return transport.executeDockerRequest(r)
|
return transport.executeDockerRequest(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) proxyConfigRequest(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) proxyConfigRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/configs/create":
|
case "/configs/create":
|
||||||
return transport.decorateGenericResourceCreationOperation(request, configObjectIdentifier, portainer.ConfigResourceControl)
|
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) {
|
func (transport *Transport) proxyContainerRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/containers/create":
|
case "/containers/create":
|
||||||
return transport.decorateContainerCreationOperation(request, containerObjectIdentifier, portainer.ContainerResourceControl)
|
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) {
|
func (transport *Transport) proxyServiceRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/services/create":
|
case "/services/create":
|
||||||
return transport.decorateServiceCreationOperation(request)
|
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) {
|
func (transport *Transport) proxyVolumeRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/volumes/create":
|
case "/volumes/create":
|
||||||
return transport.decorateVolumeResourceCreationOperation(request, portainer.VolumeResourceControl)
|
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) {
|
func (transport *Transport) proxyNetworkRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/networks/create":
|
case "/networks/create":
|
||||||
return transport.decorateGenericResourceCreationOperation(request, networkObjectIdentifier, portainer.NetworkResourceControl)
|
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) {
|
func (transport *Transport) proxySecretRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/secrets/create":
|
case "/secrets/create":
|
||||||
return transport.decorateGenericResourceCreationOperation(request, secretObjectIdentifier, portainer.SecretResourceControl)
|
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) {
|
func (transport *Transport) proxyNodeRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
requestPath := request.URL.Path
|
requestPath := unversionedPath
|
||||||
|
|
||||||
// assume /nodes/{id}
|
// assume /nodes/{id}
|
||||||
if path.Base(requestPath) != "nodes" {
|
if path.Base(requestPath) != "nodes" {
|
||||||
|
@ -362,8 +366,10 @@ func (transport *Transport) proxyNodeRequest(request *http.Request) (*http.Respo
|
||||||
return transport.executeDockerRequest(request)
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) proxySwarmRequest(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) proxySwarmRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/swarm":
|
case "/swarm":
|
||||||
return transport.rewriteOperation(request, swarmInspectOperation)
|
return transport.rewriteOperation(request, swarmInspectOperation)
|
||||||
default:
|
default:
|
||||||
|
@ -372,8 +378,10 @@ func (transport *Transport) proxySwarmRequest(request *http.Request) (*http.Resp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) proxyTaskRequest(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) proxyTaskRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/tasks":
|
case "/tasks":
|
||||||
return transport.rewriteOperation(request, transport.taskListOperation)
|
return transport.rewriteOperation(request, transport.taskListOperation)
|
||||||
default:
|
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)
|
err := transport.updateDefaultGitBranch(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -408,8 +416,10 @@ func (transport *Transport) updateDefaultGitBranch(request *http.Request) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) proxyImageRequest(request *http.Request) (*http.Response, error) {
|
func (transport *Transport) proxyImageRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
switch requestPath := request.URL.Path; requestPath {
|
requestPath := unversionedPath
|
||||||
|
|
||||||
|
switch requestPath {
|
||||||
case "/images/create":
|
case "/images/create":
|
||||||
return transport.replaceRegistryAuthenticationHeader(request)
|
return transport.replaceRegistryAuthenticationHeader(request)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -5,7 +5,6 @@ function ImageHelperFactory() {
|
||||||
return {
|
return {
|
||||||
isValidTag,
|
isValidTag,
|
||||||
createImageConfigForContainer,
|
createImageConfigForContainer,
|
||||||
getImagesNamesForDownload,
|
|
||||||
removeDigestFromRepository,
|
removeDigestFromRepository,
|
||||||
imageContainsURL,
|
imageContainsURL,
|
||||||
};
|
};
|
||||||
|
@ -14,20 +13,6 @@ function ImageHelperFactory() {
|
||||||
return tag.match(/^(?![\.\-])([a-zA-Z0-9\_\.\-])+$/g);
|
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
|
* @param {PorImageRegistryModel} registry
|
||||||
|
|
|
@ -3,14 +3,6 @@ angular.module('portainer.docker').factory('VolumeHelper', [
|
||||||
'use strict';
|
'use strict';
|
||||||
var helper = {};
|
var helper = {};
|
||||||
|
|
||||||
helper.createDriverOptions = function (optionArray) {
|
|
||||||
var options = {};
|
|
||||||
optionArray.forEach(function (option) {
|
|
||||||
options[option.name] = option.value;
|
|
||||||
});
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
helper.isVolumeUsedByAService = function (volume, services) {
|
helper.isVolumeUsedByAService = function (volume, services) {
|
||||||
for (var i = 0; i < services.length; i++) {
|
for (var i = 0; i < services.length; i++) {
|
||||||
var service = services[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 { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
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 {
|
export class NetworkViewModel implements IResource {
|
||||||
Id: string;
|
Id: string;
|
||||||
|
@ -38,8 +51,7 @@ export class NetworkViewModel implements IResource {
|
||||||
ResourceControl?: ResourceControlViewModel;
|
ResourceControl?: ResourceControlViewModel;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
data: Network & {
|
data: PortainerResponse<Network> & {
|
||||||
Portainer?: PortainerMetadata;
|
|
||||||
ConfigFrom?: { Network: string };
|
ConfigFrom?: { Network: string };
|
||||||
ConfigOnly?: boolean;
|
ConfigOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,6 @@ import {
|
||||||
ResourceObject,
|
ResourceObject,
|
||||||
} from 'docker-types/generated/1.41';
|
} from 'docker-types/generated/1.41';
|
||||||
|
|
||||||
import { WithRequiredProperty } from '@/types';
|
|
||||||
|
|
||||||
export class NodeViewModel {
|
export class NodeViewModel {
|
||||||
Model: Node;
|
Model: Node;
|
||||||
|
|
||||||
|
@ -55,7 +53,7 @@ export class NodeViewModel {
|
||||||
|
|
||||||
Status: NodeStatus['State'];
|
Status: NodeStatus['State'];
|
||||||
|
|
||||||
Addr: WithRequiredProperty<NodeStatus, 'Addr'>['Addr'] = '';
|
Addr: Required<NodeStatus>['Addr'] = '';
|
||||||
|
|
||||||
Leader: ManagerStatus['Leader'];
|
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 { Secret } from 'docker-types/generated/1.41';
|
||||||
|
|
||||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
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';
|
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
||||||
|
|
||||||
export class SecretViewModel implements IResource {
|
export class SecretViewModel implements IResource {
|
||||||
|
@ -19,7 +19,7 @@ export class SecretViewModel implements IResource {
|
||||||
|
|
||||||
ResourceControl?: ResourceControlViewModel;
|
ResourceControl?: ResourceControlViewModel;
|
||||||
|
|
||||||
constructor(data: Secret & { Portainer?: PortainerMetadata }) {
|
constructor(data: PortainerResponse<Secret>) {
|
||||||
this.Id = data.ID || '';
|
this.Id = data.ID || '';
|
||||||
this.CreatedAt = data.CreatedAt || '';
|
this.CreatedAt = data.CreatedAt || '';
|
||||||
this.UpdatedAt = data.UpdatedAt || '';
|
this.UpdatedAt = data.UpdatedAt || '';
|
||||||
|
|
|
@ -9,15 +9,13 @@ import {
|
||||||
} from 'docker-types/generated/1.41';
|
} from 'docker-types/generated/1.41';
|
||||||
|
|
||||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||||
import { PortainerMetadata } from '@/react/docker/types';
|
import { PortainerResponse } from '@/react/docker/types';
|
||||||
import { WithRequiredProperty } from '@/types';
|
|
||||||
|
|
||||||
import { TaskViewModel } from './task';
|
import { TaskViewModel } from './task';
|
||||||
|
|
||||||
type ContainerSpec = WithRequiredProperty<
|
type ContainerSpec = Required<TaskSpec>['ContainerSpec'];
|
||||||
TaskSpec,
|
|
||||||
'ContainerSpec'
|
export type ServiceId = string;
|
||||||
>['ContainerSpec'];
|
|
||||||
|
|
||||||
export class ServiceViewModel {
|
export class ServiceViewModel {
|
||||||
Model: Service;
|
Model: Service;
|
||||||
|
@ -140,7 +138,7 @@ export class ServiceViewModel {
|
||||||
|
|
||||||
ResourceControl?: ResourceControlViewModel;
|
ResourceControl?: ResourceControlViewModel;
|
||||||
|
|
||||||
constructor(data: Service & { Portainer?: PortainerMetadata }) {
|
constructor(data: PortainerResponse<Service>) {
|
||||||
this.Model = data;
|
this.Model = data;
|
||||||
this.Id = data.ID || '';
|
this.Id = data.ID || '';
|
||||||
this.Tasks = [];
|
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 {
|
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) {
|
constructor(data: Task) {
|
||||||
this.Id = data.ID || '';
|
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 { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||||
import { IResource } from '@/react/docker/components/datatable/createOwnershipColumn';
|
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 {
|
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;
|
ResourceId?: string;
|
||||||
|
|
||||||
NodeName?: string;
|
NodeName?: string;
|
||||||
|
|
||||||
|
StackName?: string;
|
||||||
|
|
||||||
ResourceControl?: ResourceControlViewModel;
|
ResourceControl?: ResourceControlViewModel;
|
||||||
|
|
||||||
constructor(
|
constructor(data: PortainerResponse<Volume> & { ResourceID?: string }) {
|
||||||
data: Volume & { Portainer?: PortainerMetadata; ResourceID?: string }
|
|
||||||
) {
|
|
||||||
this.Id = data.Name;
|
this.Id = data.Name;
|
||||||
this.CreatedAt = data.CreatedAt;
|
this.CreatedAt = data.CreatedAt;
|
||||||
this.Driver = data.Driver;
|
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', [
|
import { ImageBuildModel } from '../models/build';
|
||||||
'$q',
|
|
||||||
'Build',
|
|
||||||
'FileUploadService',
|
|
||||||
function BuildServiceFactory($q, Build, FileUploadService) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.buildImageFromUpload = function (endpointID, names, file, path) {
|
angular.module('portainer.docker').factory('BuildService', BuildServiceFactory);
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
FileUploadService.buildImage(endpointID, names, file, path)
|
/* @ngInject */
|
||||||
.then(function success(response) {
|
function BuildServiceFactory(AngularToReact) {
|
||||||
var model = new ImageBuildModel(response.data);
|
const { useAxios } = AngularToReact;
|
||||||
deferred.resolve(model);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 = {
|
* @param {EnvironmentId} environmentId
|
||||||
endpointId,
|
* @param {string[]} names
|
||||||
t: names,
|
* @param {File} file
|
||||||
remote: url,
|
* @param {string} path
|
||||||
dockerfile: 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) {
|
* @param {EnvironmentId} environmentId
|
||||||
var model = new ImageBuildModel(data);
|
* @param {string[]} names
|
||||||
deferred.resolve(model);
|
* @param {string} content
|
||||||
})
|
*/
|
||||||
.catch(function error(err) {
|
async function buildImageFromDockerfileContentAngularJS(environmentId, names, content) {
|
||||||
deferred.reject(err);
|
const data = await buildImageFromDockerfileContent(environmentId, names, content);
|
||||||
});
|
return new ImageBuildModel(data);
|
||||||
|
}
|
||||||
|
|
||||||
return deferred.promise;
|
/**
|
||||||
};
|
* @param {EnvironmentId} environmentId
|
||||||
|
* @param {string[]} names
|
||||||
service.buildImageFromDockerfileContent = function (endpointId, names, content) {
|
* @param {string} content
|
||||||
var params = {
|
* @param {File[]} files
|
||||||
endpointId,
|
*/
|
||||||
t: names,
|
async function buildImageFromDockerfileContentAndFilesAngularJS(environmentId, names, content, files) {
|
||||||
};
|
const data = await buildImageFromDockerfileContentAndFiles(environmentId, names, content, files);
|
||||||
var payload = {
|
return new ImageBuildModel(data);
|
||||||
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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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';
|
import { ConfigViewModel } from '../models/config';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('ConfigService', [
|
angular.module('portainer.docker').factory('ConfigService', ConfigServiceFactory);
|
||||||
'$q',
|
|
||||||
'Config',
|
|
||||||
function ConfigServiceFactory($q, Config) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.config = function (environmentId, configId) {
|
/* @ngInspect */
|
||||||
var deferred = $q.defer();
|
function ConfigServiceFactory(AngularToReact) {
|
||||||
|
const { useAxios } = AngularToReact;
|
||||||
|
|
||||||
Config.get({ id: configId, environmentId })
|
return {
|
||||||
.$promise.then(function success(data) {
|
configs: useAxios(listConfigsAngularJS), // config list + service create + service edit
|
||||||
var config = new ConfigViewModel(data);
|
config: useAxios(getConfigAngularJS), // config create + config edit
|
||||||
deferred.resolve(config);
|
remove: useAxios(deleteConfig), // config list + config edit
|
||||||
})
|
create: useAxios(createConfig), // config create
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve config details', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service.configs = function (environmentId) {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId
|
||||||
|
*/
|
||||||
Config.query({ environmentId })
|
async function listConfigsAngularJS(environmentId) {
|
||||||
.$promise.then(function success(data) {
|
const data = await getConfigs(environmentId);
|
||||||
var configs = data.map(function (item) {
|
return data.map((c) => new ConfigViewModel(c));
|
||||||
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;
|
/**
|
||||||
};
|
* @param {EnvironmentId} environmentId
|
||||||
|
* @param {ConfigId} configId
|
||||||
service.create = function (environmentId, config) {
|
*/
|
||||||
return Config.create({ environmentId }, config).$promise;
|
async function getConfigAngularJS(environmentId, configId) {
|
||||||
};
|
const data = await getConfig(environmentId, configId);
|
||||||
|
return new ConfigViewModel(data);
|
||||||
return service;
|
}
|
||||||
},
|
}
|
||||||
]);
|
|
||||||
|
|
|
@ -9,178 +9,110 @@ import {
|
||||||
startContainer,
|
startContainer,
|
||||||
stopContainer,
|
stopContainer,
|
||||||
recreateContainer,
|
recreateContainer,
|
||||||
|
getContainerLogs,
|
||||||
} from '@/react/docker/containers/containers.service';
|
} 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';
|
import { formatLogs } from '../helpers/logHelper';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('ContainerService', ContainerServiceFactory);
|
angular.module('portainer.docker').factory('ContainerService', ContainerServiceFactory);
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
function ContainerServiceFactory($q, Container, $timeout) {
|
function ContainerServiceFactory(AngularToReact) {
|
||||||
const service = {
|
const { useAxios } = AngularToReact;
|
||||||
killContainer,
|
|
||||||
pauseContainer,
|
return {
|
||||||
renameContainer,
|
killContainer: useAxios(killContainer), // container edit
|
||||||
restartContainer,
|
pauseContainer: useAxios(pauseContainer), // container edit
|
||||||
resumeContainer,
|
renameContainer: useAxios(renameContainer), // container edit
|
||||||
startContainer,
|
restartContainer: useAxios(restartContainer), // container edit
|
||||||
stopContainer,
|
resumeContainer: useAxios(resumeContainer), // container edit
|
||||||
recreateContainer,
|
startContainer: useAxios(startContainer), // container edit
|
||||||
remove: removeContainer,
|
stopContainer: useAxios(stopContainer), // container edit
|
||||||
updateRestartPolicy,
|
recreateContainer: useAxios(recreateContainer), // container edit
|
||||||
updateLimits,
|
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();
|
* @param {EnvironmentId} environmentId
|
||||||
|
* @param {ContainerId} id
|
||||||
Container.get({ environmentId, id })
|
* @param {*} param2
|
||||||
.$promise.then(function success(data) {
|
*/
|
||||||
var container = new ContainerDetailsViewModel(data);
|
async function getContainerAngularJS(environmentId, id, { nodeName } = {}) {
|
||||||
deferred.resolve(container);
|
const data = await getContainer(environmentId, id, { nodeName });
|
||||||
})
|
return new ContainerDetailsViewModel(data);
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLimits(environmentId, id, config) {
|
/**
|
||||||
return Container.update(
|
* @param {EnvironmentId} environmentId
|
||||||
{ environmentId, id },
|
* @param {string} containerId
|
||||||
{
|
* @param {number} width
|
||||||
// MemorySwap: must be set
|
* @param {number} height
|
||||||
// -1: non limits, 0: treated as unset(cause update error).
|
* @param timeout DEPRECATED: Previously used in pure AJS implementation
|
||||||
MemoryReservation: config.HostConfig.MemoryReservation,
|
*/
|
||||||
Memory: config.HostConfig.Memory,
|
async function resizeTTYAngularJS(environmentId, containerId, width, height) {
|
||||||
MemorySwap: -1,
|
return resizeTTY(environmentId, containerId, { width, height });
|
||||||
NanoCpus: config.HostConfig.NanoCpus,
|
|
||||||
}
|
|
||||||
).$promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
service.createContainer = function (environmentId, configuration) {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId
|
||||||
Container.create({ environmentId }, configuration)
|
* @param {ContainerId} id
|
||||||
.$promise.then(function success(data) {
|
* @param {RestartPolicy['Name']} restartPolicy
|
||||||
deferred.resolve(data);
|
* @param {RestartPolicy['MaximumRetryCount']} maximumRetryCounts
|
||||||
})
|
*/
|
||||||
.catch(function error(err) {
|
async function updateRestartPolicyAngularJS(environmentId, id, restartPolicy, maximumRetryCounts) {
|
||||||
deferred.reject({ msg: 'Unable to create container', err: err });
|
return updateContainer(environmentId, id, {
|
||||||
|
RestartPolicy: {
|
||||||
|
Name: restartPolicy,
|
||||||
|
MaximumRetryCount: maximumRetryCounts,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
service.createExec = function (environmentId, execConfig) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
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);
|
* @param {EnvironmentId} environmentId
|
||||||
|
* @param {ContainerId} id
|
||||||
|
*/
|
||||||
|
async function containerStatsAngularJS(environmentId, id) {
|
||||||
|
const data = await containerStats(environmentId, id);
|
||||||
|
return new ContainerStatsViewModel(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 });
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,23 @@
|
||||||
angular.module('portainer.docker').factory('ExecService', [
|
import { resizeTTY } from '@/react/docker/proxy/queries/useExecResizeTTYMutation';
|
||||||
'$q',
|
|
||||||
'$timeout',
|
|
||||||
'Exec',
|
|
||||||
function ExecServiceFactory($q, $timeout, Exec) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.resizeTTY = function (environmentId, execId, width, height, timeout) {
|
angular.module('portainer.docker').factory('ExecService', ExecServiceFactory);
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
$timeout(function () {
|
/* @ngInject */
|
||||||
Exec.resize({ environmentId }, { id: execId, height: height, width: width })
|
function ExecServiceFactory(AngularToReact) {
|
||||||
.$promise.then(function success(data) {
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
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);
|
|
||||||
|
|
||||||
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 { 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 { ImageViewModel } from '../models/image';
|
||||||
import { ImageDetailsViewModel } from '../models/imageDetails';
|
import { ImageDetailsViewModel } from '../models/imageDetails';
|
||||||
import { ImageLayerViewModel } from '../models/imageLayer';
|
import { ImageLayerViewModel } from '../models/imageLayer';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('ImageService', [
|
angular.module('portainer.docker').factory('ImageService', ImageServiceFactory);
|
||||||
'$q',
|
|
||||||
'Image',
|
|
||||||
'ImageHelper',
|
|
||||||
'RegistryService',
|
|
||||||
'HttpRequestHelper',
|
|
||||||
'ContainerService',
|
|
||||||
'FileUploadService',
|
|
||||||
function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper, ContainerService, FileUploadService) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.image = function (imageId) {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function ImageServiceFactory(AngularToReact) {
|
||||||
Image.get({ id: imageId })
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
.$promise.then(function success(data) {
|
|
||||||
if (data.message) {
|
return {
|
||||||
deferred.reject({ msg: data.message });
|
image: useAxios(injectEnvironmentId(imageAngularJS)), // container console + image edit
|
||||||
} else {
|
images: useAxios(injectEnvironmentId(imagesAngularJS)), // por image registry controller + dashboard + service edit
|
||||||
var image = new ImageDetailsViewModel(data);
|
history: useAxios(injectEnvironmentId(historyAngularJS)), // image edit
|
||||||
deferred.resolve(image);
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
async function imageAngularJS(environmentId, imageId) {
|
||||||
|
const image = await getImage(environmentId, imageId);
|
||||||
|
return new ImageDetailsViewModel(image);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve image details', err: err });
|
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
service.images = function ({ environmentId, withUsage } = {}) {
|
async function imagesAngularJS(environmentId, withUsage) {
|
||||||
var deferred = $q.defer();
|
try {
|
||||||
|
const [containers, images] = await Promise.all([withUsage ? getContainers(environmentId) : [], getImages(environmentId)]);
|
||||||
$q.all({
|
const containerByImageId = groupBy(containers, 'ImageID');
|
||||||
containers: withUsage ? ContainerService.containers(environmentId, 1) : [],
|
return images.map((item) => new ImageViewModel(item, !!containerByImageId[item.Id] && containerByImageId[item.Id].length > 0));
|
||||||
images: Image.query({}).$promise,
|
} catch (e) {
|
||||||
})
|
throw parseAxiosError(e, 'Unable to retrieve images');
|
||||||
.then(function success(data) {
|
}
|
||||||
var containers = data.containers;
|
}
|
||||||
const containerByImageId = _.groupBy(containers, 'ImageID');
|
|
||||||
|
async function historyAngularJS(environmentId, imageId) {
|
||||||
var images = data.images.map(function (item) {
|
try {
|
||||||
item.Used = !!containerByImageId[item.Id] && containerByImageId[item.Id].length > 0;
|
const layers = await getImageHistory(environmentId, imageId);
|
||||||
return new ImageViewModel(item);
|
return layers.reverse().map((layer, idx) => new ImageLayerViewModel(idx, layer));
|
||||||
});
|
} catch (e) {
|
||||||
|
throw parseAxiosError(e, 'Unable to retrieve image history');
|
||||||
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;
|
|
||||||
/**
|
/**
|
||||||
*
|
* type PorImageRegistryModel = {
|
||||||
|
* UseRegistry: bool;
|
||||||
|
* Registry?: Registry;
|
||||||
|
* Image: string;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {EnvironmentId} environmentId Autofilled by AngularToReact
|
||||||
* @param {PorImageRegistryModel} registryModel
|
* @param {PorImageRegistryModel} registryModel
|
||||||
*/
|
*/
|
||||||
function pushImage(registryModel) {
|
async function pushImageAngularJS(environmentId, registryModel) {
|
||||||
var deferred = $q.defer();
|
const { UseRegistry, Registry, Image } = registryModel;
|
||||||
|
const registry = UseRegistry ? Registry : undefined;
|
||||||
var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : '';
|
return pushImage({ environmentId, image: Image, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PULL IMAGE
|
* @param {EnvironmentId} environmentId Autofilled by AngularToReact
|
||||||
|
* @param {PorImageRegistryModel} registryModel
|
||||||
|
* @param {string?} nodeName
|
||||||
*/
|
*/
|
||||||
|
async function pullImageAngularJS(environmentId, registryModel, nodeName) {
|
||||||
function pullImageAndIgnoreErrors(imageConfiguration) {
|
const { UseRegistry, Registry, Image } = registryModel;
|
||||||
var deferred = $q.defer();
|
const registry = UseRegistry ? Registry : undefined;
|
||||||
|
return pullImage({ environmentId, image: Image, nodeName, registry });
|
||||||
Image.create({}, imageConfiguration)
|
|
||||||
.$promise.catch(() => {
|
|
||||||
// left empty to ignore errors
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
function pullImageAndAcknowledgeErrors(imageConfiguration) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
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 });
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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';
|
import { NetworkViewModel } from '../models/network';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('NetworkService', [
|
angular.module('portainer.docker').factory('NetworkService', NetworkServiceFactory);
|
||||||
'$q',
|
|
||||||
'Network',
|
|
||||||
function NetworkServiceFactory($q, Network) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.create = function (networkConfiguration) {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function NetworkServiceFactory(AngularToReact) {
|
||||||
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
|
|
||||||
Network.create(networkConfiguration)
|
return {
|
||||||
.$promise.then(function success(data) {
|
create: useAxios(injectEnvironmentId(createNetwork)), // create network
|
||||||
deferred.resolve(data);
|
network: useAxios(injectEnvironmentId(networkAngularJS)), // service edit
|
||||||
})
|
networks: useAxios(injectEnvironmentId(networksAngularJS)), // macvlan form + container edit + dashboard + service create + service edit + custom templates list + templates list
|
||||||
.catch(function error(err) {
|
remove: useAxios(injectEnvironmentId(deleteNetwork)), // networks list
|
||||||
deferred.reject({ msg: 'Unable to create network', err: err });
|
disconnectContainer: useAxios(injectEnvironmentId(disconnectContainer)), // container edit
|
||||||
});
|
connectContainer: useAxios(injectEnvironmentId(connectContainerAngularJS)), // container edit
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service.network = function (id) {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId filled by AngularToReact
|
||||||
|
* @param {NetworkId} networkId
|
||||||
Network.get({ id: id })
|
* @param {string?} nodeName
|
||||||
.$promise.then(function success(data) {
|
* @returns NetworkViewModel
|
||||||
var network = new NetworkViewModel(data);
|
*/
|
||||||
deferred.resolve(network);
|
async function networkAngularJS(environmentId, networkId, nodeName) {
|
||||||
})
|
const data = await getNetwork(environmentId, networkId, { nodeName });
|
||||||
.catch(function error(err) {
|
return new NetworkViewModel(data);
|
||||||
deferred.reject({ msg: 'Unable to retrieve network details', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
/**
|
||||||
|
* @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));
|
||||||
}
|
}
|
||||||
if (swarmAttachableNetworks && network.Scope === 'swarm' && network.Attachable === true) {
|
|
||||||
return network;
|
/**
|
||||||
|
* @param {EnvironmentId} environmentId filled by AngularToReact
|
||||||
|
* @param {NetworkId} networkId
|
||||||
|
* @param {ContainerId} containerId
|
||||||
|
*/
|
||||||
|
async function connectContainerAngularJS(environmentId, networkId, containerId) {
|
||||||
|
return connectContainer({ environmentId, containerId, networkId });
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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';
|
import { NodeViewModel } from '../models/node';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('NodeService', [
|
angular.module('portainer.docker').factory('NodeService', NodeServiceFactory);
|
||||||
'$q',
|
|
||||||
'Node',
|
|
||||||
function NodeServiceFactory($q, Node) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.nodes = nodes;
|
/* @ngInject */
|
||||||
service.node = node;
|
function NodeServiceFactory(AngularToReact) {
|
||||||
service.updateNode = updateNode;
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
service.getActiveManager = getActiveManager;
|
|
||||||
|
|
||||||
function node(id) {
|
return {
|
||||||
var deferred = $q.defer();
|
nodes: useAxios(injectEnvironmentId(nodesAngularJS)), // macvlan form + services list + service create + service edit + swarm visualizer + stack edit
|
||||||
Node.get({ id: id })
|
node: useAxios(injectEnvironmentId(nodeAngularJS)), // node browser + node details
|
||||||
.$promise.then(function onNodeLoaded(rawNode) {
|
updateNode: useAxios(injectEnvironmentId(updateNodeAngularJS)), // swarm node details panel
|
||||||
var node = new NodeViewModel(rawNode);
|
};
|
||||||
return deferred.resolve(node);
|
|
||||||
})
|
|
||||||
.catch(function onFailed(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve node', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
||||||
|
*/
|
||||||
Node.query({})
|
async function nodesAngularJS(environmentId) {
|
||||||
.$promise.then(function success(data) {
|
const data = await getNodes(environmentId);
|
||||||
var nodes = data.map(function (item) {
|
return data.map((n) => new NodeViewModel(n));
|
||||||
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;
|
* @param {EnvironmentId} environmentId
|
||||||
|
* @param {NodeSpec & { Id: string; Version: number }} nodeConfig
|
||||||
|
*/
|
||||||
|
async function updateNodeAngularJS(environmentId, nodeConfig) {
|
||||||
|
return updateNode(environmentId, nodeConfig.Id, nodeConfig, nodeConfig.Version);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -1,76 +1,51 @@
|
||||||
import _ from 'lodash-es';
|
import { isFulfilled } from '@/portainer/helpers/promise-utils';
|
||||||
import { PluginViewModel } from '../models/plugin';
|
import { getInfo } from '@/react/docker/proxy/queries/useInfo';
|
||||||
|
import { aggregateData, getPlugins } from '@/react/docker/proxy/queries/useServicePlugins';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('PluginService', [
|
angular.module('portainer.docker').factory('PluginService', PluginServiceFactory);
|
||||||
'$q',
|
|
||||||
'Plugin',
|
|
||||||
'SystemService',
|
|
||||||
function PluginServiceFactory($q, Plugin, SystemService) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.plugins = function () {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function PluginServiceFactory(AngularToReact) {
|
||||||
var plugins = [];
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
|
|
||||||
Plugin.query({})
|
return {
|
||||||
.$promise.then(function success(data) {
|
volumePlugins: useAxios(injectEnvironmentId(volumePlugins)), // volume create
|
||||||
for (var i = 0; i < data.length; i++) {
|
networkPlugins: useAxios(injectEnvironmentId(networksPlugins)), // network create
|
||||||
var plugin = new PluginViewModel(data[i]);
|
loggingPlugins: useAxios(injectEnvironmentId(loggingPlugins)), // service create + service edit
|
||||||
plugins.push(plugin);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
deferred.resolve(plugins);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function servicePlugins(systemOnly, pluginType, pluginVersion) {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
* @param {boolean} systemOnly
|
||||||
|
*/
|
||||||
|
async function volumePlugins(environmentId, systemOnly) {
|
||||||
|
const { systemPluginsData, pluginsData } = await getAllPlugins(environmentId);
|
||||||
|
return aggregateData(systemPluginsData, pluginsData, systemOnly, 'Volume');
|
||||||
|
}
|
||||||
|
|
||||||
$q.all({
|
/**
|
||||||
system: SystemService.plugins(),
|
* @param {EnvironmentId} environmentId Injected
|
||||||
plugins: systemOnly ? [] : service.plugins(),
|
* @param {boolean} systemOnly
|
||||||
})
|
*/
|
||||||
.then(function success(data) {
|
async function networksPlugins(environmentId, systemOnly) {
|
||||||
var aggregatedPlugins = [];
|
const { systemPluginsData, pluginsData } = await getAllPlugins(environmentId);
|
||||||
var systemPlugins = data.system;
|
return aggregateData(systemPluginsData, pluginsData, systemOnly, 'Network');
|
||||||
var plugins = data.plugins;
|
}
|
||||||
|
|
||||||
if (systemPlugins[pluginType]) {
|
/**
|
||||||
aggregatedPlugins = aggregatedPlugins.concat(systemPlugins[pluginType]);
|
* @param {EnvironmentId} environmentId Injected
|
||||||
}
|
* @param {boolean} systemOnly
|
||||||
|
*/
|
||||||
|
async function loggingPlugins(environmentId, systemOnly) {
|
||||||
|
const { systemPluginsData, pluginsData } = await getAllPlugins(environmentId);
|
||||||
|
return aggregateData(systemPluginsData, pluginsData, systemOnly, 'Log');
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < plugins.length; i++) {
|
async function getAllPlugins(environmentId) {
|
||||||
var plugin = plugins[i];
|
const [system, plugins] = await Promise.allSettled([getInfo(environmentId), getPlugins(environmentId)]);
|
||||||
if (plugin.Enabled && _.includes(plugin.Config.Interface.Types, pluginVersion)) {
|
const systemPluginsData = isFulfilled(system) ? system.value.Plugins : undefined;
|
||||||
aggregatedPlugins.push(plugin.Name);
|
const pluginsData = isFulfilled(plugins) ? plugins.value : undefined;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolve(aggregatedPlugins);
|
return { systemPluginsData, pluginsData };
|
||||||
})
|
}
|
||||||
.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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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';
|
import { SecretViewModel } from '../models/secret';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('SecretService', [
|
angular.module('portainer.docker').factory('SecretService', SecretServiceFactory);
|
||||||
'$q',
|
|
||||||
'Secret',
|
|
||||||
function SecretServiceFactory($q, Secret) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.secret = function (secretId) {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function SecretServiceFactory(AngularToReact) {
|
||||||
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
|
|
||||||
Secret.get({ id: secretId })
|
return {
|
||||||
.$promise.then(function success(data) {
|
secret: useAxios(injectEnvironmentId(secretAngularJS)), // secret edit
|
||||||
var secret = new SecretViewModel(data);
|
secrets: useAxios(injectEnvironmentId(secretsAngularJS)), // secret list + service create + service edit
|
||||||
deferred.resolve(secret);
|
remove: useAxios(injectEnvironmentId(removeSecret)), // secret list + secret edit
|
||||||
})
|
create: useAxios(injectEnvironmentId(createSecret)), // secret create
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve secret details', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service.secrets = function () {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
* @param {SecretId} id
|
||||||
Secret.query({})
|
*/
|
||||||
.$promise.then(function success(data) {
|
async function secretAngularJS(environmentId, id) {
|
||||||
var secrets = data.map(function (item) {
|
const data = await getSecret(environmentId, id);
|
||||||
return new SecretViewModel(item);
|
return new SecretViewModel(data);
|
||||||
});
|
|
||||||
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;
|
/**
|
||||||
};
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
*/
|
||||||
service.create = function (secretConfig) {
|
async function secretsAngularJS(environmentId) {
|
||||||
return Secret.create(secretConfig).$promise;
|
const data = await getSecrets(environmentId);
|
||||||
};
|
return data.map((s) => new SecretViewModel(s));
|
||||||
|
}
|
||||||
return service;
|
}
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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 { ServiceViewModel } from '../models/service';
|
||||||
|
import { formatLogs } from '../helpers/logHelper';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('ServiceService', [
|
angular.module('portainer.docker').factory('ServiceService', ServiceServiceFactory);
|
||||||
'$q',
|
|
||||||
'Service',
|
|
||||||
function ServiceServiceFactory($q, Service) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.services = function (filters) {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function ServiceServiceFactory(AngularToReact) {
|
||||||
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
|
|
||||||
Service.query({ filters: filters ? filters : {} })
|
return {
|
||||||
.$promise.then(function success(data) {
|
services: useAxios(injectEnvironmentId(getServicesAngularJS)), // dashboard + service list + swarm visualizer + volume list + stackservice + stack edit
|
||||||
var services = data.map(function (item) {
|
service: useAxios(injectEnvironmentId(getServiceAngularJS)), // service edit + task edit
|
||||||
return new ServiceViewModel(item);
|
remove: useAxios(injectEnvironmentId(removeServiceAngularJS)), // service edit
|
||||||
});
|
update: useAxios(injectEnvironmentId(updateServiceAngularJS)), // service edit
|
||||||
deferred.resolve(services);
|
create: useAxios(injectEnvironmentId(createServiceAngularJS)), // service create
|
||||||
})
|
logs: useAxios(injectEnvironmentId(serviceLogsAngularJS)), // service logs
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve services', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service.service = function (id) {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
* @param {*} filters
|
||||||
Service.get({ id: id })
|
*/
|
||||||
.$promise.then(function success(data) {
|
async function getServicesAngularJS(environmentId, filters) {
|
||||||
var service = new ServiceViewModel(data);
|
const data = await getServices(environmentId, filters);
|
||||||
deferred.resolve(service);
|
return data.map((s) => new ServiceViewModel(s));
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve service details', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
service.remove = function (service) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
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;
|
/**
|
||||||
};
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
* @param {ServiceId} serviceId
|
||||||
service.update = function (serv, config, rollback) {
|
*/
|
||||||
return service.service(serv.Id).then((data) => {
|
async function getServiceAngularJS(environmentId, serviceId) {
|
||||||
const params = {
|
const data = await getService(environmentId, serviceId);
|
||||||
id: serv.Id,
|
return new ServiceViewModel(data);
|
||||||
version: data.Version,
|
|
||||||
};
|
|
||||||
if (rollback) {
|
|
||||||
params.rollback = rollback;
|
|
||||||
}
|
}
|
||||||
return Service.update(params, config).$promise;
|
|
||||||
|
/**
|
||||||
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
* @param {ServiceViewModel} service
|
||||||
|
*/
|
||||||
|
async function removeServiceAngularJS(environmentId, service) {
|
||||||
|
return removeService(environmentId, service.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.logs = function (id, stdout, stderr, timestamps, since, tail) {
|
/**
|
||||||
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 });
|
||||||
|
}
|
||||||
|
|
||||||
var parameters = {
|
/**
|
||||||
id: id,
|
* @param {EnvironmentId} environmentId Injected
|
||||||
stdout: stdout || 0,
|
* @param {ServiceId} id
|
||||||
stderr: stderr || 0,
|
* @param {boolean?} stdout
|
||||||
timestamps: timestamps || 0,
|
* @param {boolean?} stderr
|
||||||
since: since || 0,
|
* @param {boolean?} timestamps
|
||||||
tail: tail || 'all',
|
* @param {number?} since
|
||||||
};
|
* @param {number?} tail
|
||||||
|
*/
|
||||||
Service.logs(parameters)
|
async function serviceLogsAngularJS(environmentId, id, stdout = false, stderr = false, timestamps = false, since = 0, tail = 'all') {
|
||||||
.$promise.then(function success(data) {
|
const data = await getServiceLogs(environmentId, id, {
|
||||||
var logs = formatLogs(data.logs, { stripHeaders: true, withTimestamps: !!timestamps });
|
since,
|
||||||
deferred.resolve(logs);
|
stderr,
|
||||||
})
|
stdout,
|
||||||
.catch(function error(err) {
|
tail,
|
||||||
deferred.reject(err);
|
timestamps,
|
||||||
});
|
});
|
||||||
|
return formatLogs(data, { stripHeaders: true, withTimestamps: !!timestamps });
|
||||||
return deferred.promise;
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -1,27 +1,12 @@
|
||||||
import { SwarmViewModel } from '../models/swarm';
|
import { getSwarm } from '@/react/docker/proxy/queries/useSwarm';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('SwarmService', [
|
angular.module('portainer.docker').factory('SwarmService', SwarmServiceFactory);
|
||||||
'$q',
|
|
||||||
'Swarm',
|
|
||||||
function SwarmServiceFactory($q, Swarm) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.swarm = function (endpointId) {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function SwarmServiceFactory(AngularToReact) {
|
||||||
|
const { useAxios } = AngularToReact;
|
||||||
|
|
||||||
Swarm.get(endpointId ? { endpointId } : undefined)
|
return {
|
||||||
.$promise.then(function success(data) {
|
swarm: useAxios(getSwarm), // stack service
|
||||||
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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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 { EventViewModel } from '../models/event';
|
||||||
import { ping } from './ping';
|
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('SystemService', [
|
angular.module('portainer.docker').factory('SystemService', SystemServiceFactory);
|
||||||
'$q',
|
|
||||||
'System',
|
|
||||||
function SystemServiceFactory($q, System) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.plugins = function () {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function SystemServiceFactory(AngularToReact) {
|
||||||
System.info({})
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
.$promise.then(function success(data) {
|
|
||||||
var plugins = data.Plugins;
|
return {
|
||||||
deferred.resolve(plugins);
|
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
|
||||||
.catch(function error(err) {
|
version: useAxios(injectEnvironmentId(getVersion)), // docker host view + swarm inspect view + stateManager (update endpoint state)
|
||||||
deferred.reject({ msg: 'Unable to retrieve plugins information from system', err: err });
|
events: useAxios(injectEnvironmentId(eventsAngularJS)), // events list
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service.info = function () {
|
/**
|
||||||
return System.info({}).$promise;
|
* @param {EnvironmentId} environmentId Injected
|
||||||
};
|
* @param {{since: string; until: string;}} param1
|
||||||
|
*/
|
||||||
service.ping = function (endpointId) {
|
async function eventsAngularJS(environmentId, { since, until }) {
|
||||||
return ping(endpointId);
|
const data = await getEvents(environmentId, { since, until });
|
||||||
};
|
return data.map((e) => new EventViewModel(e));
|
||||||
|
}
|
||||||
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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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 { TaskViewModel } from '../models/task';
|
||||||
|
import { formatLogs } from '../helpers/logHelper';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('TaskService', [
|
angular.module('portainer.docker').factory('TaskService', TaskServiceFactory);
|
||||||
'$q',
|
|
||||||
'Task',
|
|
||||||
function TaskServiceFactory($q, Task) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.task = function (id) {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function TaskServiceFactory(AngularToReact) {
|
||||||
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
|
|
||||||
Task.get({ id: id })
|
return {
|
||||||
.$promise.then(function success(data) {
|
task: useAxios(injectEnvironmentId(taskAngularJS)), // task edit
|
||||||
var task = new TaskViewModel(data);
|
tasks: useAxios(injectEnvironmentId(tasksAngularJS)), // services list + service edit + swarm visualizer + stack edit
|
||||||
deferred.resolve(task);
|
logs: useAxios(injectEnvironmentId(taskLogsAngularJS)), // task logs
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve task details', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service.tasks = function (filters) {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
* @param {TaskId} id
|
||||||
|
*/
|
||||||
|
async function taskAngularJS(environmentId, id) {
|
||||||
|
const data = await getTask(environmentId, id);
|
||||||
|
return new TaskViewModel(data);
|
||||||
|
}
|
||||||
|
|
||||||
Task.query({ filters: filters ? filters : {} })
|
/**
|
||||||
.$promise.then(function success(data) {
|
* @param {EnvironmentId} environmentId Injected
|
||||||
var tasks = data.map(function (item) {
|
* @param {*} filters
|
||||||
return new TaskViewModel(item);
|
*/
|
||||||
|
async function tasksAngularJS(environmentId, filters) {
|
||||||
|
const data = await getTasks(environmentId, filters);
|
||||||
|
return data.map((t) => new TaskViewModel(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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,
|
||||||
});
|
});
|
||||||
deferred.resolve(tasks);
|
return formatLogs(data, { stripHeaders: true, withTimestamps: !!timestamps });
|
||||||
})
|
}
|
||||||
.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;
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
|
@ -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';
|
import { VolumeViewModel } from '../models/volume';
|
||||||
|
|
||||||
angular.module('portainer.docker').factory('VolumeService', [
|
angular.module('portainer.docker').factory('VolumeService', VolumeServiceFactory);
|
||||||
'$q',
|
|
||||||
'Volume',
|
|
||||||
'VolumeHelper',
|
|
||||||
function VolumeServiceFactory($q, Volume, VolumeHelper) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.volumes = function (params) {
|
/* @ngInject */
|
||||||
var deferred = $q.defer();
|
function VolumeServiceFactory(AngularToReact) {
|
||||||
Volume.query(params)
|
const { useAxios, injectEnvironmentId } = AngularToReact;
|
||||||
.$promise.then(function success(data) {
|
|
||||||
var volumes = data.Volumes || [];
|
return {
|
||||||
volumes = volumes.map(function (item) {
|
volumes: useAxios(injectEnvironmentId(volumesAngularJS)), // dashboard + service create + service edit + volume list
|
||||||
return new VolumeViewModel(item);
|
volume: useAxios(injectEnvironmentId(volumeAngularJS)), // volume edit
|
||||||
});
|
getVolumes: useAxios(injectEnvironmentId(getVolumesAngularJS)), // template list
|
||||||
deferred.resolve(volumes);
|
remove: useAxios(injectEnvironmentId(removeAngularJS)), // volume list + volume edit
|
||||||
})
|
createVolume: useAxios(injectEnvironmentId(createAngularJS)), // volume create
|
||||||
.catch(function error(err) {
|
createVolumeConfiguration, // volume create
|
||||||
deferred.reject({ msg: 'Unable to retrieve volumes', err: err });
|
|
||||||
});
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service.volume = function (id) {
|
/**
|
||||||
var deferred = $q.defer();
|
* @param {EnvironmentId} environmentId Injected
|
||||||
Volume.get({ id: id })
|
* @param {Filters} filters
|
||||||
.$promise.then(function success(data) {
|
*/
|
||||||
var volume = new VolumeViewModel(data);
|
async function volumesAngularJS(environmentId, filters) {
|
||||||
deferred.resolve(volume);
|
const data = await getVolumes(environmentId, filters);
|
||||||
})
|
return data.map((v) => new VolumeViewModel(v));
|
||||||
.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;
|
/**
|
||||||
};
|
* @param {EnvironmentId} environmentId Injected
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
async function volumeAngularJS(environmentId, id) {
|
||||||
|
const data = await getVolume(environmentId, id);
|
||||||
|
return new VolumeViewModel(data);
|
||||||
|
}
|
||||||
|
|
||||||
service.createVolumeConfiguration = function (name, driver, driverOptions) {
|
/**
|
||||||
var volumeConfiguration = {
|
* @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,
|
Name: name,
|
||||||
Driver: driver,
|
Driver: driver,
|
||||||
DriverOpts: VolumeHelper.createDriverOptions(driverOptions),
|
DriverOpts: driverOptions.reduce((res, { name, value }) => ({ ...res, [name]: value }), {}),
|
||||||
};
|
};
|
||||||
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) {
|
* @param {EnvironmentId} environmentId Injected
|
||||||
return service.createVolume(volumeConfiguration);
|
* @param {VolumeConfiguration} volumeConfiguration
|
||||||
});
|
* @param {string?} nodeName
|
||||||
return $q.all(createVolumeQueries);
|
*/
|
||||||
};
|
async function createAngularJS(environmentId, volumeConfiguration, nodeName) {
|
||||||
|
const data = await createVolume(environmentId, volumeConfiguration, { nodeName });
|
||||||
return service;
|
return new VolumeViewModel(data);
|
||||||
},
|
}
|
||||||
]);
|
}
|
||||||
|
|
|
@ -9,28 +9,12 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [
|
||||||
'ContainerService',
|
'ContainerService',
|
||||||
'ImageService',
|
'ImageService',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'ContainerHelper',
|
|
||||||
'ExecService',
|
'ExecService',
|
||||||
'HttpRequestHelper',
|
'HttpRequestHelper',
|
||||||
'LocalStorage',
|
|
||||||
'CONSOLE_COMMANDS_LABEL_PREFIX',
|
'CONSOLE_COMMANDS_LABEL_PREFIX',
|
||||||
'SidebarService',
|
'SidebarService',
|
||||||
'endpoint',
|
'endpoint',
|
||||||
function (
|
function ($scope, $state, $transition$, ContainerService, ImageService, Notifications, ExecService, HttpRequestHelper, CONSOLE_COMMANDS_LABEL_PREFIX, SidebarService, endpoint) {
|
||||||
$scope,
|
|
||||||
$state,
|
|
||||||
$transition$,
|
|
||||||
ContainerService,
|
|
||||||
ImageService,
|
|
||||||
Notifications,
|
|
||||||
ContainerHelper,
|
|
||||||
ExecService,
|
|
||||||
HttpRequestHelper,
|
|
||||||
LocalStorage,
|
|
||||||
CONSOLE_COMMANDS_LABEL_PREFIX,
|
|
||||||
SidebarService,
|
|
||||||
endpoint
|
|
||||||
) {
|
|
||||||
var socket, term;
|
var socket, term;
|
||||||
|
|
||||||
let states = Object.freeze({
|
let states = Object.freeze({
|
||||||
|
@ -97,7 +81,6 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [
|
||||||
$scope.state = states.connecting;
|
$scope.state = states.connecting;
|
||||||
var command = $scope.formValues.isCustomCommand ? $scope.formValues.customCommand : $scope.formValues.command;
|
var command = $scope.formValues.isCustomCommand ? $scope.formValues.customCommand : $scope.formValues.command;
|
||||||
var execConfig = {
|
var execConfig = {
|
||||||
id: $transition$.params().id,
|
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
|
@ -106,7 +89,7 @@ angular.module('portainer.docker').controller('ContainerConsoleController', [
|
||||||
Cmd: commandStringToArray(command),
|
Cmd: commandStringToArray(command),
|
||||||
};
|
};
|
||||||
|
|
||||||
ContainerService.createExec(endpoint.Id, execConfig)
|
ContainerService.createExec(endpoint.Id, $transition$.params().id, execConfig)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
const params = {
|
const params = {
|
||||||
endpointId: $state.params.endpointId,
|
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 { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||||
import { ResourceControlType } from '@/react/portainer/access-control/types';
|
import { ResourceControlType } from '@/react/portainer/access-control/types';
|
||||||
import { confirmContainerRecreation } from '@/react/docker/containers/ItemView/ConfirmRecreationModal';
|
import { confirmContainerRecreation } from '@/react/docker/containers/ItemView/ConfirmRecreationModal';
|
||||||
|
import { commitContainer } from '@/react/docker/proxy/queries/useCommitContainerMutation';
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('ContainerController', [
|
angular.module('portainer.docker').controller('ContainerController', [
|
||||||
'$q',
|
'$q',
|
||||||
|
@ -13,38 +14,14 @@ angular.module('portainer.docker').controller('ContainerController', [
|
||||||
'$transition$',
|
'$transition$',
|
||||||
'$filter',
|
'$filter',
|
||||||
'$async',
|
'$async',
|
||||||
'Commit',
|
|
||||||
'ContainerHelper',
|
|
||||||
'ContainerService',
|
'ContainerService',
|
||||||
'ImageHelper',
|
'ImageHelper',
|
||||||
'NetworkService',
|
'NetworkService',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'ResourceControlService',
|
|
||||||
'RegistryService',
|
|
||||||
'ImageService',
|
|
||||||
'HttpRequestHelper',
|
'HttpRequestHelper',
|
||||||
'Authentication',
|
'Authentication',
|
||||||
'endpoint',
|
'endpoint',
|
||||||
function (
|
function ($q, $scope, $state, $transition$, $filter, $async, ContainerService, ImageHelper, NetworkService, Notifications, HttpRequestHelper, Authentication, endpoint) {
|
||||||
$q,
|
|
||||||
$scope,
|
|
||||||
$state,
|
|
||||||
$transition$,
|
|
||||||
$filter,
|
|
||||||
$async,
|
|
||||||
Commit,
|
|
||||||
ContainerHelper,
|
|
||||||
ContainerService,
|
|
||||||
ImageHelper,
|
|
||||||
NetworkService,
|
|
||||||
Notifications,
|
|
||||||
ResourceControlService,
|
|
||||||
RegistryService,
|
|
||||||
ImageService,
|
|
||||||
HttpRequestHelper,
|
|
||||||
Authentication,
|
|
||||||
endpoint
|
|
||||||
) {
|
|
||||||
$scope.resourceType = ResourceControlType.Container;
|
$scope.resourceType = ResourceControlType.Container;
|
||||||
$scope.endpoint = endpoint;
|
$scope.endpoint = endpoint;
|
||||||
$scope.isAdmin = Authentication.isAdmin();
|
$scope.isAdmin = Authentication.isAdmin();
|
||||||
|
@ -227,7 +204,7 @@ angular.module('portainer.docker').controller('ContainerController', [
|
||||||
|
|
||||||
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
|
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
|
||||||
$scope.state.leaveNetworkInProgress = true;
|
$scope.state.leaveNetworkInProgress = true;
|
||||||
NetworkService.disconnectContainer(networkId, container.Id, false)
|
NetworkService.disconnectContainer(networkId, container.Id)
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Container left network', container.Id);
|
Notifications.success('Container left network', container.Id);
|
||||||
$state.reload();
|
$state.reload();
|
||||||
|
@ -260,7 +237,7 @@ angular.module('portainer.docker').controller('ContainerController', [
|
||||||
const registryModel = $scope.config.RegistryModel;
|
const registryModel = $scope.config.RegistryModel;
|
||||||
const imageConfig = ImageHelper.createImageConfigForContainer(registryModel);
|
const imageConfig = ImageHelper.createImageConfigForContainer(registryModel);
|
||||||
try {
|
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);
|
Notifications.success('Image created', $transition$.params().id);
|
||||||
$state.reload();
|
$state.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -6,10 +6,10 @@ angular.module('portainer.docker').controller('EventsController', [
|
||||||
'SystemService',
|
'SystemService',
|
||||||
function ($scope, Notifications, SystemService) {
|
function ($scope, Notifications, SystemService) {
|
||||||
function initView() {
|
function initView() {
|
||||||
var from = moment().subtract(24, 'hour').unix();
|
const since = moment().subtract(24, 'hour').unix();
|
||||||
var to = moment().unix();
|
const until = moment().unix();
|
||||||
|
|
||||||
SystemService.events(from, to)
|
SystemService.events({ since, until })
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
$scope.events = data;
|
$scope.events = data;
|
||||||
})
|
})
|
||||||
|
|
|
@ -35,15 +35,10 @@ angular.module('portainer.docker').controller('ImagesController', [
|
||||||
const registryModel = $scope.formValues.RegistryModel;
|
const registryModel = $scope.formValues.RegistryModel;
|
||||||
|
|
||||||
var nodeName = $scope.formValues.NodeName;
|
var nodeName = $scope.formValues.NodeName;
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
|
||||||
|
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
ImageService.pullImage(registryModel, false)
|
ImageService.pullImage(registryModel, nodeName)
|
||||||
.then(function success(data) {
|
.then(function success() {
|
||||||
var err = data[data.length - 1].errorDetail;
|
|
||||||
if (err) {
|
|
||||||
return Notifications.error('Failure', err, 'Unable to pull image');
|
|
||||||
}
|
|
||||||
Notifications.success('Image successfully pulled', registryModel.Image);
|
Notifications.success('Image successfully pulled', registryModel.Image);
|
||||||
$state.reload();
|
$state.reload();
|
||||||
})
|
})
|
||||||
|
|
|
@ -230,11 +230,8 @@ angular.module('portainer.docker').controller('CreateNetworkController', [
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNetwork(context) {
|
function createNetwork(context) {
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName);
|
|
||||||
HttpRequestHelper.setPortainerAgentManagerOperation(context.managerOperation);
|
|
||||||
|
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
NetworkService.create(context.networkConfiguration)
|
NetworkService.create(context.networkConfiguration, { nodeName: context.nodeName, agentManagerOperation: context.managerOperation })
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
const userId = context.userDetails.ID;
|
const userId = context.userDetails.ID;
|
||||||
const accessControlData = context.accessControlData;
|
const accessControlData = context.accessControlData;
|
||||||
|
|
|
@ -14,7 +14,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$state',
|
'$state',
|
||||||
'$timeout',
|
'$timeout',
|
||||||
'Service',
|
'ServiceService',
|
||||||
'ServiceHelper',
|
'ServiceHelper',
|
||||||
'ConfigService',
|
'ConfigService',
|
||||||
'ConfigHelper',
|
'ConfigHelper',
|
||||||
|
@ -29,8 +29,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'FormValidator',
|
'FormValidator',
|
||||||
'PluginService',
|
'PluginService',
|
||||||
'RegistryService',
|
|
||||||
'HttpRequestHelper',
|
|
||||||
'NodeService',
|
'NodeService',
|
||||||
'WebhookService',
|
'WebhookService',
|
||||||
'endpoint',
|
'endpoint',
|
||||||
|
@ -39,7 +37,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||||
$scope,
|
$scope,
|
||||||
$state,
|
$state,
|
||||||
$timeout,
|
$timeout,
|
||||||
Service,
|
ServiceService,
|
||||||
ServiceHelper,
|
ServiceHelper,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
ConfigHelper,
|
ConfigHelper,
|
||||||
|
@ -54,8 +52,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||||
Notifications,
|
Notifications,
|
||||||
FormValidator,
|
FormValidator,
|
||||||
PluginService,
|
PluginService,
|
||||||
RegistryService,
|
|
||||||
HttpRequestHelper,
|
|
||||||
NodeService,
|
NodeService,
|
||||||
WebhookService,
|
WebhookService,
|
||||||
endpoint
|
endpoint
|
||||||
|
@ -523,11 +519,9 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||||
|
|
||||||
function createNewService(config, accessControlData) {
|
function createNewService(config, accessControlData) {
|
||||||
const registryModel = $scope.formValues.RegistryModel;
|
const registryModel = $scope.formValues.RegistryModel;
|
||||||
var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : '';
|
|
||||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
|
||||||
|
|
||||||
Service.create(config)
|
ServiceService.create(config, registryModel.Registry.Authentication ? registryModel.Registry.Id : 0)
|
||||||
.$promise.then(function success(data) {
|
.then(function success(data) {
|
||||||
const serviceId = data.ID;
|
const serviceId = data.ID;
|
||||||
const resourceControl = data.Portainer.ResourceControl;
|
const resourceControl = data.Portainer.ResourceControl;
|
||||||
const userId = Authentication.getUserDetails().ID;
|
const userId = Authentication.getUserDetails().ID;
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { VolumesNFSFormData } from '../../../components/volumesNFSForm/volumesNF
|
||||||
import { VolumesCIFSFormData } from '../../../components/volumesCIFSForm/volumesCifsFormModel';
|
import { VolumesCIFSFormData } from '../../../components/volumesCIFSForm/volumesCifsFormModel';
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('CreateVolumeController', [
|
angular.module('portainer.docker').controller('CreateVolumeController', [
|
||||||
'$q',
|
|
||||||
'$scope',
|
'$scope',
|
||||||
'$state',
|
'$state',
|
||||||
'VolumeService',
|
'VolumeService',
|
||||||
|
@ -12,9 +11,8 @@ angular.module('portainer.docker').controller('CreateVolumeController', [
|
||||||
'Authentication',
|
'Authentication',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'FormValidator',
|
'FormValidator',
|
||||||
'HttpRequestHelper',
|
|
||||||
'endpoint',
|
'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.endpoint = endpoint;
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
|
@ -126,10 +124,9 @@ angular.module('portainer.docker').controller('CreateVolumeController', [
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeName = $scope.formValues.NodeName;
|
var nodeName = $scope.formValues.NodeName;
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
|
||||||
|
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
VolumeService.createVolume(volumeConfiguration)
|
VolumeService.createVolume(volumeConfiguration, nodeName)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
const userId = userDetails.ID;
|
const userId = userDetails.ID;
|
||||||
const resourceControl = data.ResourceControl;
|
const resourceControl = data.ResourceControl;
|
||||||
|
|
|
@ -21,7 +21,7 @@ angular.module('portainer.docker').controller('VolumeController', [
|
||||||
$scope.removeVolume = function removeVolume() {
|
$scope.removeVolume = function removeVolume() {
|
||||||
confirmDelete('Do you want to remove this volume?').then((confirmed) => {
|
confirmDelete('Do you want to remove this volume?').then((confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
VolumeService.remove($scope.volume)
|
VolumeService.remove($scope.volume.Id)
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Volume successfully removed', $transition$.params().id);
|
Notifications.success('Volume successfully removed', $transition$.params().id);
|
||||||
$state.go('docker.volumes', {});
|
$state.go('docker.volumes', {});
|
||||||
|
|
|
@ -8,14 +8,12 @@ angular.module('portainer.docker').controller('VolumesController', [
|
||||||
'ServiceService',
|
'ServiceService',
|
||||||
'VolumeHelper',
|
'VolumeHelper',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'HttpRequestHelper',
|
|
||||||
'Authentication',
|
'Authentication',
|
||||||
'endpoint',
|
'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) {
|
$scope.removeAction = async function (selectedItems) {
|
||||||
async function doRemove(volume) {
|
async function doRemove(volume) {
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader(volume.NodeName);
|
return VolumeService.remove(volume.Id, volume.NodeName)
|
||||||
return VolumeService.remove(volume)
|
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Volume successfully removed', volume.Id);
|
Notifications.success('Volume successfully removed', volume.Id);
|
||||||
var index = $scope.volumes.indexOf(volume);
|
var index = $scope.volumes.indexOf(volume);
|
||||||
|
@ -36,8 +34,8 @@ angular.module('portainer.docker').controller('VolumesController', [
|
||||||
var endpointRole = $scope.applicationState.endpoint.mode.role;
|
var endpointRole = $scope.applicationState.endpoint.mode.role;
|
||||||
|
|
||||||
$q.all({
|
$q.all({
|
||||||
attached: VolumeService.volumes({ filters: { dangling: ['false'] } }),
|
attached: VolumeService.volumes({ dangling: ['false'] }),
|
||||||
dangling: VolumeService.volumes({ filters: { dangling: ['true'] } }),
|
dangling: VolumeService.volumes({ dangling: ['true'] }),
|
||||||
services: endpointProvider === 'DOCKER_SWARM_MODE' && endpointRole === 'MANAGER' ? ServiceService.services() : [],
|
services: endpointProvider === 'DOCKER_SWARM_MODE' && endpointRole === 'MANAGER' ? ServiceService.services() : [],
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.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 { rawResponse } from 'Kubernetes/rest/response/transform';
|
||||||
import { logsHandler } from 'Docker/rest/response/handlers';
|
|
||||||
|
|
||||||
angular.module('portainer.kubernetes').factory('KubernetesPods', [
|
angular.module('portainer.kubernetes').factory('KubernetesPods', [
|
||||||
'$resource',
|
'$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)
|
SwarmService.swarm(targetEndpointId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var swarm = 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 });
|
deferred.reject({ msg: 'Target environment is located in the same Swarm cluster as the current environment', err: null });
|
||||||
return;
|
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() {
|
.then(function success() {
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
|
@ -182,10 +182,10 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
service.swarmStacks = function (endpointId, includeExternalStacks, filters = {}) {
|
service.swarmStacks = function (endpointId, includeExternalStacks, filters = {}) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
SwarmService.swarm()
|
SwarmService.swarm(endpointId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var swarm = data;
|
var swarm = data;
|
||||||
filters = { SwarmID: swarm.Id, ...filters };
|
filters = { SwarmID: swarm.ID, ...filters };
|
||||||
|
|
||||||
return $q.all({
|
return $q.all({
|
||||||
stacks: Stack.query({ filters: filters }).$promise,
|
stacks: Stack.query({ filters: filters }).$promise,
|
||||||
|
@ -239,10 +239,10 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
if (stack.Type == 1) {
|
if (stack.Type == 1) {
|
||||||
SwarmService.swarm()
|
SwarmService.swarm(endpointId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
const swarm = 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) {
|
.then(function success(data) {
|
||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
|
@ -305,10 +305,10 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
service.createSwarmStackFromFileUpload = function (name, stackFile, env, endpointId) {
|
service.createSwarmStackFromFileUpload = function (name, stackFile, env, endpointId) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
SwarmService.swarm()
|
SwarmService.swarm(endpointId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var swarm = 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) {
|
.then(function success(data) {
|
||||||
deferred.resolve(data.data);
|
deferred.resolve(data.data);
|
||||||
|
@ -335,7 +335,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
.then(function success(swarm) {
|
.then(function success(swarm) {
|
||||||
var payload = {
|
var payload = {
|
||||||
Name: name,
|
Name: name,
|
||||||
SwarmID: swarm.Id,
|
SwarmID: swarm.ID,
|
||||||
StackFileContent: stackFileContent,
|
StackFileContent: stackFileContent,
|
||||||
Env: env,
|
Env: env,
|
||||||
};
|
};
|
||||||
|
@ -376,12 +376,12 @@ angular.module('portainer.app').factory('StackService', [
|
||||||
service.createSwarmStackFromGitRepository = function (name, repositoryOptions, env, endpointId) {
|
service.createSwarmStackFromGitRepository = function (name, repositoryOptions, env, endpointId) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
SwarmService.swarm()
|
SwarmService.swarm(endpointId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var swarm = data;
|
var swarm = data;
|
||||||
var payload = {
|
var payload = {
|
||||||
Name: name,
|
Name: name,
|
||||||
SwarmID: swarm.Id,
|
SwarmID: swarm.ID,
|
||||||
RepositoryURL: repositoryOptions.RepositoryURL,
|
RepositoryURL: repositoryOptions.RepositoryURL,
|
||||||
RepositoryReferenceName: repositoryOptions.RepositoryReferenceName,
|
RepositoryReferenceName: repositoryOptions.RepositoryReferenceName,
|
||||||
ComposeFile: repositoryOptions.ComposeFilePathInRepository,
|
ComposeFile: repositoryOptions.ComposeFilePathInRepository,
|
||||||
|
|
|
@ -21,6 +21,8 @@ import {
|
||||||
portainerAgentManagerOperation,
|
portainerAgentManagerOperation,
|
||||||
portainerAgentTargetHeader,
|
portainerAgentTargetHeader,
|
||||||
} from './http-request.helper';
|
} from './http-request.helper';
|
||||||
|
import { dockerMaxAPIVersionInterceptor } from './dockerMaxApiVersionInterceptor';
|
||||||
|
import { MAX_DOCKER_API_VERSION } from './dockerMaxApiVersion';
|
||||||
|
|
||||||
const portainerCacheHeader = 'X-Portainer-Cache';
|
const portainerCacheHeader = 'X-Portainer-Cache';
|
||||||
|
|
||||||
|
@ -48,7 +50,10 @@ function headerInterpreter(
|
||||||
return 'not enough headers';
|
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) => {
|
axios.interceptors.request.use((req) => {
|
||||||
dispatchCacheRefreshEventIfNeeded(req);
|
dispatchCacheRefreshEventIfNeeded(req);
|
||||||
return req;
|
return req;
|
||||||
|
@ -118,13 +123,14 @@ export function agentInterceptor(config: InternalAxiosRequestConfig) {
|
||||||
return newConfig;
|
return newConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
axios.interceptors.request.use(dockerMaxAPIVersionInterceptor);
|
||||||
axios.interceptors.request.use(agentInterceptor);
|
axios.interceptors.request.use(agentInterceptor);
|
||||||
|
|
||||||
axios.interceptors.response.use(undefined, (error) => {
|
axios.interceptors.response.use(undefined, (error) => {
|
||||||
if (
|
if (
|
||||||
error.response?.status === 401 &&
|
error.response?.status === 401 &&
|
||||||
!error.config.url.includes('/v2/') &&
|
!error.config.url.includes('/v2/') && // docker proxy through agent
|
||||||
!error.config.url.includes('/api/v4/') &&
|
!error.config.url.includes('/api/v4/') && // gitlab proxy
|
||||||
isTransitionRequiresAuthentication()
|
isTransitionRequiresAuthentication()
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -188,6 +194,12 @@ export function defaultErrorParser(axiosError: AxiosError<unknown>) {
|
||||||
const error = new Error(message);
|
const error = new Error(message);
|
||||||
return { error, details };
|
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
|
const details = axiosError.response?.data
|
||||||
? axiosError.response?.data.toString()
|
? axiosError.response?.data.toString()
|
||||||
|
@ -196,6 +208,16 @@ export function defaultErrorParser(axiosError: AxiosError<unknown>) {
|
||||||
return { error, details };
|
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(
|
export function isDefaultResponse(
|
||||||
data: unknown
|
data: unknown
|
||||||
): data is DefaultAxiosErrorType {
|
): data is DefaultAxiosErrorType {
|
||||||
|
@ -249,3 +271,18 @@ export function json2formData(json: Record<string, unknown>) {
|
||||||
|
|
||||||
return formData;
|
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 { environmentStore } from '@/react/hooks/current-environment-store';
|
||||||
import {
|
import {
|
||||||
Environment,
|
Environment,
|
||||||
|
|
|
@ -1,66 +1,28 @@
|
||||||
import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models';
|
import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models';
|
||||||
import { genericHandler, jsonObjectsToArrayHandler } from '../../docker/rest/response/handlers';
|
|
||||||
|
|
||||||
angular.module('portainer.app').factory('FileUploadService', [
|
angular.module('portainer.app').factory('FileUploadService', FileUploadFactory);
|
||||||
'$q',
|
|
||||||
'Upload',
|
|
||||||
'EndpointProvider',
|
|
||||||
function FileUploadFactory($q, Upload, EndpointProvider) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
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) {
|
function uploadFile(url, file) {
|
||||||
return Upload.upload({ url: url, data: { file: file } });
|
return Upload.upload({ url: url, data: { file: file } });
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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.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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
service.createSchedule = function (payload) {
|
service.createSchedule = function (payload) {
|
||||||
return Upload.upload({
|
return Upload.upload({
|
||||||
url: 'api/edge_jobs/create/file',
|
url: 'api/edge_jobs/create/file',
|
||||||
|
@ -86,7 +48,7 @@ angular.module('portainer.app').factory('FileUploadService', [
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createSwarmStack = function (stackName, swarmId, file, env, endpointId) {
|
service.createSwarmStack = function (stackName, swarmId, file, env, endpointId, webhook) {
|
||||||
return Upload.upload({
|
return Upload.upload({
|
||||||
url: `api/stacks/create/swarm/file?endpointId=${endpointId}`,
|
url: `api/stacks/create/swarm/file?endpointId=${endpointId}`,
|
||||||
data: {
|
data: {
|
||||||
|
@ -94,30 +56,34 @@ angular.module('portainer.app').factory('FileUploadService', [
|
||||||
Name: stackName,
|
Name: stackName,
|
||||||
SwarmID: swarmId,
|
SwarmID: swarmId,
|
||||||
Env: Upload.json(env),
|
Env: Upload.json(env),
|
||||||
|
Webhook: webhook,
|
||||||
},
|
},
|
||||||
ignoreLoadingBar: true,
|
ignoreLoadingBar: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createComposeStack = function (stackName, file, env, endpointId) {
|
service.createComposeStack = function (stackName, file, env, endpointId, webhook) {
|
||||||
return Upload.upload({
|
return Upload.upload({
|
||||||
url: `api/stacks/create/standalone/file?endpointId=${endpointId}`,
|
url: `api/stacks/create/standalone/file?endpointId=${endpointId}`,
|
||||||
data: {
|
data: {
|
||||||
file: file,
|
file: file,
|
||||||
Name: stackName,
|
Name: stackName,
|
||||||
Env: Upload.json(env),
|
Env: Upload.json(env),
|
||||||
|
Webhook: webhook,
|
||||||
},
|
},
|
||||||
ignoreLoadingBar: true,
|
ignoreLoadingBar: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createEdgeStack = function createEdgeStack({ EdgeGroups, envVars, ...payload }, file) {
|
service.createEdgeStack = function createEdgeStack({ EdgeGroups, Registries, envVars, staggerConfig, ...payload }, file, dryrun) {
|
||||||
return Upload.upload({
|
return Upload.upload({
|
||||||
url: `api/edge_stacks/create/file`,
|
url: `api/edge_stacks/create/file?dryrun=${dryrun}`,
|
||||||
data: {
|
data: {
|
||||||
file,
|
file,
|
||||||
EdgeGroups: Upload.json(EdgeGroups),
|
EdgeGroups: Upload.json(EdgeGroups),
|
||||||
|
Registries: Upload.json(Registries),
|
||||||
EnvVars: Upload.json(envVars),
|
EnvVars: Upload.json(envVars),
|
||||||
|
StaggerConfig: Upload.json(staggerConfig),
|
||||||
...payload,
|
...payload,
|
||||||
},
|
},
|
||||||
ignoreLoadingBar: true,
|
ignoreLoadingBar: true,
|
||||||
|
@ -152,7 +118,10 @@ angular.module('portainer.app').factory('FileUploadService', [
|
||||||
TLSCAFile,
|
TLSCAFile,
|
||||||
TLSCertFile,
|
TLSCertFile,
|
||||||
TLSKeyFile,
|
TLSKeyFile,
|
||||||
checkinInterval
|
checkinInterval,
|
||||||
|
EdgePingInterval,
|
||||||
|
EdgeSnapshotInterval,
|
||||||
|
EdgeCommandInterval
|
||||||
) {
|
) {
|
||||||
return Upload.upload({
|
return Upload.upload({
|
||||||
url: 'api/endpoints',
|
url: 'api/endpoints',
|
||||||
|
@ -170,6 +139,9 @@ angular.module('portainer.app').factory('FileUploadService', [
|
||||||
TLSCertFile: TLSCertFile,
|
TLSCertFile: TLSCertFile,
|
||||||
TLSKeyFile: TLSKeyFile,
|
TLSKeyFile: TLSKeyFile,
|
||||||
CheckinInterval: checkinInterval,
|
CheckinInterval: checkinInterval,
|
||||||
|
EdgePingInterval: EdgePingInterval,
|
||||||
|
EdgeSnapshotInterval: EdgeSnapshotInterval,
|
||||||
|
EdgeCommandInterval: EdgeCommandInterval,
|
||||||
},
|
},
|
||||||
ignoreLoadingBar: true,
|
ignoreLoadingBar: true,
|
||||||
});
|
});
|
||||||
|
@ -234,5 +206,4 @@ angular.module('portainer.app').factory('FileUploadService', [
|
||||||
};
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
},
|
}
|
||||||
]);
|
|
||||||
|
|
|
@ -4,9 +4,11 @@ import { apiServicesModule } from './api';
|
||||||
import { Notifications } from './notifications';
|
import { Notifications } from './notifications';
|
||||||
import { HttpRequestHelperAngular } from './http-request.helper';
|
import { HttpRequestHelperAngular } from './http-request.helper';
|
||||||
import { EndpointProvider } from './endpointProvider';
|
import { EndpointProvider } from './endpointProvider';
|
||||||
|
import { AngularToReact } from './angularToReact';
|
||||||
|
|
||||||
export default angular
|
export default angular
|
||||||
.module('portainer.app.services', [apiServicesModule])
|
.module('portainer.app.services', [apiServicesModule])
|
||||||
.factory('Notifications', Notifications)
|
.factory('Notifications', Notifications)
|
||||||
.factory('EndpointProvider', EndpointProvider)
|
.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 { 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';
|
import { PortMapping } from './container-instances/CreateView/PortsMappingField';
|
||||||
|
|
||||||
|
@ -47,14 +47,13 @@ interface ContainerGroupProperties {
|
||||||
osType: OS;
|
osType: OS;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ContainerGroup = {
|
export type ContainerGroup = PortainerResponse<{
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
location: string;
|
location: string;
|
||||||
type: string;
|
type: string;
|
||||||
properties: ContainerGroupProperties;
|
properties: ContainerGroupProperties;
|
||||||
Portainer?: PortainerMetadata;
|
}>;
|
||||||
};
|
|
||||||
|
|
||||||
export interface Subscription {
|
export interface Subscription {
|
||||||
subscriptionId: string;
|
subscriptionId: string;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Box } from 'lucide-react';
|
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 { Environment } from '@/react/portainer/environments/types';
|
||||||
import { createStore } from '@/react/docker/containers/ListView/ContainersDatatable/datatable-store';
|
import { createStore } from '@/react/docker/containers/ListView/ContainersDatatable/datatable-store';
|
||||||
import { useColumns } from '@/react/docker/containers/ListView/ContainersDatatable/columns';
|
import { useColumns } from '@/react/docker/containers/ListView/ContainersDatatable/columns';
|
||||||
|
@ -20,7 +20,7 @@ import {
|
||||||
import { TableSettingsProvider } from '@@/datatables/useTableSettings';
|
import { TableSettingsProvider } from '@@/datatables/useTableSettings';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
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';
|
import { RowProvider } from '../../../docker/containers/ListView/ContainersDatatable/RowContext';
|
||||||
|
|
||||||
const storageKey = 'stack-containers';
|
const storageKey = 'stack-containers';
|
||||||
|
@ -71,7 +71,7 @@ export function StackContainersDatatable({ environment, stackName }: Props) {
|
||||||
data-cy="stack-containers-datatable"
|
data-cy="stack-containers-datatable"
|
||||||
renderTableSettings={(tableInstance) => (
|
renderTableSettings={(tableInstance) => (
|
||||||
<>
|
<>
|
||||||
<ColumnVisibilityMenu<DockerContainer>
|
<ColumnVisibilityMenu<ContainerListViewModel>
|
||||||
table={tableInstance}
|
table={tableInstance}
|
||||||
onChange={(hiddenColumns) => {
|
onChange={(hiddenColumns) => {
|
||||||
tableState.setHiddenColumns(hiddenColumns);
|
tableState.setHiddenColumns(hiddenColumns);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||||
|
|
||||||
import { queryKeys } from '../queries/utils';
|
import { queryKeys } from '../queries/utils';
|
||||||
import { buildDockerUrl } from '../queries/utils/root';
|
import { buildDockerUrl } from '../queries/utils/buildDockerUrl';
|
||||||
|
|
||||||
interface DashboardResponse {
|
interface DashboardResponse {
|
||||||
containers: {
|
containers: {
|
||||||
|
@ -29,7 +29,7 @@ export function useDashboard(envId: EnvironmentId) {
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<DashboardResponse>(
|
const res = await axios.get<DashboardResponse>(
|
||||||
`${buildDockerUrl(envId)}/dashboard`
|
buildDockerUrl(envId, 'dashboard')
|
||||||
);
|
);
|
||||||
return res.data;
|
return res.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import axios, {
|
||||||
} from '@/portainer/services/axios';
|
} from '@/portainer/services/axios';
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
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) {
|
export function useApiVersion(environmentId: EnvironmentId) {
|
||||||
return useQuery(['environment', environmentId, 'agent', 'ping'], () =>
|
return useQuery(['environment', environmentId, 'agent', 'ping'], () =>
|
||||||
|
@ -16,7 +16,9 @@ export function useApiVersion(environmentId: EnvironmentId) {
|
||||||
|
|
||||||
async function getApiVersion(environmentId: EnvironmentId) {
|
async function getApiVersion(environmentId: EnvironmentId) {
|
||||||
try {
|
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;
|
return parseInt(headers['portainer-agent-api-version'], 10) || 1;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 404 - agent is up - set version to 1
|
// 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 { UserId } from '@/portainer/users/types';
|
||||||
import { getDefaultImageConfig } from '@/react/portainer/registries/utils/getImageConfig';
|
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 { toViewModel as toPortsMappingViewModel } from './PortsMappingField.viewModel';
|
||||||
import { Values } from './BaseForm';
|
import { Values } from './BaseForm';
|
||||||
|
|
||||||
export function toViewModel(
|
export function toViewModel(
|
||||||
config: ContainerResponse,
|
config: ContainerDetailsResponse,
|
||||||
isPureAdmin: boolean,
|
isPureAdmin: boolean,
|
||||||
currentUserId: UserId,
|
currentUserId: UserId,
|
||||||
nodeName: string,
|
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 { capabilities } from './types';
|
||||||
import { Values } from './CapabilitiesTab';
|
import { Values } from './CapabilitiesTab';
|
||||||
|
|
||||||
export function toViewModel(config: ContainerJSON): Values {
|
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||||
const { CapAdd, CapDrop } = getDefaults(config);
|
const { CapAdd, CapDrop } = getDefaults(config);
|
||||||
|
|
||||||
const missingCaps = capabilities
|
const missingCaps = capabilities
|
||||||
|
@ -15,7 +15,7 @@ export function toViewModel(config: ContainerJSON): Values {
|
||||||
|
|
||||||
return [...CapAdd, ...missingCaps];
|
return [...CapAdd, ...missingCaps];
|
||||||
|
|
||||||
function getDefaults(config: ContainerJSON) {
|
function getDefaults(config: ContainerDetailsJSON) {
|
||||||
return {
|
return {
|
||||||
CapAdd: config.HostConfig?.CapAdd || [],
|
CapAdd: config.HostConfig?.CapAdd || [],
|
||||||
CapDrop: config.HostConfig?.CapDrop || [],
|
CapDrop: config.HostConfig?.CapDrop || [],
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { HostConfig } from 'docker-types/generated/1.41';
|
||||||
|
|
||||||
import { commandArrayToString } from '@/docker/helpers/containers';
|
import { commandArrayToString } from '@/docker/helpers/containers';
|
||||||
|
|
||||||
import { ContainerJSON } from '../../queries/container';
|
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||||
|
|
||||||
import { ConsoleConfig, ConsoleSetting } from './ConsoleSettings';
|
import { ConsoleConfig, ConsoleSetting } from './ConsoleSettings';
|
||||||
import { LogConfig } from './LoggerConfig';
|
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) {
|
if (!config.Config) {
|
||||||
return getDefaultViewModel();
|
return getDefaultViewModel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { InformationPanel } from '@@/InformationPanel';
|
||||||
import { TextTip } from '@@/Tip/TextTip';
|
import { TextTip } from '@@/Tip/TextTip';
|
||||||
import { HelpLink } from '@@/HelpLink';
|
import { HelpLink } from '@@/HelpLink';
|
||||||
|
|
||||||
import { useContainers } from '../queries/containers';
|
import { useContainers } from '../queries/useContainers';
|
||||||
import { useSystemLimits, useIsWindows } from '../../proxy/queries/useInfo';
|
import { useSystemLimits, useIsWindows } from '../../proxy/queries/useInfo';
|
||||||
|
|
||||||
import { useCreateOrReplaceMutation } from './useCreateMutation';
|
import { useCreateOrReplaceMutation } from './useCreateMutation';
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { parseArrayOfStrings } from '@@/form-components/EnvironmentVariablesFieldset/utils';
|
import { parseArrayOfStrings } from '@@/form-components/EnvironmentVariablesFieldset/utils';
|
||||||
|
|
||||||
import { ContainerJSON } from '../../queries/container';
|
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||||
|
|
||||||
export function getDefaultViewModel() {
|
export function getDefaultViewModel() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toViewModel(container: ContainerJSON) {
|
export function toViewModel(container: ContainerDetailsJSON) {
|
||||||
return parseArrayOfStrings(container.Config?.Env);
|
return parseArrayOfStrings(container.Config?.Env);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { ContainerJSON } from '../../queries/container';
|
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||||
|
|
||||||
import { Values } from './types';
|
import { Values } from './types';
|
||||||
|
|
||||||
export function toViewModel(config: ContainerJSON): Values {
|
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||||
if (!config || !config.Config || !config.Config.Labels) {
|
if (!config || !config.Config || !config.Config.Labels) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
|
||||||
import { Option, PortainerSelect } from '@@/form-components/PortainerSelect';
|
import { Option, PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||||
|
|
||||||
import { useContainers } from '../../queries/containers';
|
import { useContainers } from '../../queries/useContainers';
|
||||||
import { ContainerStatus } from '../../types';
|
import { ContainerStatus } from '../../types';
|
||||||
|
|
||||||
export function ContainerSelector({
|
export function ContainerSelector({
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { DockerNetwork } from '@/react/docker/networks/types';
|
import { DockerNetwork } from '@/react/docker/networks/types';
|
||||||
|
|
||||||
import { ContainerJSON } from '../../queries/container';
|
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||||
import { DockerContainer } from '../../types';
|
import { ContainerListViewModel } from '../../types';
|
||||||
|
|
||||||
import { CONTAINER_MODE, Values } from './types';
|
import { CONTAINER_MODE, Values } from './types';
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ export function getDefaultViewModel(isWindows: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toViewModel(
|
export function toViewModel(
|
||||||
config: ContainerJSON,
|
config: ContainerDetailsJSON,
|
||||||
networks: Array<DockerNetwork>,
|
networks: Array<DockerNetwork>,
|
||||||
runningContainers: Array<DockerContainer> = []
|
runningContainers: Array<ContainerListViewModel> = []
|
||||||
): Values {
|
): Values {
|
||||||
const dns = config.HostConfig?.Dns;
|
const dns = config.HostConfig?.Dns;
|
||||||
const [primaryDns = '', secondaryDns = ''] = dns || [];
|
const [primaryDns = '', secondaryDns = ''] = dns || [];
|
||||||
|
@ -62,9 +62,9 @@ export function toViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNetworkMode(
|
function getNetworkMode(
|
||||||
config: ContainerJSON,
|
config: ContainerDetailsJSON,
|
||||||
networks: Array<DockerNetwork>,
|
networks: Array<DockerNetwork>,
|
||||||
runningContainers: Array<DockerContainer> = []
|
runningContainers: Array<ContainerListViewModel> = []
|
||||||
) {
|
) {
|
||||||
let networkMode = config.HostConfig?.NetworkMode || '';
|
let networkMode = config.HostConfig?.NetworkMode || '';
|
||||||
if (!networkMode) {
|
if (!networkMode) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useCurrentStateAndParams } from '@uirouter/react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FormikHelpers } from 'formik/dist/types';
|
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 { notifySuccess } from '@/portainer/services/notifications';
|
||||||
import { mutationOptions, withError } from '@/react-tools/react-query';
|
import { mutationOptions, withError } from '@/react-tools/react-query';
|
||||||
import { useSystemLimits } from '@/react/docker/proxy/queries/useInfo';
|
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 { toDevicesViewModel } from './DevicesField';
|
||||||
import { gpuFieldsetUtils } from './GpuFieldset';
|
import { gpuFieldsetUtils } from './GpuFieldset';
|
||||||
import { toViewModelCpu, toViewModelMemory } from './memory-utils';
|
import { toViewModelCpu, toViewModelMemory } from './memory-utils';
|
||||||
import { Values } from './ResourcesTab';
|
import { Values } from './ResourcesTab';
|
||||||
|
|
||||||
export function toViewModel(config: ContainerJSON): Values {
|
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||||
return {
|
return {
|
||||||
runtime: {
|
runtime: {
|
||||||
privileged: config.HostConfig?.Privileged || false,
|
privileged: config.HostConfig?.Privileged || false,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { ContainerJSON } from '../../queries/container';
|
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||||
|
|
||||||
import { RestartPolicy } from './types';
|
import { RestartPolicy } from './types';
|
||||||
|
|
||||||
export function toViewModel(config: ContainerJSON): RestartPolicy {
|
export function toViewModel(config: ContainerDetailsJSON): RestartPolicy {
|
||||||
switch (config.HostConfig?.RestartPolicy?.Name) {
|
switch (config.HostConfig?.RestartPolicy?.Name) {
|
||||||
case 'always':
|
case 'always':
|
||||||
return RestartPolicy.Always;
|
return RestartPolicy.Always;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { ContainerJSON } from '../../queries/container';
|
import { ContainerDetailsJSON } from '../../queries/useContainer';
|
||||||
|
|
||||||
import { VolumeType, Values } from './types';
|
import { VolumeType, Values } from './types';
|
||||||
|
|
||||||
export function toViewModel(config: ContainerJSON): Values {
|
export function toViewModel(config: ContainerDetailsJSON): Values {
|
||||||
return Object.values(config.Mounts || {}).map((mount) => ({
|
return Object.values(config.Mounts || {}).map((mount) => ({
|
||||||
type: (mount.Type || 'volume') as VolumeType,
|
type: (mount.Type || 'volume') as VolumeType,
|
||||||
name: mount.Name || mount.Source || '',
|
name: mount.Name || mount.Source || '',
|
||||||
|
|
|
@ -31,13 +31,13 @@ import {
|
||||||
renameContainer,
|
renameContainer,
|
||||||
startContainer,
|
startContainer,
|
||||||
stopContainer,
|
stopContainer,
|
||||||
urlBuilder,
|
|
||||||
} from '../containers.service';
|
} from '../containers.service';
|
||||||
import { PortainerResponse } from '../../types';
|
import { PortainerResponse } from '../../types';
|
||||||
import { connectContainer } from '../../networks/queries/useConnectContainer';
|
import { connectContainer } from '../../networks/queries/useConnectContainerMutation';
|
||||||
import { DockerContainer } from '../types';
|
import { ContainerListViewModel } from '../types';
|
||||||
import { queryKeys } from '../queries/query-keys';
|
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 { CreateContainerRequest } from './types';
|
||||||
import { Values } from './useInitialValues';
|
import { Values } from './useInitialValues';
|
||||||
|
@ -75,7 +75,7 @@ interface CreateOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReplaceOptions extends CreateOptions {
|
interface ReplaceOptions extends CreateOptions {
|
||||||
oldContainer: DockerContainer;
|
oldContainer: ContainerListViewModel;
|
||||||
extraNetworks: Array<ExtraNetwork>;
|
extraNetworks: Array<ExtraNetwork>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ async function replace({
|
||||||
async function renameAndCreate(
|
async function renameAndCreate(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
name: string,
|
name: string,
|
||||||
oldContainer: DockerContainer,
|
oldContainer: ContainerListViewModel,
|
||||||
config: CreateContainerRequest,
|
config: CreateContainerRequest,
|
||||||
nodeName?: string
|
nodeName?: string
|
||||||
) {
|
) {
|
||||||
|
@ -234,7 +234,7 @@ async function applyContainerSettings(
|
||||||
async function createAndStart(
|
async function createAndStart(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
config: CreateContainerRequest,
|
config: CreateContainerRequest,
|
||||||
name: string,
|
name?: string,
|
||||||
nodeName?: string
|
nodeName?: string
|
||||||
) {
|
) {
|
||||||
let containerId = '';
|
let containerId = '';
|
||||||
|
@ -290,12 +290,10 @@ async function createContainer(
|
||||||
{ nodeName }: { nodeName?: string } = {}
|
{ nodeName }: { nodeName?: string } = {}
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const headers = addNodeHeader(nodeName);
|
|
||||||
|
|
||||||
const { data } = await axios.post<
|
const { data } = await axios.post<
|
||||||
PortainerResponse<{ Id: string; Warnings: Array<string> }>
|
PortainerResponse<{ Id: string; Warnings: Array<string> }>
|
||||||
>(urlBuilder(environmentId, undefined, 'create'), config, {
|
>(buildDockerProxyUrl(environmentId, 'containers', 'create'), config, {
|
||||||
headers,
|
headers: { ...withAgentTargetHeader(nodeName) },
|
||||||
params: { name },
|
params: { name },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -349,7 +347,7 @@ function connectToExtraNetworks(
|
||||||
|
|
||||||
function stopContainerIfNeeded(
|
function stopContainerIfNeeded(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
container: DockerContainer,
|
container: ContainerListViewModel,
|
||||||
nodeName?: string
|
nodeName?: string
|
||||||
) {
|
) {
|
||||||
if (container.State !== 'running' || !container.Id) {
|
if (container.State !== 'running' || !container.Id) {
|
||||||
|
|
|
@ -43,8 +43,8 @@ import { useEnvironmentRegistries } from '@/react/portainer/environments/queries
|
||||||
import { EnvVarValues } from '@@/form-components/EnvironmentVariablesFieldset';
|
import { EnvVarValues } from '@@/form-components/EnvironmentVariablesFieldset';
|
||||||
|
|
||||||
import { useNetworksForSelector } from '../components/NetworkSelector';
|
import { useNetworksForSelector } from '../components/NetworkSelector';
|
||||||
import { useContainers } from '../queries/containers';
|
import { useContainers } from '../queries/useContainers';
|
||||||
import { useContainer } from '../queries/container';
|
import { useContainer } from '../queries/useContainer';
|
||||||
|
|
||||||
export interface Values extends BaseFormValues {
|
export interface Values extends BaseFormValues {
|
||||||
commands: CommandsTabValues;
|
commands: CommandsTabValues;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { SchemaOf, object, string } from 'yup';
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
|
|
||||||
import { useAuthorizations } from '@/react/hooks/useUser';
|
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 { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
|
||||||
import { FormControl } from '@@/form-components/FormControl';
|
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