Revert multiple commits to set it back to tag 2.19 (#10946)

* Revert "docs(dashboard): update link for swarm node [EE-6318] (#10770)"

This reverts commit 30356d2c15.

* Revert "docs(api): default to pascal case for property name [EE-6471] (#10861)"

This reverts commit 392819576c.

* Revert "fix(edge/jobs): clear logs [EE-5923] (#10819)"

This reverts commit e01386f63d.

* Revert "disable html5 validation (#10843)"

This reverts commit 4b0f08e92a.

* Revert "fix(edgestack): allow to set retry deployment  toggle EE-6167 (#10676) (#10805)"

This reverts commit eee632b22d.

* Revert "fix(stack): edit git stack validation (#10812)"

This reverts commit d3b150b29c.

* Revert "Revert "Revert "fix(rollback): reversed rollback code from 2.19.4 [EE-6435] (#10811)" (#10832)"

This reverts commit 32e05bb705.

* Revert "fix(setting/ssl): cert files are optional to upload [EE-6139] (#10779)"

This reverts commit 7408668dbb.

* Revert "fix(endpoint): delete the endpoint proxy when updating an endpoint address [EE-5577] (#10824)"

This reverts commit 4b5ea01456.

* Revert "fix(swagger): custom template create docs EE-6428 (#10806)"

This reverts commit 0d55cb3e08.

* Revert "fix(images): sort by tags [EE-6410] (#10755)"

This reverts commit 7f51c727a0.

* Revert "fix(stacks): sort by date [EE-5766] (#10758)"

This reverts commit 57b80cd9ac.

* Revert "fix(UI): remember backup settings tab [EE-6347] (#10764)"

This reverts commit c20452492d.

* Revert "fix(backup ui): minor typo on backup page EE-6348 (#10717)"

This reverts commit d58046eb68.

* Revert "fix(app): shift external to the top [EE-6392] (#10753) (#10802)"

This reverts commit 4795e85d18.

* Revert "fix(app): update sliders when limits are known [EE-5933] (#10769) (#10801)"

This reverts commit d090b0043a.

* Revert "fix(gitops): correct commit hash link [EE-6346] (#10800)"

This reverts commit 0e59cf76ec.

* Revert "fix toast error (#10804)"

This reverts commit 9978b88ed4.

* Revert "fix(kube): configmaps and secrets from envFrom in the app detail screen [EE-6282] (#10741) (#10798)"

This reverts commit c1a01558d0.

* Revert "fix(stacks): allow editing custom templates before stack deployment EE-6380 (#10713)"

This reverts commit f0aa0554f8.

* Reapply "fix(rollback): reversed rollback code from 2.19.4 [EE-6435] (#10811)

This reverts commit c58fa274e7.
pull/11470/head
Prabhat Khera 2024-01-25 12:50:56 +13:00 committed by GitHub
parent d2eb53eb18
commit 6f5d9c357f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 213 additions and 331 deletions

View File

@ -114,7 +114,7 @@ dev-extension: build-server build-client ## Run the extension in development mod
##@ Docs
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
docs-build: init-dist ## Build docs
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 -p pascalcase --markdownFiles ./
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 --markdownFiles ./
docs-validate: docs-build ## Validate docs
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml

View File

@ -149,7 +149,7 @@ func isValidNote(note string) bool {
// @success 200 {object} portainer.CustomTemplate
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /custom_templates/create/string [post]
// @router /custom_templates/string [post]
func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*portainer.CustomTemplate, error) {
var payload customTemplateFromFileContentPayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
@ -263,7 +263,7 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request)
// @success 200 {object} portainer.CustomTemplate
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /custom_templates/create/repository [post]
// @router /custom_templates/repository [post]
func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) (*portainer.CustomTemplate, error) {
var payload customTemplateFromGitRepositoryPayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
@ -443,7 +443,7 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er
// @success 200 {object} portainer.CustomTemplate
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /custom_templates/create/file [post]
// @router /custom_templates/file [post]
func (handler *Handler) createCustomTemplateFromFileUpload(r *http.Request) (*portainer.CustomTemplate, error) {
payload := &customTemplateFromFileUploadPayload{}
err := payload.Validate(r)

View File

@ -110,6 +110,11 @@ func (handler *Handler) clearEdgeJobTaskLogs(tx dataservices.DataStoreTx, edgeJo
return httperror.InternalServerError("Unable to persist Edge job changes in the database", err)
}
err = handler.FileService.ClearEdgeJobTaskLogs(strconv.Itoa(int(edgeJobID)), strconv.Itoa(int(endpointID)))
if err != nil {
return httperror.InternalServerError("Unable to clear log file from disk", err)
}
endpoint, err := tx.Endpoint().Endpoint(endpointID)
if err != nil {
return httperror.NotFound("Unable to retrieve environment from the database", err)

View File

@ -89,8 +89,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err)
}
updateEndpointProxy := shouldReloadTLSConfiguration(endpoint, &payload)
if payload.Name != nil {
name := *payload.Name
isUnique, err := handler.isNameUnique(name, endpoint.ID)
@ -106,9 +104,8 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
}
if payload.URL != nil && *payload.URL != endpoint.URL {
if payload.URL != nil {
endpoint.URL = *payload.URL
updateEndpointProxy = true
}
if payload.PublicURL != nil {
@ -182,8 +179,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
}
if endpoint.Type == portainer.AzureEnvironment {
updateEndpointProxy = true
credentials := endpoint.AzureCredentials
if payload.AzureApplicationID != nil {
credentials.ApplicationID = *payload.AzureApplicationID
@ -252,7 +247,10 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
}
}
if updateEndpointProxy {
if (payload.URL != nil && *payload.URL != endpoint.URL) ||
(payload.TLS != nil && endpoint.TLSConfig.TLS != *payload.TLS) ||
endpoint.Type == portainer.AzureEnvironment ||
shouldReloadTLSConfiguration(endpoint, &payload) {
handler.ProxyManager.DeleteEndpointProxy(endpoint.ID)
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
if err != nil {
@ -293,12 +291,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
}
func shouldReloadTLSConfiguration(endpoint *portainer.Endpoint, payload *endpointUpdatePayload) bool {
// If we change anything in the tls config then we need to reload the proxy
if payload.TLS != nil && endpoint.TLSConfig.TLS != *payload.TLS {
return true
}
// When updating Docker API environment, as long as TLS is true and TLSSkipVerify is false,
// we assume that new TLS files have been uploaded and we need to reload the TLS configuration.
if endpoint.Type != portainer.DockerEnvironment ||

View File

@ -34,7 +34,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
endpoint.Status = status;
if (status === EnvironmentStatus.Down) {
throw new Error(`The environment named ${endpoint.Name} is unreachable.`);
throw new Error('Environment is unreachable.');
}
await StateManager.updateEndpointState(endpoint);

View File

@ -18,7 +18,7 @@
<p class="text-muted" ng-if="applicationState.endpoint.mode.role === 'MANAGER'">
<pr-icon icon="'alert-circle'" mode="'primary'"></pr-icon>
Portainer is connected to a node that is part of a Swarm cluster. Some resources located on other nodes in the cluster might not be available for management, have a look at
<a href="https://docs.portainer.io/admin/environments/add/swarm/agent" target="_blank">our agent setup</a> for more details.
<a href="https://docs.portainer.io/start/install/agent/swarm/linux" target="_blank">our agent setup</a> for more details.
</p>
<p class="text-muted" ng-if="applicationState.endpoint.mode.role === 'WORKER'">
<pr-icon icon="'alert-circle'" mode="'primary'"></pr-icon>

View File

@ -101,7 +101,6 @@ export class EditEdgeStackViewController {
edgeGroups: values.edgeGroups,
deploymentType: values.deploymentType,
updateVersion,
retryDeploy: values.retryDeploy,
webhook: values.webhookEnabled ? this.stack.Webhook || createWebhookId() : '',
envVars: values.envVars,
});

View File

@ -53,7 +53,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
try {
await getSelfSubjectAccessReview(endpoint.Id, 'default');
} catch (e) {
throw new Error(`The environment named ${endpoint.Name} is unreachable.`);
throw new Error('Environment is unreachable.');
}
} catch (e) {
let params = {};

View File

@ -59,94 +59,7 @@
<div class="col-xs-12">
<rd-widget>
<rd-widget-body>
<form class="form-horizontal mt-4" name="kubernetesApplicationCreationForm" autocomplete="off" novalidate>
<div ng-if="ctrl.isExternalApplication()">
<div class="col-sm-12 form-section-title" ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"> Namespace </div>
<!-- #region NAMESPACE -->
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
<label for="resource-pool-selector" class="col-sm-1 control-label text-left">Namespace</label>
<div class="col-sm-11">
<select
class="form-control"
id="resource-pool-selector"
ng-model="ctrl.formValues.ResourcePool"
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
ng-change="ctrl.onResourcePoolSelectionChange()"
ng-disabled="ctrl.state.isEdit"
data-cy="k8sAppCreate-nsSelect"
></select>
</div>
</div>
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
<div class="col-sm-12 small text-danger">
<pr-icon icon="'alert-circle'" mode="'danger'"></pr-icon>
This namespace has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the
namespace.
</div>
</div>
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
<div class="col-sm-12 small text-muted">
<pr-icon icon="'alert-circle'" mode="'warning'"></pr-icon>
You do not have access to any namespace. Contact your administrator to get access to a namespace.
</div>
</div>
<!-- kubernetes services options -->
<div ng-if="ctrl.formValues.ResourcePool">
<kube-services-form
on-change="(ctrl.onServicesChange)"
values="ctrl.formValues.Services"
app-name="ctrl.formValues.Name"
selector="ctrl.formValues.Selector"
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services, ingressPaths: ctrl.ingressPaths, originalIngressPaths: ctrl.originalIngressPaths}"
is-edit-mode="ctrl.state.isEdit"
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
></kube-services-form>
</div>
<!-- kubernetes services options -->
<kubernetes-summary-view form-values="ctrl.formValues" old-form-values="ctrl.savedFormValues"></kubernetes-summary-view>
<!-- #region ACTIONS -->
<div class="col-sm-12 form-section-title !mt-6" ng-if="ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.GIT" ng-hide="ctrl.stack.IsComposeFormat"> Actions </div>
<div class="form-group" ng-hide="ctrl.stack.IsComposeFormat">
<div class="col-sm-12">
<button
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
type="button"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.imageValidityIsValid() || ctrl.hasPortErrors() || !ctrl.formValues.ResourcePool"
ng-click="ctrl.deployApplication()"
button-spinner="ctrl.state.actionInProgress"
data-cy="k8sAppCreate-deployButton"
>
<span ng-show="!ctrl.state.isEdit && !ctrl.state.actionInProgress">Deploy application</span>
<span ng-show="!ctrl.state.isEdit && ctrl.state.actionInProgress">Deployment in progress...</span>
<span ng-show="ctrl.state.isEdit && !ctrl.state.actionInProgress">Update application</span>
<span ng-show="ctrl.state.isEdit && ctrl.state.actionInProgress">Update in progress...</span>
</button>
<button
ng-if="ctrl.state.isEdit && !ctrl.state.actionInProgress && ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
type="button"
class="btn btn-sm btn-default"
ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })"
data-cy="k8sAppCreate-appCancelButton"
>
Cancel
</button>
<!-- #Web editor buttons -->
<button
class="btn btn-sm btn-primary"
ng-click="ctrl.updateApplicationViaWebEditor()"
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.CONTENT || ctrl.state.updateWebEditorInProgress"
ng-disabled="!kubernetesApplicationCreationForm.$valid || !ctrl.state.isEditorDirty || ctrl.state.updateWebEditorInProgress"
style="margin-top: 7px; margin-left: 0"
button-spinner="ctrl.state.updateWebEditorInProgress"
>
<span ng-show="!ctrl.state.updateWebEditorInProgress">Update the application</span>
<span ng-show="ctrl.state.updateWebEditorInProgress">Update in progress...</span>
</button>
</div>
</div>
<!-- #endregion -->
</div>
<form class="form-horizontal mt-4" name="kubernetesApplicationCreationForm" autocomplete="off">
<div ng-if="!ctrl.isExternalApplication()">
<git-form-info-panel
ng-if="ctrl.state.appType == ctrl.KubernetesDeploymentTypes.GIT"
@ -1402,54 +1315,105 @@
></kube-services-form>
</div>
<!-- kubernetes services options -->
<!-- summary -->
<kubernetes-summary-view
ng-if="!(!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.state.pullImageValidity)"
form-values="ctrl.formValues"
old-form-values="ctrl.savedFormValues"
></kubernetes-summary-view>
</div>
<!-- #region ACTIONS -->
<div class="col-sm-12 form-section-title !mt-6" ng-if="ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.GIT" ng-hide="ctrl.stack.IsComposeFormat"> Actions </div>
<div class="form-group" ng-hide="ctrl.stack.IsComposeFormat">
<div class="col-sm-12">
<button
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
type="button"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.imageValidityIsValid() || ctrl.hasPortErrors() || !ctrl.formValues.ResourcePool"
ng-click="ctrl.deployApplication()"
button-spinner="ctrl.state.actionInProgress"
data-cy="k8sAppCreate-deployButton"
>
<span ng-show="!ctrl.state.isEdit && !ctrl.state.actionInProgress">Deploy application</span>
<span ng-show="!ctrl.state.isEdit && ctrl.state.actionInProgress">Deployment in progress...</span>
<span ng-show="ctrl.state.isEdit && !ctrl.state.actionInProgress">Update application</span>
<span ng-show="ctrl.state.isEdit && ctrl.state.actionInProgress">Update in progress...</span>
</button>
<button
ng-if="ctrl.state.isEdit && !ctrl.state.actionInProgress && ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
type="button"
class="btn btn-sm btn-default"
ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })"
data-cy="k8sAppCreate-appCancelButton"
>
Cancel
</button>
<!-- #Web editor buttons -->
<button
class="btn btn-sm btn-primary"
ng-click="ctrl.updateApplicationViaWebEditor()"
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.CONTENT || ctrl.state.updateWebEditorInProgress"
ng-disabled="!kubernetesApplicationCreationForm.$valid || !ctrl.state.isEditorDirty || ctrl.state.updateWebEditorInProgress"
style="margin-top: 7px; margin-left: 0"
button-spinner="ctrl.state.updateWebEditorInProgress"
>
<span ng-show="!ctrl.state.updateWebEditorInProgress">Update the application</span>
<span ng-show="ctrl.state.updateWebEditorInProgress">Update in progress...</span>
</button>
</div>
<div ng-if="ctrl.isExternalApplication()">
<div class="col-sm-12 form-section-title" ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"> Namespace </div>
<!-- #region NAMESPACE -->
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
<label for="resource-pool-selector" class="col-sm-1 control-label text-left">Namespace</label>
<div class="col-sm-11">
<select
class="form-control"
id="resource-pool-selector"
ng-model="ctrl.formValues.ResourcePool"
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
ng-change="ctrl.onResourcePoolSelectionChange()"
ng-disabled="ctrl.state.isEdit"
data-cy="k8sAppCreate-nsSelect"
></select>
</div>
</div>
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
<div class="col-sm-12 small text-danger">
<pr-icon icon="'alert-circle'" mode="'danger'"></pr-icon>
This namespace has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the
namespace.
</div>
</div>
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
<div class="col-sm-12 small text-muted">
<pr-icon icon="'alert-circle'" mode="'warning'"></pr-icon>
You do not have access to any namespace. Contact your administrator to get access to a namespace.
</div>
</div>
<!-- kubernetes services options -->
<div ng-if="ctrl.formValues.ResourcePool">
<kube-services-form
on-change="(ctrl.onServicesChange)"
values="ctrl.formValues.Services"
app-name="ctrl.formValues.Name"
selector="ctrl.formValues.Selector"
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services, ingressPaths: ctrl.ingressPaths, originalIngressPaths: ctrl.originalIngressPaths}"
is-edit-mode="ctrl.state.isEdit"
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
></kube-services-form>
</div>
<!-- kubernetes services options -->
</div>
<!-- kubernetes summary for external application -->
<kubernetes-summary-view ng-if="ctrl.isExternalApplication()" form-values="ctrl.formValues" old-form-values="ctrl.savedFormValues"></kubernetes-summary-view>
<!-- kubernetes summary for external application -->
<div class="col-sm-12 form-section-title !mt-6" ng-if="ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.GIT" ng-hide="ctrl.stack.IsComposeFormat"> Actions </div>
<!-- #region ACTIONS -->
<div class="form-group" ng-hide="ctrl.stack.IsComposeFormat">
<div class="col-sm-12">
<button
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
type="button"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.imageValidityIsValid() || ctrl.hasPortErrors() || !ctrl.formValues.ResourcePool"
ng-click="ctrl.deployApplication()"
button-spinner="ctrl.state.actionInProgress"
data-cy="k8sAppCreate-deployButton"
>
<span ng-show="!ctrl.state.isEdit && !ctrl.state.actionInProgress">Deploy application</span>
<span ng-show="!ctrl.state.isEdit && ctrl.state.actionInProgress">Deployment in progress...</span>
<span ng-show="ctrl.state.isEdit && !ctrl.state.actionInProgress">Update application</span>
<span ng-show="ctrl.state.isEdit && ctrl.state.actionInProgress">Update in progress...</span>
</button>
<button
ng-if="ctrl.state.isEdit && !ctrl.state.actionInProgress && ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
type="button"
class="btn btn-sm btn-default"
ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })"
data-cy="k8sAppCreate-appCancelButton"
>
Cancel
</button>
<!-- #Web editor buttons -->
<button
class="btn btn-sm btn-primary"
ng-click="ctrl.updateApplicationViaWebEditor()"
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.CONTENT || ctrl.state.updateWebEditorInProgress"
ng-disabled="!kubernetesApplicationCreationForm.$valid || !ctrl.state.isEditorDirty || ctrl.state.updateWebEditorInProgress"
style="margin-top: 7px; margin-left: 0"
button-spinner="ctrl.state.updateWebEditorInProgress"
>
<span ng-show="!ctrl.state.updateWebEditorInProgress">Update the application</span>
<span ng-show="ctrl.state.updateWebEditorInProgress">Update in progress...</span>
</button>
</div>
</div>
<!-- #endregion -->
</form>
</rd-widget-body>
</rd-widget>

View File

@ -1114,10 +1114,10 @@ class KubernetesCreateApplicationController {
});
if (this.resourcePools.length) {
this.namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.resourcePools[0].Namespace.Name);
this.formValues.ResourcePool.Quota = this.namespaceWithQuota.Quota;
this.updateNamespaceLimits(this.namespaceWithQuota);
this.updateSliders(this.namespaceWithQuota);
const namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.resourcePools[0].Namespace.Name);
this.formValues.ResourcePool.Quota = namespaceWithQuota.Quota;
this.updateNamespaceLimits(namespaceWithQuota);
this.updateSliders(namespaceWithQuota);
}
this.formValues.ResourcePool = this.resourcePools[0];
if (!this.formValues.ResourcePool) {
@ -1140,8 +1140,6 @@ class KubernetesCreateApplicationController {
this.nodesLabels,
this.ingresses
);
this.formValues.Services = this.formValues.Services || [];
this.originalServicePorts = structuredClone(this.formValues.Services.flatMap((service) => service.Ports));
this.originalIngressPaths = structuredClone(this.originalServicePorts.flatMap((port) => port.ingressPaths).filter((ingressPath) => ingressPath.Host));
@ -1162,8 +1160,6 @@ class KubernetesCreateApplicationController {
this.formValues.OriginalIngresses = this.ingresses;
this.formValues.ImageModel = await this.parseImageConfiguration(this.formValues.ImageModel);
this.savedFormValues = angular.copy(this.formValues);
this.updateNamespaceLimits(this.namespaceWithQuota);
this.updateSliders(this.namespaceWithQuota);
delete this.formValues.ApplicationType;
if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) {

View File

@ -194,7 +194,7 @@ class KubernetesDeployController {
this.state.templateContent = await this.CustomTemplateService.customTemplateFile(templateId, template.GitConfig !== null);
this.onChangeFileContent(this.state.templateContent);
this.state.isEditorReadOnly = false;
this.state.isEditorReadOnly = true;
} catch (err) {
this.state.templateLoadFailed = true;
throw err;

View File

@ -144,18 +144,18 @@
<table-column-header
col-title="'Created'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'CreationDate'"
is-sorted-desc="$ctrl.state.orderBy === 'CreationDate' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('CreationDate')"
is-sorted="$ctrl.state.orderBy === 'ResourceControl.CreationDate'"
is-sorted-desc="$ctrl.state.orderBy === 'ResourceControl.CreationDate' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('ResourceControl.CreationDate')"
></table-column-header>
</th>
<th ng-if="$ctrl.columnVisibility.columns.updated.display">
<table-column-header
col-title="'Updated'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'UpdateDate'"
is-sorted-desc="$ctrl.state.orderBy === 'UpdateDate' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('UpdateDate')"
is-sorted="$ctrl.state.orderBy === 'ResourceControl.UpdateDate'"
is-sorted-desc="$ctrl.state.orderBy === 'ResourceControl.UpdateDate' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('ResourceControl.UpdateDate')"
></table-column-header>
</th>
<th>

View File

@ -52,7 +52,7 @@ export default class GitFormAuthFieldsetController {
...newValues,
};
this.onChange?.(value);
await this.runGitValidation(value, this.isAuthEdit);
await this.runGitValidation(value, false);
}
async runGitValidation(value: GitAuthModel, isAuthEdit: boolean) {

View File

@ -179,14 +179,6 @@ class StackRedeployGitFormController {
});
}
disablePullAndRedeployButton() {
return this.isSubmitButtonDisabled() || this.state.hasUnsavedChanges || !this.redeployGitForm.$valid;
}
disableSaveSettingsButton() {
return this.isSubmitButtonDisabled() || !this.state.hasUnsavedChanges || !this.redeployGitForm.$valid;
}
isSubmitButtonDisabled() {
return this.state.inProgress || this.state.redeployInProgress;
}

View File

@ -75,7 +75,7 @@
<button
class="btn btn-sm btn-primary"
ng-click="$ctrl.submit()"
ng-disabled="$ctrl.disablePullAndRedeployButton()"
ng-disabled="$ctrl.isSubmitButtonDisabled() || $ctrl.state.hasUnsavedChanges || !$ctrl.redeployGitForm.$valid"
style="margin-top: 7px; margin-left: 0"
button-spinner="$ctrl.state.redeployInProgress"
analytics-on
@ -92,7 +92,7 @@
<button
class="btn btn-sm btn-primary"
ng-click="$ctrl.saveGitSettings()"
ng-disabled="$ctrl.disableSaveSettingsButton()"
ng-disabled="$ctrl.isSubmitButtonDisabled() || !$ctrl.state.hasUnsavedChanges || !$ctrl.redeployGitForm.$valid"
style="margin-top: 7px; margin-left: 0"
button-spinner="$ctrl.state.inProgress"
analytics-on

View File

@ -13,9 +13,9 @@ export function StackViewModel(data) {
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
}
this.Status = data.Status;
this.CreationDate = data.CreationDate || undefined; // set undefined for angular sorting
this.CreationDate = data.CreationDate;
this.CreatedBy = data.CreatedBy;
this.UpdateDate = data.UpdateDate || undefined; // set undefined for angular sorting
this.UpdateDate = data.UpdateDate;
this.UpdatedBy = data.UpdatedBy;
this.Regular = true;
this.External = false;
@ -30,7 +30,7 @@ export function StackViewModel(data) {
export function ExternalStackViewModel(name, type, creationDate) {
this.Name = name;
this.Type = type;
this.CreationDate = creationDate || undefined; // set undefined for angular sorting
this.CreationDate = creationDate;
this.Regular = false;
this.External = true;
@ -50,9 +50,9 @@ export function OrphanedStackViewModel(data) {
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
}
this.Status = data.Status;
this.CreationDate = data.CreationDate || undefined; // set undefined for angular sorting
this.CreationDate = data.CreationDate;
this.CreatedBy = data.CreatedBy;
this.UpdateDate = data.UpdateDate || undefined; // set undefined for angular sorting
this.UpdateDate = data.UpdateDate;
this.UpdatedBy = data.UpdatedBy;
this.Regular = false;

View File

@ -306,7 +306,7 @@ angular
$scope.state.templateContent = await this.CustomTemplateService.customTemplateFile(templateId, template.GitConfig !== null);
onChangeFileContent($scope.state.templateContent);
$scope.state.isEditorReadOnly = false;
$scope.state.isEditorReadOnly = true;
} catch (err) {
$scope.state.templateLoadFailed = true;
throw err;

View File

@ -4,14 +4,14 @@ import { DockerImage } from '@/react/docker/images/types';
import { columnHelper } from './helper';
export const tags = columnHelper.accessor((item) => item.RepoTags.join(','), {
export const tags = columnHelper.accessor('RepoTags', {
id: 'tags',
header: 'Tags',
cell: Cell,
});
function Cell({ row: { original: item } }: CellContext<DockerImage, unknown>) {
const repoTags = item.RepoTags;
function Cell({ getValue }: CellContext<DockerImage, string[]>) {
const repoTags = getValue();
return (
<>

View File

@ -9,7 +9,6 @@ import UpToDate from '@/assets/ico/icon_up-to-date.svg?c';
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { getDashboardRoute } from '@/react/portainer/environments/utils';
import { cleanGitRepoUrl } from '@/react/portainer/gitops/utils';
import { Button } from '@@/buttons';
import { Icon } from '@@/Icon';
@ -188,9 +187,7 @@ function TargetVersionCell({
{row.original.TargetCommitHash ? (
<div>
<a
href={`${cleanGitRepoUrl(row.original.GitConfigURL)}/commit/${
row.original.TargetCommitHash
}`}
href={`${row.original.GitConfigURL}/commit/${row.original.TargetCommitHash}`}
target="_blank"
rel="noreferrer"
>
@ -239,9 +236,7 @@ function DeployedVersionCell({
<div>
{statusIcon}
<a
href={`${cleanGitRepoUrl(row.original.GitConfigURL)}/commit/${
row.original.TargetCommitHash
}`}
href={`${row.original.GitConfigURL}/commit/${row.original.TargetCommitHash}`}
target="_blank"
rel="noreferrer"
>

View File

@ -3,7 +3,6 @@ import _ from 'lodash';
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { cleanGitRepoUrl } from '@/react/portainer/gitops/utils';
import { buildNameColumn } from '@@/datatables/NameCell';
import { Link } from '@@/Link';
@ -152,9 +151,7 @@ export const columns = _.compact([
<div className="text-center">
<a
target="_blank"
href={`${cleanGitRepoUrl(item.GitConfig.URL)}/commit/${
item.GitConfig.ConfigHash
}`}
href={`${item.GitConfig.URL}/commit/${item.GitConfig.ConfigHash}`}
rel="noreferrer"
>
{item.GitConfig.ConfigHash.slice(0, 7)}

View File

@ -1,4 +1,4 @@
import { Pod } from 'kubernetes-types/core/v1';
import { EnvVar, Pod } from 'kubernetes-types/core/v1';
import { Asterisk, File, FileCode, Key, Lock } from 'lucide-react';
import { Icon } from '@@/Icon';
@ -44,7 +44,7 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
{envVar.isInitContainer && (
<span>
<Icon icon={Asterisk} className="!ml-1" />
{envVar.fieldPath} (
{envVar.valueFrom?.fieldRef?.fieldPath} (
<a
href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/"
target="_blank"
@ -56,13 +56,13 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
</span>
)}
</td>
<td data-cy="k8sAppDetail-envVarName">{envVar.key || '-'}</td>
<td data-cy="k8sAppDetail-envVarName">{envVar.name}</td>
<td data-cy="k8sAppDetail-envVarValue">
{envVar.value && <span>{envVar.value}</span>}
{envVar.fieldPath && (
{envVar.valueFrom?.fieldRef?.fieldPath && (
<span>
<Icon icon={Asterisk} className="!ml-1" />
{envVar.fieldPath} (
{envVar.valueFrom.fieldRef.fieldPath} (
<a
href="https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/"
target="_blank"
@ -73,36 +73,50 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
)
</span>
)}
{envVar.key ? (
{envVar.valueFrom?.secretKeyRef?.key && (
<span className="flex items-center">
<Icon icon={Key} className="!mr-1" />
{envVar.key}
{envVar.valueFrom.secretKeyRef.key}
</span>
) : (
'-'
)}
{envVar.valueFrom?.configMapKeyRef?.key && (
<span className="flex items-center">
<Icon icon={Key} className="!mr-1" />
{envVar.valueFrom.configMapKeyRef.key}
</span>
)}
{!envVar.value && !envVar.valueFrom && <span>-</span>}
</td>
<td data-cy="k8sAppDetail-configName">
{!envVar.resourseName && <span>-</span>}
{envVar.resourseName && (
{!envVar.valueFrom?.configMapKeyRef?.name &&
!envVar.valueFrom?.secretKeyRef?.name && <span>-</span>}
{envVar.valueFrom?.configMapKeyRef && (
<span>
<Link
to={
envVar.type === 'configMap'
? 'kubernetes.configmaps.configmap'
: 'kubernetes.secrets.secret'
}
to="kubernetes.configmaps.configmap"
params={{
name: envVar.resourseName,
name: envVar.valueFrom.configMapKeyRef.name,
namespace,
}}
className="flex items-center"
>
<Icon
icon={envVar.type === 'configMap' ? FileCode : Lock}
className="!mr-1"
/>
{envVar.resourseName}
<Icon icon={FileCode} className="!mr-1" />
{envVar.valueFrom.configMapKeyRef.name}
</Link>
</span>
)}
{envVar.valueFrom?.secretKeyRef && (
<span>
<Link
to="kubernetes.secrets.secret"
params={{
name: envVar.valueFrom.secretKeyRef.name,
namespace,
}}
className="flex items-center"
>
<Icon icon={Lock} className="!mr-1" />
{envVar.valueFrom.secretKeyRef.name}
</Link>
</span>
)}
@ -116,14 +130,9 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
);
}
interface ContainerEnvVar {
key?: string;
value?: string;
fieldPath?: string;
interface ContainerEnvVar extends EnvVar {
containerName: string;
isInitContainer: boolean;
type: 'configMap' | 'secret';
resourseName: string;
}
function getApplicationEnvironmentVariables(
@ -141,60 +150,23 @@ function getApplicationEnvironmentVariables(
// get all the environment variables for each container
const appContainersEnvVars =
appContainers?.flatMap((container) => {
const containerEnvVars: ContainerEnvVar[] =
appContainers?.flatMap(
(container) =>
container?.env?.map((envVar) => ({
key: envVar?.name,
fieldPath: envVar?.valueFrom?.fieldRef?.fieldPath,
...envVar,
containerName: container.name,
isInitContainer: false,
type: envVar?.valueFrom?.configMapKeyRef ? 'configMap' : 'secret',
resourseName:
envVar?.valueFrom?.configMapKeyRef?.name ||
envVar?.valueFrom?.secretKeyRef?.name ||
'',
})) || [];
const containerEnvFroms: ContainerEnvVar[] =
container?.envFrom?.map((envFrom) => ({
name: '',
resourseName:
envFrom?.configMapRef?.name || envFrom?.secretRef?.name || '',
containerName: container.name,
isInitContainer: false,
type: envFrom?.configMapRef ? 'configMap' : 'secret',
})) || [];
return [...containerEnvVars, ...containerEnvFroms];
}) || [];
})) || []
) || [];
const appInitContainersEnvVars =
appInitContainers?.flatMap((container) => {
const containerEnvVars: ContainerEnvVar[] =
appInitContainers?.flatMap(
(container) =>
container?.env?.map((envVar) => ({
key: envVar?.name,
fieldPath: envVar?.valueFrom?.fieldRef?.fieldPath,
...envVar,
containerName: container.name,
isInitContainer: false,
type: envVar?.valueFrom?.configMapKeyRef ? 'configMap' : 'secret',
resourseName:
envVar?.valueFrom?.configMapKeyRef?.name ||
envVar?.valueFrom?.secretKeyRef?.name ||
'',
})) || [];
const containerEnvFroms: ContainerEnvVar[] =
container?.envFrom?.map((envFrom) => ({
name: '',
resourseName:
envFrom?.configMapRef?.name || envFrom?.secretRef?.name || '',
containerName: container.name,
isInitContainer: false,
type: envFrom?.configMapRef ? 'configMap' : 'secret',
})) || [];
return [...containerEnvVars, ...containerEnvFroms];
}) || [];
isInitContainer: true,
})) || []
) || [];
return [...appContainersEnvVars, ...appInitContainersEnvVars];
}

View File

@ -250,12 +250,7 @@ async function getApplicationsByKind<T extends ApplicationList>(
const { data } = await axios.get<T>(
buildUrl(environmentId, namespace, `${appKind}s`)
);
const items = (data.items || []).map((app) => ({
...app,
kind: appKind,
apiVersion: data.apiVersion,
}));
return items as T['items'];
return data.items as T['items'];
} catch (e) {
throw parseAxiosError(e as Error, `Unable to retrieve ${appKind}s`);
}

View File

@ -19,15 +19,7 @@ export async function getNamespacePods(
},
}
);
const items = (data.items || []).map(
(pod) =>
<Pod>{
...pod,
kind: 'Pod',
apiVersion: data.apiVersion,
}
);
return items;
return data.items;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve pods');
}

View File

@ -14,16 +14,12 @@ export function getIsConfigMapInUse(
? app?.spec
: app?.spec?.template?.spec;
const hasEnvVarReference = appSpec?.containers.some((container) => {
const valueFromEnv = container.env?.some(
const hasEnvVarReference = appSpec?.containers.some((container) =>
container.env?.some(
(envVar) =>
envVar.valueFrom?.configMapKeyRef?.name === configMap.metadata?.name
);
const envFromEnv = container.envFrom?.some(
(envVar) => envVar.configMapRef?.name === configMap.metadata?.name
);
return valueFromEnv || envFromEnv;
});
)
);
const hasVolumeReference = appSpec?.volumes?.some(
(volume) => volume.configMap?.name === configMap.metadata?.name
);

View File

@ -11,16 +11,12 @@ export function getIsSecretInUse(secret: Secret, applications: Application[]) {
? app?.spec
: app?.spec?.template?.spec;
const hasEnvVarReference = appSpec?.containers.some((container) => {
const valueFromEnv = container.env?.some(
const hasEnvVarReference = appSpec?.containers.some((container) =>
container.env?.some(
(envVar) =>
envVar.valueFrom?.secretKeyRef?.name === secret.metadata?.name
);
const envFromEnv = container.envFrom?.some(
(envVar) => envVar.secretRef?.name === secret.metadata?.name
);
return valueFromEnv || envFromEnv;
});
)
);
const hasVolumeReference = appSpec?.volumes?.some(
(volume) => volume.secret?.secretName === secret.metadata?.name
);

View File

@ -165,8 +165,7 @@ export function gitAuthValidation(
NewCredentialName: string()
.default('')
.when(['RepositoryAuthentication', 'SaveCredential'], {
is: (RepositoryAuthentication: boolean, SaveCredential: boolean) =>
RepositoryAuthentication && SaveCredential && !isAuthEdit,
is: true,
then: string()
.required('Name is required')
.test(

View File

@ -32,10 +32,3 @@ export function confirmEnableTLSVerify() {
'Enabling the verification of TLS certificates without ensuring the correct configuration of your Certificate Authority (CA) for self-signed certificates can result in deployment failures.',
});
}
export function cleanGitRepoUrl(url: string) {
return url
.trim() // remove leading and trailing whitespace
.replace(/\/$/, '') // if there's a trailing slash, remove it
.replace(/\.git$/, ''); // if there's a trailing .git extension, remove it
}

View File

@ -1,6 +1,5 @@
import { Download } from 'lucide-react';
import { useLocalStorage } from '@/react/hooks/useLocalStorage';
import { useState } from 'react';
import { Widget, WidgetBody, WidgetTitle } from '@@/Widget';
import { FormSection } from '@@/form-components/FormSection';
@ -11,14 +10,11 @@ import { BackupFileForm } from './BackupFileForm';
import { BackupS3Form } from './BackupS3Form';
export function BackupSettingsPanel() {
const [backupType, setBackupType] = useLocalStorage(
'settings_backup_type',
options[0].value
);
const [backupType, setBackupType] = useState(options[0].value);
return (
<Widget>
<WidgetTitle icon={Download} title="Back up Portainer" />
<WidgetTitle icon={Download} title="Backup up Portainer" />
<WidgetBody>
<div className="form-horizontal">
<FormSection title="Backup configuration">

View File

@ -20,8 +20,8 @@ import { useUpdateSSLConfigMutation } from '../useUpdateSSLConfigMutation';
import { useSSLSettings } from '../../queries/useSSLSettings';
interface FormValues {
certFile?: File;
keyFile?: File;
certFile: File | null;
keyFile: File | null;
forceHTTPS: boolean;
}
@ -37,8 +37,8 @@ function SSLSettingsPanel() {
}
const initialValues: FormValues = {
certFile: undefined,
keyFile: undefined,
certFile: null,
keyFile: null,
forceHTTPS: !settingsQuery.data.httpEnabled,
};
@ -52,7 +52,7 @@ function SSLSettingsPanel() {
validationSchema={validation}
validateOnMount
>
{({ values, setFieldValue, isValid, errors, dirty }) => (
{({ values, setFieldValue, isValid, errors }) => (
<Form className="form-horizontal">
<div className="form-group">
<div className="col-sm-12">
@ -92,6 +92,7 @@ function SSLSettingsPanel() {
errors={errors.certFile}
>
<FileUploadField
required={typeof errors.certFile !== 'undefined'}
inputId="ca-cert-field"
name="certFile"
onChange={(file) => setFieldValue('certFile', file)}
@ -106,6 +107,7 @@ function SSLSettingsPanel() {
errors={errors.keyFile}
>
<FileUploadField
required={typeof errors.keyFile !== 'undefined'}
inputId="ca-cert-field"
name="keyFile"
onChange={(file) => setFieldValue('keyFile', file)}
@ -117,7 +119,7 @@ function SSLSettingsPanel() {
<div className="col-sm-12">
<LoadingButton
isLoading={mutation.isLoading || reloadingPage}
disabled={!dirty || !isValid}
disabled={!isValid}
loadingText={reloadingPage ? 'Reloading' : 'Saving'}
className="!ml-0"
>
@ -133,12 +135,16 @@ function SSLSettingsPanel() {
);
function handleSubmit({ certFile, forceHTTPS, keyFile }: FormValues) {
if (!certFile || !keyFile) {
return;
}
mutation.mutate(
{ certFile, httpEnabled: !forceHTTPS, keyFile },
{
async onSuccess() {
await new Promise((resolve) => {
setTimeout(resolve, 10000);
setTimeout(resolve, 5000);
});
window.location.reload();
setReloadingPage(true);
@ -150,13 +156,10 @@ function SSLSettingsPanel() {
function validation(): SchemaOf<FormValues> {
return object({
certFile: withFileExtension(file(), [
'pem',
'crt',
'cer',
'cert',
]).optional(),
keyFile: withFileExtension(file(), ['pem', 'key']).optional(),
certFile: withFileExtension(file(), ['pem', 'crt', 'cer', 'cert']).required(
''
),
keyFile: withFileExtension(file(), ['pem', 'key']).required(''),
forceHTTPS: bool().required(),
});
}