Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c1977e0aa | ||
|
|
c47fd9f9ed | ||
|
|
767d1d1970 | ||
|
|
ef81e5c0e0 | ||
|
|
234b7a3d5e | ||
|
|
af49305e64 | ||
|
|
d181d1251c | ||
|
|
5f7db66e95 | ||
|
|
17378bdef6 | ||
|
|
010542ac1e | ||
|
|
1bb253479a | ||
|
|
f0a13a2ad1 | ||
|
|
f9b28aa0a1 | ||
|
|
d26e1b6983 | ||
|
|
7b00fdd208 |
@@ -103,8 +103,26 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
||||
store.createBackupFolders()
|
||||
|
||||
options = store.setupOptions(options)
|
||||
dbPath := store.databasePath()
|
||||
|
||||
return options.BackupPath, store.copyDBFile(store.databasePath(), options.BackupPath)
|
||||
if err := store.Close(); err != nil {
|
||||
return options.BackupPath, fmt.Errorf(
|
||||
"error closing datastore before creating backup: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
if err := store.copyDBFile(dbPath, options.BackupPath); err != nil {
|
||||
return options.BackupPath, err
|
||||
}
|
||||
|
||||
if _, err := store.Open(); err != nil {
|
||||
return options.BackupPath, fmt.Errorf(
|
||||
"error opening datastore after creating backup: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return options.BackupPath, nil
|
||||
}
|
||||
|
||||
// RestoreWithOptions previously saved backup for the current Edition with options
|
||||
|
||||
@@ -910,7 +910,7 @@
|
||||
],
|
||||
"version": {
|
||||
"DB_UPDATING": "false",
|
||||
"DB_VERSION": "50",
|
||||
"DB_VERSION": "52",
|
||||
"INSTANCE_ID": "null"
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
|
||||
}
|
||||
|
||||
filePaths := stackutils.GetStackFilePaths(stack)
|
||||
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths)
|
||||
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths, "")
|
||||
return errors.Wrap(err, "failed to remove a stack")
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021
|
||||
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a
|
||||
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108
|
||||
github.com/portainer/libhttp v0.0.0-20211208103139-07a5f798eb3f
|
||||
|
||||
@@ -807,10 +807,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220526210722-e1574867298e h1:gW1Ooaj7RZ9YkwHxesnNEyOB5nUD71FlZ7cdb5h63vw=
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220526210722-e1574867298e/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410 h1:LjxLd8UGR8ae73ov/vLrt/0jedj/nh98XnONkr8DJj8=
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021 h1:GFTn2e5AyIoBuK6hXbdVNkuV2m450DQnYmgQDZRU3x8=
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021/go.mod h1:WxDlJWZxCnicdLCPnLNEv7/gRhjeIVuCGmsv+iOPH3c=
|
||||
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a h1:qY8TbocN75n5PDl16o0uVr5MevtM5IhdwSelXEd4nFM=
|
||||
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a/go.mod h1:n54EEIq+MM0NNtqLeCby8ljL+l275VpolXO0ibHegLE=
|
||||
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108 h1:5e8KAnDa2G3cEHK7aV/ue8lOaoQwBZUzoALslwWkR04=
|
||||
|
||||
@@ -80,7 +80,7 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
// @version 2.14.0
|
||||
// @version 2.14.2
|
||||
// @description.markdown api-description.md
|
||||
// @termsOfService
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package helm
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/portainer/libhelm"
|
||||
@@ -108,7 +107,7 @@ func (handler *Handler) getHelmClusterAccess(r *http.Request) (*options.Kubernet
|
||||
|
||||
hostURL := "localhost"
|
||||
if !sslSettings.SelfSigned {
|
||||
hostURL = strings.Split(r.Host, ":")[0]
|
||||
hostURL = r.Host
|
||||
}
|
||||
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
@@ -145,8 +144,7 @@ func (handler *Handler) buildConfig(r *http.Request, tokenData *portainer.TokenD
|
||||
}
|
||||
|
||||
func (handler *Handler) buildCluster(r *http.Request, endpoint portainer.Endpoint) clientV1.NamedCluster {
|
||||
hostURL := strings.Split(r.Host, ":")[0]
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(r.Host, endpoint.ID)
|
||||
return clientV1.NamedCluster{
|
||||
Name: buildClusterName(endpoint.Name),
|
||||
Cluster: clientV1.Cluster{
|
||||
|
||||
@@ -95,8 +95,10 @@ func generatePublicSettings(appSettings *portainer.Settings) *publicSettingsResp
|
||||
}
|
||||
}
|
||||
//if LDAP authentication is on, compose the related fields from application settings
|
||||
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP {
|
||||
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings) > 0
|
||||
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP && appSettings.LDAPSettings.GroupSearchSettings != nil {
|
||||
if len(appSettings.LDAPSettings.GroupSearchSettings) > 0 {
|
||||
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings[0].GroupBaseDN) > 0
|
||||
}
|
||||
}
|
||||
return publicSettings
|
||||
}
|
||||
|
||||
@@ -81,11 +81,11 @@ func FilterRegistries(registries []portainer.Registry, user *portainer.User, tea
|
||||
}
|
||||
|
||||
// FilterEndpoints filters environments(endpoints) based on user role and team memberships.
|
||||
// Non administrator and non-team-leader only have access to authorized environments(endpoints) (can be inherited via endpoint groups).
|
||||
// Non administrator only have access to authorized environments(endpoints) (can be inherited via endpoint groups).
|
||||
func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.Endpoint {
|
||||
filteredEndpoints := endpoints
|
||||
|
||||
if !context.IsAdmin && !context.IsTeamLeader {
|
||||
if !context.IsAdmin {
|
||||
filteredEndpoints = make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
@@ -101,11 +101,11 @@ func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.Endpoint
|
||||
}
|
||||
|
||||
// FilterEndpointGroups filters environment(endpoint) groups based on user role and team memberships.
|
||||
// Non administrator users and Non-team-leaders only have access to authorized environment(endpoint) groups.
|
||||
// Non administrator users only have access to authorized environment(endpoint) groups.
|
||||
func FilterEndpointGroups(endpointGroups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.EndpointGroup {
|
||||
filteredEndpointGroups := endpointGroups
|
||||
|
||||
if !context.IsAdmin && !context.IsTeamLeader {
|
||||
if !context.IsAdmin {
|
||||
filteredEndpointGroups = make([]portainer.EndpointGroup, 0)
|
||||
|
||||
for _, group := range endpointGroups {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// KubeClusterAccessService represents a service that is responsible for centralizing kube cluster access data
|
||||
@@ -94,11 +95,20 @@ func (service *kubeClusterAccessService) IsSecure() bool {
|
||||
// - pass down params to binaries
|
||||
func (service *kubeClusterAccessService) GetData(hostURL string, endpointID portainer.EndpointID) kubernetesClusterAccessData {
|
||||
baseURL := service.baseURL
|
||||
|
||||
// When the api call is internal, the baseURL should not be used.
|
||||
if hostURL == "localhost" {
|
||||
hostURL = hostURL + service.httpsBindAddr
|
||||
baseURL = "/"
|
||||
}
|
||||
|
||||
if baseURL != "/" {
|
||||
baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/"))
|
||||
}
|
||||
|
||||
clusterURL := hostURL + service.httpsBindAddr + baseURL
|
||||
logrus.Infof("[kubeconfig] [hostURL: %s, httpsBindAddr: %s, baseURL: %s]", hostURL, service.httpsBindAddr, baseURL)
|
||||
|
||||
clusterURL := hostURL + baseURL
|
||||
|
||||
clusterServerURL := fmt.Sprintf("https://%sapi/endpoints/%d/kubernetes", clusterURL, endpointID)
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestKubeClusterAccessService_GetKubeConfigInternal(t *testing.T) {
|
||||
clusterAccessDetails := kcs.GetData("mysite.com", 1)
|
||||
|
||||
wantClusterAccessDetails := kubernetesClusterAccessData{
|
||||
ClusterServerURL: "https://mysite.com:9443/api/endpoints/1/kubernetes",
|
||||
ClusterServerURL: "https://mysite.com/api/endpoints/1/kubernetes",
|
||||
CertificateAuthorityFile: "",
|
||||
CertificateAuthorityData: "",
|
||||
}
|
||||
|
||||
@@ -1385,9 +1385,9 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "2.14.0"
|
||||
APIVersion = "2.14.2"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 50
|
||||
DBVersion = 52
|
||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||
ComposeSyntaxMaxVersion = "3.9"
|
||||
// AssetsServerURL represents the URL of the Portainer asset server
|
||||
|
||||
@@ -5,7 +5,7 @@ angular.module('portainer.kubernetes').factory('HelmFactory', HelmFactory);
|
||||
/* @ngInject */
|
||||
function HelmFactory($resource, API_ENDPOINT_ENDPOINTS) {
|
||||
const helmUrl = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/helm';
|
||||
const templatesUrl = '/api/templates/helm';
|
||||
const templatesUrl = 'api/templates/helm';
|
||||
|
||||
return $resource(
|
||||
helmUrl,
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
></custom-template-selector>
|
||||
|
||||
<custom-templates-variables-field
|
||||
ng-if="$ctrl.isTemplateVariablesEnabled && ctrl.state.template"
|
||||
ng-if="ctrl.isTemplateVariablesEnabled && ctrl.state.template"
|
||||
definitions="ctrl.state.template.Variables"
|
||||
value="ctrl.formValues.Variables"
|
||||
on-change="(ctrl.onChangeTemplateVariables)"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
loaded="$ctrl.loaded"
|
||||
page-type="$ctrl.pageType"
|
||||
table-type="available"
|
||||
retrieve-page="$ctrl.getPaginatedEndpoints"
|
||||
retrieve-page="$ctrl.getAvailableEndpoints"
|
||||
dataset="$ctrl.endpoints.available"
|
||||
entry-click="$ctrl.associateEndpoint"
|
||||
pagination-state="$ctrl.state.available"
|
||||
@@ -34,7 +34,7 @@
|
||||
loaded="$ctrl.loaded"
|
||||
page-type="$ctrl.pageType"
|
||||
table-type="associated"
|
||||
retrieve-page="$ctrl.getPaginatedEndpoints"
|
||||
retrieve-page="$ctrl.getAssociatedEndpoints"
|
||||
dataset="$ctrl.endpoints.associated"
|
||||
entry-click="$ctrl.dissociateEndpoint"
|
||||
pagination-state="$ctrl.state.associated"
|
||||
|
||||
@@ -27,7 +27,7 @@ class AssoicatedEndpointsSelectorController {
|
||||
available: null,
|
||||
};
|
||||
|
||||
this.getEndpoints = this.getEndpoints.bind(this);
|
||||
this.getAvailableEndpoints = this.getAvailableEndpoints.bind(this);
|
||||
this.getEndpointsAsync = this.getEndpointsAsync.bind(this);
|
||||
this.getAssociatedEndpoints = this.getAssociatedEndpoints.bind(this);
|
||||
this.getAssociatedEndpointsAsync = this.getAssociatedEndpointsAsync.bind(this);
|
||||
@@ -47,10 +47,10 @@ class AssoicatedEndpointsSelectorController {
|
||||
|
||||
loadData() {
|
||||
this.getAssociatedEndpoints();
|
||||
this.getEndpoints();
|
||||
this.getAvailableEndpoints();
|
||||
}
|
||||
|
||||
getEndpoints() {
|
||||
getAvailableEndpoints() {
|
||||
return this.$async(this.getEndpointsAsync);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,22 +22,3 @@ export function withFileSize(fileValidation: FileSchema, maxSize: number) {
|
||||
return file.size <= maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
export function withFileType(
|
||||
fileValidation: FileSchema,
|
||||
fileTypes: File['type'][]
|
||||
) {
|
||||
return fileValidation.test(
|
||||
'file-type',
|
||||
'Selected file has unsupported format.',
|
||||
validateFileType
|
||||
);
|
||||
|
||||
function validateFileType(file?: File) {
|
||||
if (!file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileTypes.includes(file.type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,7 @@
|
||||
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
<input type="password" class="form-control" ng-model="formValues.confirmPassword" id="confirm_password" />
|
||||
<span class="input-group-addon"
|
||||
><i
|
||||
ng-class="
|
||||
{ true: 'fa fa-check green-icon', false: 'fa fa-times red-icon' }[
|
||||
form.new_password.$viewValue !== '' && form.new_password.$viewValue === formValues.confirmPassword
|
||||
]
|
||||
"
|
||||
aria-hidden="true"
|
||||
></i
|
||||
><i ng-class="{ true: 'fa fa-check green-icon', false: 'fa fa-times red-icon' }[formValues.newPassword === formValues.confirmPassword]" aria-hidden="true"></i
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,7 +60,7 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="isDemoUser || (AuthenticationMethod !== 1 && !initialUser) || !formValues.currentPassword || !formValues.newPassword || !formValues.confirmPassword || form.$invalid || form.new_password.$viewValue !== formValues.confirmPassword"
|
||||
ng-disabled="isDemoUser || (AuthenticationMethod !== 1 && !isInitialAdmin) || !formValues.currentPassword || !formValues.newPassword || form.$invalid || formValues.newPassword !== formValues.confirmPassword"
|
||||
ng-click="updatePassword()"
|
||||
>
|
||||
Update password
|
||||
@@ -75,11 +68,11 @@
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-click="skipPasswordChange()" ng-if="forceChangePassword && timesPasswordChangeSkipped < 2"
|
||||
>Remind me later</button
|
||||
>
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 2 && !initialUser">
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 2 && !isInitialAdmin">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
You cannot change your password when using LDAP authentication.
|
||||
</span>
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 3 && !initialUser">
|
||||
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 3 && !isInitialAdmin">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
You cannot change your password when using OAuth authentication.
|
||||
</span>
|
||||
|
||||
@@ -118,6 +118,7 @@ angular.module('portainer.app').controller('AccountController', [
|
||||
$scope.userID = userDetails.ID;
|
||||
$scope.userRole = Authentication.getUserDetails().role;
|
||||
$scope.forceChangePassword = userDetails.forceChangePassword;
|
||||
$scope.isInitialAdmin = userDetails.ID === 1;
|
||||
|
||||
if (state.application.demoEnvironment.enabled) {
|
||||
$scope.isDemoUser = state.application.demoEnvironment.users.includes($scope.userID);
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
label="'Use custom logo'"
|
||||
value="formValues.customLogo"
|
||||
checked="formValues.customLogo"
|
||||
name="'toggle_logo'"
|
||||
disabled="state.isDemo"
|
||||
on-change="(onToggleCustomLogo)"
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
label="'Allow the collection of anonymous statistics'"
|
||||
value="formValues.enableTelemetry"
|
||||
checked="formValues.enableTelemetry"
|
||||
name="'toggle_enableTelemetry'"
|
||||
on-change="(onToggleEnableTelemetry)"
|
||||
disabled="state.isDemo"
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
children-paths="['portainer.users.user' ,'portainer.teams' ,'portainer.teams.team' ,'portainer.roles' ,'portainer.roles.role' ,'portainer.roles.new']"
|
||||
>
|
||||
<sidebar-menu-item path="portainer.teams" class-name="sidebar-sublist" data-cy="portainerSidebar-teams" title="Teams">Teams</sidebar-menu-item>
|
||||
<sidebar-menu-item path="portainer.roles" class-name="sidebar-sublist" data-cy="portainerSidebar-roles" title="Roles">Roles</sidebar-menu-item>
|
||||
<sidebar-menu-item ng-if="isAdmin" path="portainer.roles" class-name="sidebar-sublist" data-cy="portainerSidebar-roles" title="Roles">Roles</sidebar-menu-item>
|
||||
</sidebar-menu>
|
||||
|
||||
<div ng-if="isAdmin">
|
||||
|
||||
@@ -5,6 +5,8 @@ import { AccessControlFormData } from '@/portainer/components/accessControlForm/
|
||||
import { STACK_NAME_VALIDATION_REGEX } from '@/constants';
|
||||
import { RepositoryMechanismTypes } from '@/kubernetes/models/deploy';
|
||||
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||
import { isBE } from '@/portainer/feature-flags/feature-flags.service';
|
||||
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
|
||||
|
||||
angular
|
||||
.module('portainer.app')
|
||||
@@ -31,6 +33,8 @@ angular
|
||||
endpoint
|
||||
) {
|
||||
$scope.onChangeTemplateId = onChangeTemplateId;
|
||||
$scope.onChangeTemplateVariables = onChangeTemplateVariables;
|
||||
$scope.isTemplateVariablesEnabled = isBE;
|
||||
$scope.buildAnalyticsProperties = buildAnalyticsProperties;
|
||||
$scope.stackWebhookFeature = FeatureId.STACK_WEBHOOK;
|
||||
$scope.STACK_NAME_VALIDATION_REGEX = STACK_NAME_VALIDATION_REGEX;
|
||||
@@ -53,6 +57,7 @@ angular
|
||||
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
|
||||
RepositoryFetchInterval: '5m',
|
||||
RepositoryWebhookURL: WebhookHelper.returnStackWebhookUrl(uuidv4()),
|
||||
Variables: {},
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
@@ -265,11 +270,12 @@ angular
|
||||
});
|
||||
};
|
||||
|
||||
$scope.onChangeFileContent = function onChangeFileContent(value) {
|
||||
$scope.onChangeFileContent = onChangeFileContent;
|
||||
function onChangeFileContent(value) {
|
||||
$scope.formValues.StackFileContent = value;
|
||||
$scope.state.editorYamlValidationError = StackHelper.validateYAML($scope.formValues.StackFileContent, $scope.containerNames);
|
||||
$scope.state.isEditorDirty = true;
|
||||
};
|
||||
}
|
||||
|
||||
async function onFileLoadAsync(event) {
|
||||
$scope.state.uploadYamlValidationError = StackHelper.validateYAML(event.target.result, $scope.containerNames);
|
||||
@@ -292,18 +298,38 @@ angular
|
||||
|
||||
function onChangeTemplateId(templateId, template) {
|
||||
return $async(async () => {
|
||||
if (!template || ($scope.state.selectedTemplateId === templateId && $scope.state.selectedTemplate === template)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$scope.state.selectedTemplateId = templateId;
|
||||
$scope.state.selectedTemplate = template;
|
||||
|
||||
const fileContent = await CustomTemplateService.customTemplateFile(templateId);
|
||||
$scope.onChangeFileContent(fileContent);
|
||||
$scope.state.templateContent = fileContent;
|
||||
onChangeFileContent(fileContent);
|
||||
|
||||
if (template.Variables && template.Variables.length > 0) {
|
||||
const variables = Object.fromEntries(template.Variables.map((variable) => [variable.name, '']));
|
||||
onChangeTemplateVariables(variables);
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Custom Template file');
|
||||
Notifications.error('Failure', err, 'Unable to retrieve Custom Template file');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onChangeTemplateVariables(value) {
|
||||
onChangeFormValues({ Variables: value });
|
||||
|
||||
if (!$scope.isTemplateVariablesEnabled) {
|
||||
return;
|
||||
}
|
||||
const rendered = renderTemplate($scope.state.templateContent, $scope.formValues.Variables, $scope.state.selectedTemplate.Variables);
|
||||
onChangeFormValues({ StackFileContent: rendered });
|
||||
}
|
||||
|
||||
async function initView() {
|
||||
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||
$scope.state.StackType = 2;
|
||||
@@ -328,8 +354,11 @@ angular
|
||||
|
||||
initView();
|
||||
|
||||
function onChangeFormValues(newValues) {
|
||||
$scope.formValues = newValues;
|
||||
function onChangeFormValues(values) {
|
||||
$scope.formValues = {
|
||||
...$scope.formValues,
|
||||
...values,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -131,13 +131,21 @@
|
||||
path-placeholder="docker-compose.yml"
|
||||
></git-form>
|
||||
|
||||
<custom-template-selector
|
||||
ng-show="state.Method === 'template'"
|
||||
new-template-path="docker.templates.custom.new"
|
||||
stack-type="state.StackType"
|
||||
on-change="(onChangeTemplateId)"
|
||||
value="state.selectedTemplateId"
|
||||
></custom-template-selector>
|
||||
<div ng-show="state.Method === 'template'">
|
||||
<custom-template-selector
|
||||
new-template-path="docker.templates.custom.new"
|
||||
stack-type="state.StackType"
|
||||
on-change="(onChangeTemplateId)"
|
||||
value="state.selectedTemplateId"
|
||||
></custom-template-selector>
|
||||
|
||||
<custom-templates-variables-field
|
||||
ng-if="isTemplateVariablesEnabled && state.selectedTemplate"
|
||||
definitions="state.selectedTemplate.Variables"
|
||||
value="formValues.Variables"
|
||||
on-change="(onChangeTemplateVariables)"
|
||||
></custom-templates-variables-field>
|
||||
</div>
|
||||
|
||||
<web-editor-form
|
||||
ng-if="state.Method === 'editor' || (state.Method === 'template' && state.selectedTemplateId)"
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="state.actionInProgress || !state.validUsername || formValues.Username === '' || !formValues.Password || !formValues.ConfirmPassword || (AuthenticationMethod === 1 && form.$invalid) || (AuthenticationMethod === 1 && form.password.$viewValue !== formValues.ConfirmPassword)"
|
||||
ng-disabled="state.actionInProgress || !state.validUsername || formValues.Username === '' || (AuthenticationMethod === 1 && (!formValues.Password || form.$invalid || formValues.Password !== formValues.ConfirmPassword))"
|
||||
ng-click="addUser()"
|
||||
button-spinner="state.actionInProgress"
|
||||
data-cy="user-createUserButton"
|
||||
|
||||
@@ -38,12 +38,15 @@ export function intersectVariables(
|
||||
) {
|
||||
const oldVariablesWithLabel = oldVariables.filter((v) => !!v.label);
|
||||
|
||||
return [
|
||||
...oldVariablesWithLabel,
|
||||
...newVariables.filter(
|
||||
(v) => !oldVariablesWithLabel.find(({ name }) => name === v.name)
|
||||
),
|
||||
];
|
||||
return _.uniqBy(
|
||||
[
|
||||
...oldVariablesWithLabel,
|
||||
...newVariables.filter(
|
||||
(v) => !oldVariablesWithLabel.find(({ name }) => name === v.name)
|
||||
),
|
||||
],
|
||||
'name'
|
||||
);
|
||||
}
|
||||
|
||||
export function renderTemplate(
|
||||
@@ -68,5 +71,5 @@ export function renderTemplate(
|
||||
)
|
||||
);
|
||||
|
||||
return Mustache.render(template, state);
|
||||
return Mustache.render(template, state, undefined, { escape: (t) => t });
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@ import { useFormikContext } from 'formik';
|
||||
import { FileUploadField } from '@/portainer/components/form-components/FileUpload';
|
||||
import { SwitchField } from '@/portainer/components/form-components/SwitchField';
|
||||
import { FormControl } from '@/portainer/components/form-components/FormControl';
|
||||
import {
|
||||
file,
|
||||
withFileSize,
|
||||
withFileType,
|
||||
} from '@/portainer/helpers/yup-file-validation';
|
||||
import { file, withFileSize } from '@/portainer/helpers/yup-file-validation';
|
||||
|
||||
import { FormValues } from './types';
|
||||
|
||||
@@ -82,16 +78,9 @@ export function TLSFieldset() {
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 5_242_880; // 5MB
|
||||
const ALLOWED_FILE_TYPES = [
|
||||
'application/x-x509-ca-cert',
|
||||
'application/x-pem-file',
|
||||
];
|
||||
|
||||
function certValidation() {
|
||||
return withFileType(
|
||||
withFileSize(file(), MAX_FILE_SIZE),
|
||||
ALLOWED_FILE_TYPES
|
||||
).when(['tls', 'skipVerify'], {
|
||||
return withFileSize(file(), MAX_FILE_SIZE).when(['tls', 'skipVerify'], {
|
||||
is: (tls: boolean, skipVerify: boolean) => tls && !skipVerify,
|
||||
then: (schema) => schema.required('File is required'),
|
||||
});
|
||||
|
||||
@@ -21,10 +21,10 @@ fi
|
||||
|
||||
|
||||
if [[ "$PLATFORM" == "windows" ]]; then
|
||||
wget -O "dist/docker-compose.plugin.exe" "https://github.com/docker/compose/releases/download/$COMPOSE_VERSION/docker-compose-windows-${ARCH}.exe"
|
||||
chmod +x "dist/docker-compose.plugin.exe"
|
||||
wget -O "dist/docker-compose.exe" "https://github.com/docker/compose/releases/download/$COMPOSE_VERSION/docker-compose-windows-${ARCH}.exe"
|
||||
chmod +x "dist/docker-compose.exe"
|
||||
else
|
||||
wget -O "dist/docker-compose.plugin" "https://github.com/docker/compose/releases/download/$COMPOSE_VERSION/docker-compose-${PLATFORM}-${ARCH}"
|
||||
chmod +x "dist/docker-compose.plugin"
|
||||
wget -O "dist/docker-compose" "https://github.com/docker/compose/releases/download/$COMPOSE_VERSION/docker-compose-${PLATFORM}-${ARCH}"
|
||||
chmod +x "dist/docker-compose"
|
||||
fi
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "2.14.0",
|
||||
"version": "2.14.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
||||
Reference in New Issue
Block a user