mirror of https://github.com/portainer/portainer
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 commitpull/11470/head30356d2c15
. * Revert "docs(api): default to pascal case for property name [EE-6471] (#10861)" This reverts commit392819576c
. * Revert "fix(edge/jobs): clear logs [EE-5923] (#10819)" This reverts commite01386f63d
. * Revert "disable html5 validation (#10843)" This reverts commit4b0f08e92a
. * Revert "fix(edgestack): allow to set retry deployment toggle EE-6167 (#10676) (#10805)" This reverts commiteee632b22d
. * Revert "fix(stack): edit git stack validation (#10812)" This reverts commitd3b150b29c
. * Revert "Revert "Revert "fix(rollback): reversed rollback code from 2.19.4 [EE-6435] (#10811)" (#10832)" This reverts commit32e05bb705
. * Revert "fix(setting/ssl): cert files are optional to upload [EE-6139] (#10779)" This reverts commit7408668dbb
. * Revert "fix(endpoint): delete the endpoint proxy when updating an endpoint address [EE-5577] (#10824)" This reverts commit4b5ea01456
. * Revert "fix(swagger): custom template create docs EE-6428 (#10806)" This reverts commit0d55cb3e08
. * Revert "fix(images): sort by tags [EE-6410] (#10755)" This reverts commit7f51c727a0
. * Revert "fix(stacks): sort by date [EE-5766] (#10758)" This reverts commit57b80cd9ac
. * Revert "fix(UI): remember backup settings tab [EE-6347] (#10764)" This reverts commitc20452492d
. * Revert "fix(backup ui): minor typo on backup page EE-6348 (#10717)" This reverts commitd58046eb68
. * Revert "fix(app): shift external to the top [EE-6392] (#10753) (#10802)" This reverts commit4795e85d18
. * Revert "fix(app): update sliders when limits are known [EE-5933] (#10769) (#10801)" This reverts commitd090b0043a
. * Revert "fix(gitops): correct commit hash link [EE-6346] (#10800)" This reverts commit0e59cf76ec
. * Revert "fix toast error (#10804)" This reverts commit9978b88ed4
. * Revert "fix(kube): configmaps and secrets from envFrom in the app detail screen [EE-6282] (#10741) (#10798)" This reverts commitc1a01558d0
. * Revert "fix(stacks): allow editing custom templates before stack deployment EE-6380 (#10713)" This reverts commitf0aa0554f8
. * Reapply "fix(rollback): reversed rollback code from 2.19.4 [EE-6435] (#10811) This reverts commitc58fa274e7
.
parent
d2eb53eb18
commit
6f5d9c357f
2
Makefile
2
Makefile
|
@ -114,7 +114,7 @@ dev-extension: build-server build-client ## Run the extension in development mod
|
||||||
##@ Docs
|
##@ Docs
|
||||||
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
|
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
|
||||||
docs-build: init-dist ## Build docs
|
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
|
docs-validate: docs-build ## Validate docs
|
||||||
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
|
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
|
||||||
|
|
|
@ -149,7 +149,7 @@ func isValidNote(note string) bool {
|
||||||
// @success 200 {object} portainer.CustomTemplate
|
// @success 200 {object} portainer.CustomTemplate
|
||||||
// @failure 400 "Invalid request"
|
// @failure 400 "Invalid request"
|
||||||
// @failure 500 "Server error"
|
// @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) {
|
func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*portainer.CustomTemplate, error) {
|
||||||
var payload customTemplateFromFileContentPayload
|
var payload customTemplateFromFileContentPayload
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
|
@ -263,7 +263,7 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request)
|
||||||
// @success 200 {object} portainer.CustomTemplate
|
// @success 200 {object} portainer.CustomTemplate
|
||||||
// @failure 400 "Invalid request"
|
// @failure 400 "Invalid request"
|
||||||
// @failure 500 "Server error"
|
// @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) {
|
func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) (*portainer.CustomTemplate, error) {
|
||||||
var payload customTemplateFromGitRepositoryPayload
|
var payload customTemplateFromGitRepositoryPayload
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
|
@ -443,7 +443,7 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er
|
||||||
// @success 200 {object} portainer.CustomTemplate
|
// @success 200 {object} portainer.CustomTemplate
|
||||||
// @failure 400 "Invalid request"
|
// @failure 400 "Invalid request"
|
||||||
// @failure 500 "Server error"
|
// @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) {
|
func (handler *Handler) createCustomTemplateFromFileUpload(r *http.Request) (*portainer.CustomTemplate, error) {
|
||||||
payload := &customTemplateFromFileUploadPayload{}
|
payload := &customTemplateFromFileUploadPayload{}
|
||||||
err := payload.Validate(r)
|
err := payload.Validate(r)
|
||||||
|
|
|
@ -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)
|
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)
|
endpoint, err := tx.Endpoint().Endpoint(endpointID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.NotFound("Unable to retrieve environment from the database", err)
|
return httperror.NotFound("Unable to retrieve environment from the database", err)
|
||||||
|
|
|
@ -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)
|
return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEndpointProxy := shouldReloadTLSConfiguration(endpoint, &payload)
|
|
||||||
|
|
||||||
if payload.Name != nil {
|
if payload.Name != nil {
|
||||||
name := *payload.Name
|
name := *payload.Name
|
||||||
isUnique, err := handler.isNameUnique(name, endpoint.ID)
|
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
|
endpoint.URL = *payload.URL
|
||||||
updateEndpointProxy = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.PublicURL != nil {
|
if payload.PublicURL != nil {
|
||||||
|
@ -182,8 +179,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpoint.Type == portainer.AzureEnvironment {
|
if endpoint.Type == portainer.AzureEnvironment {
|
||||||
updateEndpointProxy = true
|
|
||||||
|
|
||||||
credentials := endpoint.AzureCredentials
|
credentials := endpoint.AzureCredentials
|
||||||
if payload.AzureApplicationID != nil {
|
if payload.AzureApplicationID != nil {
|
||||||
credentials.ApplicationID = *payload.AzureApplicationID
|
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)
|
handler.ProxyManager.DeleteEndpointProxy(endpoint.ID)
|
||||||
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
||||||
if err != nil {
|
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 {
|
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,
|
// 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.
|
// we assume that new TLS files have been uploaded and we need to reload the TLS configuration.
|
||||||
if endpoint.Type != portainer.DockerEnvironment ||
|
if endpoint.Type != portainer.DockerEnvironment ||
|
||||||
|
|
|
@ -34,7 +34,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
|
||||||
endpoint.Status = status;
|
endpoint.Status = status;
|
||||||
|
|
||||||
if (status === EnvironmentStatus.Down) {
|
if (status === EnvironmentStatus.Down) {
|
||||||
throw new Error(`The environment named ${endpoint.Name} is unreachable.`);
|
throw new Error('Environment is unreachable.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await StateManager.updateEndpointState(endpoint);
|
await StateManager.updateEndpointState(endpoint);
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<p class="text-muted" ng-if="applicationState.endpoint.mode.role === 'MANAGER'">
|
<p class="text-muted" ng-if="applicationState.endpoint.mode.role === 'MANAGER'">
|
||||||
<pr-icon icon="'alert-circle'" mode="'primary'"></pr-icon>
|
<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
|
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>
|
||||||
<p class="text-muted" ng-if="applicationState.endpoint.mode.role === 'WORKER'">
|
<p class="text-muted" ng-if="applicationState.endpoint.mode.role === 'WORKER'">
|
||||||
<pr-icon icon="'alert-circle'" mode="'primary'"></pr-icon>
|
<pr-icon icon="'alert-circle'" mode="'primary'"></pr-icon>
|
||||||
|
|
|
@ -101,7 +101,6 @@ export class EditEdgeStackViewController {
|
||||||
edgeGroups: values.edgeGroups,
|
edgeGroups: values.edgeGroups,
|
||||||
deploymentType: values.deploymentType,
|
deploymentType: values.deploymentType,
|
||||||
updateVersion,
|
updateVersion,
|
||||||
retryDeploy: values.retryDeploy,
|
|
||||||
webhook: values.webhookEnabled ? this.stack.Webhook || createWebhookId() : '',
|
webhook: values.webhookEnabled ? this.stack.Webhook || createWebhookId() : '',
|
||||||
envVars: values.envVars,
|
envVars: values.envVars,
|
||||||
});
|
});
|
||||||
|
|
|
@ -53,7 +53,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
||||||
try {
|
try {
|
||||||
await getSelfSubjectAccessReview(endpoint.Id, 'default');
|
await getSelfSubjectAccessReview(endpoint.Id, 'default');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`The environment named ${endpoint.Name} is unreachable.`);
|
throw new Error('Environment is unreachable.');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let params = {};
|
let params = {};
|
||||||
|
|
|
@ -59,94 +59,7 @@
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-body>
|
<rd-widget-body>
|
||||||
<form class="form-horizontal mt-4" name="kubernetesApplicationCreationForm" autocomplete="off" novalidate>
|
<form class="form-horizontal mt-4" name="kubernetesApplicationCreationForm" autocomplete="off">
|
||||||
<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>
|
|
||||||
<div ng-if="!ctrl.isExternalApplication()">
|
<div ng-if="!ctrl.isExternalApplication()">
|
||||||
<git-form-info-panel
|
<git-form-info-panel
|
||||||
ng-if="ctrl.state.appType == ctrl.KubernetesDeploymentTypes.GIT"
|
ng-if="ctrl.state.appType == ctrl.KubernetesDeploymentTypes.GIT"
|
||||||
|
@ -1402,54 +1315,105 @@
|
||||||
></kube-services-form>
|
></kube-services-form>
|
||||||
</div>
|
</div>
|
||||||
<!-- kubernetes services options -->
|
<!-- kubernetes services options -->
|
||||||
|
|
||||||
|
<!-- summary -->
|
||||||
<kubernetes-summary-view
|
<kubernetes-summary-view
|
||||||
ng-if="!(!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.state.pullImageValidity)"
|
ng-if="!(!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.state.pullImageValidity)"
|
||||||
form-values="ctrl.formValues"
|
form-values="ctrl.formValues"
|
||||||
old-form-values="ctrl.savedFormValues"
|
old-form-values="ctrl.savedFormValues"
|
||||||
></kubernetes-summary-view>
|
></kubernetes-summary-view>
|
||||||
</div>
|
</div>
|
||||||
<!-- #region ACTIONS -->
|
</div>
|
||||||
<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 ng-if="ctrl.isExternalApplication()">
|
||||||
<div class="form-group" ng-hide="ctrl.stack.IsComposeFormat">
|
<div class="col-sm-12 form-section-title" ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"> Namespace </div>
|
||||||
<div class="col-sm-12">
|
<!-- #region NAMESPACE -->
|
||||||
<button
|
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
|
||||||
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
|
<label for="resource-pool-selector" class="col-sm-1 control-label text-left">Namespace</label>
|
||||||
type="button"
|
<div class="col-sm-11">
|
||||||
class="btn btn-primary btn-sm !ml-0"
|
<select
|
||||||
ng-disabled="!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.imageValidityIsValid() || ctrl.hasPortErrors() || !ctrl.formValues.ResourcePool"
|
class="form-control"
|
||||||
ng-click="ctrl.deployApplication()"
|
id="resource-pool-selector"
|
||||||
button-spinner="ctrl.state.actionInProgress"
|
ng-model="ctrl.formValues.ResourcePool"
|
||||||
data-cy="k8sAppCreate-deployButton"
|
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
||||||
>
|
ng-change="ctrl.onResourcePoolSelectionChange()"
|
||||||
<span ng-show="!ctrl.state.isEdit && !ctrl.state.actionInProgress">Deploy application</span>
|
ng-disabled="ctrl.state.isEdit"
|
||||||
<span ng-show="!ctrl.state.isEdit && ctrl.state.actionInProgress">Deployment in progress...</span>
|
data-cy="k8sAppCreate-nsSelect"
|
||||||
<span ng-show="ctrl.state.isEdit && !ctrl.state.actionInProgress">Update application</span>
|
></select>
|
||||||
<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>
|
||||||
</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>
|
</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>
|
</form>
|
||||||
</rd-widget-body>
|
</rd-widget-body>
|
||||||
</rd-widget>
|
</rd-widget>
|
||||||
|
|
|
@ -1114,10 +1114,10 @@ class KubernetesCreateApplicationController {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.resourcePools.length) {
|
if (this.resourcePools.length) {
|
||||||
this.namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.resourcePools[0].Namespace.Name);
|
const namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.resourcePools[0].Namespace.Name);
|
||||||
this.formValues.ResourcePool.Quota = this.namespaceWithQuota.Quota;
|
this.formValues.ResourcePool.Quota = namespaceWithQuota.Quota;
|
||||||
this.updateNamespaceLimits(this.namespaceWithQuota);
|
this.updateNamespaceLimits(namespaceWithQuota);
|
||||||
this.updateSliders(this.namespaceWithQuota);
|
this.updateSliders(namespaceWithQuota);
|
||||||
}
|
}
|
||||||
this.formValues.ResourcePool = this.resourcePools[0];
|
this.formValues.ResourcePool = this.resourcePools[0];
|
||||||
if (!this.formValues.ResourcePool) {
|
if (!this.formValues.ResourcePool) {
|
||||||
|
@ -1140,8 +1140,6 @@ class KubernetesCreateApplicationController {
|
||||||
this.nodesLabels,
|
this.nodesLabels,
|
||||||
this.ingresses
|
this.ingresses
|
||||||
);
|
);
|
||||||
|
|
||||||
this.formValues.Services = this.formValues.Services || [];
|
|
||||||
this.originalServicePorts = structuredClone(this.formValues.Services.flatMap((service) => service.Ports));
|
this.originalServicePorts = structuredClone(this.formValues.Services.flatMap((service) => service.Ports));
|
||||||
this.originalIngressPaths = structuredClone(this.originalServicePorts.flatMap((port) => port.ingressPaths).filter((ingressPath) => ingressPath.Host));
|
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.OriginalIngresses = this.ingresses;
|
||||||
this.formValues.ImageModel = await this.parseImageConfiguration(this.formValues.ImageModel);
|
this.formValues.ImageModel = await this.parseImageConfiguration(this.formValues.ImageModel);
|
||||||
this.savedFormValues = angular.copy(this.formValues);
|
this.savedFormValues = angular.copy(this.formValues);
|
||||||
this.updateNamespaceLimits(this.namespaceWithQuota);
|
|
||||||
this.updateSliders(this.namespaceWithQuota);
|
|
||||||
delete this.formValues.ApplicationType;
|
delete this.formValues.ApplicationType;
|
||||||
|
|
||||||
if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) {
|
if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) {
|
||||||
|
|
|
@ -194,7 +194,7 @@ class KubernetesDeployController {
|
||||||
this.state.templateContent = await this.CustomTemplateService.customTemplateFile(templateId, template.GitConfig !== null);
|
this.state.templateContent = await this.CustomTemplateService.customTemplateFile(templateId, template.GitConfig !== null);
|
||||||
this.onChangeFileContent(this.state.templateContent);
|
this.onChangeFileContent(this.state.templateContent);
|
||||||
|
|
||||||
this.state.isEditorReadOnly = false;
|
this.state.isEditorReadOnly = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.state.templateLoadFailed = true;
|
this.state.templateLoadFailed = true;
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
@ -144,18 +144,18 @@
|
||||||
<table-column-header
|
<table-column-header
|
||||||
col-title="'Created'"
|
col-title="'Created'"
|
||||||
can-sort="true"
|
can-sort="true"
|
||||||
is-sorted="$ctrl.state.orderBy === 'CreationDate'"
|
is-sorted="$ctrl.state.orderBy === 'ResourceControl.CreationDate'"
|
||||||
is-sorted-desc="$ctrl.state.orderBy === 'CreationDate' && $ctrl.state.reverseOrder"
|
is-sorted-desc="$ctrl.state.orderBy === 'ResourceControl.CreationDate' && $ctrl.state.reverseOrder"
|
||||||
ng-click="$ctrl.changeOrderBy('CreationDate')"
|
ng-click="$ctrl.changeOrderBy('ResourceControl.CreationDate')"
|
||||||
></table-column-header>
|
></table-column-header>
|
||||||
</th>
|
</th>
|
||||||
<th ng-if="$ctrl.columnVisibility.columns.updated.display">
|
<th ng-if="$ctrl.columnVisibility.columns.updated.display">
|
||||||
<table-column-header
|
<table-column-header
|
||||||
col-title="'Updated'"
|
col-title="'Updated'"
|
||||||
can-sort="true"
|
can-sort="true"
|
||||||
is-sorted="$ctrl.state.orderBy === 'UpdateDate'"
|
is-sorted="$ctrl.state.orderBy === 'ResourceControl.UpdateDate'"
|
||||||
is-sorted-desc="$ctrl.state.orderBy === 'UpdateDate' && $ctrl.state.reverseOrder"
|
is-sorted-desc="$ctrl.state.orderBy === 'ResourceControl.UpdateDate' && $ctrl.state.reverseOrder"
|
||||||
ng-click="$ctrl.changeOrderBy('UpdateDate')"
|
ng-click="$ctrl.changeOrderBy('ResourceControl.UpdateDate')"
|
||||||
></table-column-header>
|
></table-column-header>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default class GitFormAuthFieldsetController {
|
||||||
...newValues,
|
...newValues,
|
||||||
};
|
};
|
||||||
this.onChange?.(value);
|
this.onChange?.(value);
|
||||||
await this.runGitValidation(value, this.isAuthEdit);
|
await this.runGitValidation(value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async runGitValidation(value: GitAuthModel, isAuthEdit: boolean) {
|
async runGitValidation(value: GitAuthModel, isAuthEdit: boolean) {
|
||||||
|
|
|
@ -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() {
|
isSubmitButtonDisabled() {
|
||||||
return this.state.inProgress || this.state.redeployInProgress;
|
return this.state.inProgress || this.state.redeployInProgress;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-primary"
|
class="btn btn-sm btn-primary"
|
||||||
ng-click="$ctrl.submit()"
|
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"
|
style="margin-top: 7px; margin-left: 0"
|
||||||
button-spinner="$ctrl.state.redeployInProgress"
|
button-spinner="$ctrl.state.redeployInProgress"
|
||||||
analytics-on
|
analytics-on
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-primary"
|
class="btn btn-sm btn-primary"
|
||||||
ng-click="$ctrl.saveGitSettings()"
|
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"
|
style="margin-top: 7px; margin-left: 0"
|
||||||
button-spinner="$ctrl.state.inProgress"
|
button-spinner="$ctrl.state.inProgress"
|
||||||
analytics-on
|
analytics-on
|
||||||
|
|
|
@ -13,9 +13,9 @@ export function StackViewModel(data) {
|
||||||
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
||||||
}
|
}
|
||||||
this.Status = data.Status;
|
this.Status = data.Status;
|
||||||
this.CreationDate = data.CreationDate || undefined; // set undefined for angular sorting
|
this.CreationDate = data.CreationDate;
|
||||||
this.CreatedBy = data.CreatedBy;
|
this.CreatedBy = data.CreatedBy;
|
||||||
this.UpdateDate = data.UpdateDate || undefined; // set undefined for angular sorting
|
this.UpdateDate = data.UpdateDate;
|
||||||
this.UpdatedBy = data.UpdatedBy;
|
this.UpdatedBy = data.UpdatedBy;
|
||||||
this.Regular = true;
|
this.Regular = true;
|
||||||
this.External = false;
|
this.External = false;
|
||||||
|
@ -30,7 +30,7 @@ export function StackViewModel(data) {
|
||||||
export function ExternalStackViewModel(name, type, creationDate) {
|
export function ExternalStackViewModel(name, type, creationDate) {
|
||||||
this.Name = name;
|
this.Name = name;
|
||||||
this.Type = type;
|
this.Type = type;
|
||||||
this.CreationDate = creationDate || undefined; // set undefined for angular sorting
|
this.CreationDate = creationDate;
|
||||||
|
|
||||||
this.Regular = false;
|
this.Regular = false;
|
||||||
this.External = true;
|
this.External = true;
|
||||||
|
@ -50,9 +50,9 @@ export function OrphanedStackViewModel(data) {
|
||||||
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
||||||
}
|
}
|
||||||
this.Status = data.Status;
|
this.Status = data.Status;
|
||||||
this.CreationDate = data.CreationDate || undefined; // set undefined for angular sorting
|
this.CreationDate = data.CreationDate;
|
||||||
this.CreatedBy = data.CreatedBy;
|
this.CreatedBy = data.CreatedBy;
|
||||||
this.UpdateDate = data.UpdateDate || undefined; // set undefined for angular sorting
|
this.UpdateDate = data.UpdateDate;
|
||||||
this.UpdatedBy = data.UpdatedBy;
|
this.UpdatedBy = data.UpdatedBy;
|
||||||
|
|
||||||
this.Regular = false;
|
this.Regular = false;
|
||||||
|
|
|
@ -306,7 +306,7 @@ angular
|
||||||
$scope.state.templateContent = await this.CustomTemplateService.customTemplateFile(templateId, template.GitConfig !== null);
|
$scope.state.templateContent = await this.CustomTemplateService.customTemplateFile(templateId, template.GitConfig !== null);
|
||||||
onChangeFileContent($scope.state.templateContent);
|
onChangeFileContent($scope.state.templateContent);
|
||||||
|
|
||||||
$scope.state.isEditorReadOnly = false;
|
$scope.state.isEditorReadOnly = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$scope.state.templateLoadFailed = true;
|
$scope.state.templateLoadFailed = true;
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
@ -4,14 +4,14 @@ import { DockerImage } from '@/react/docker/images/types';
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
import { columnHelper } from './helper';
|
||||||
|
|
||||||
export const tags = columnHelper.accessor((item) => item.RepoTags.join(','), {
|
export const tags = columnHelper.accessor('RepoTags', {
|
||||||
id: 'tags',
|
id: 'tags',
|
||||||
header: 'Tags',
|
header: 'Tags',
|
||||||
cell: Cell,
|
cell: Cell,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Cell({ row: { original: item } }: CellContext<DockerImage, unknown>) {
|
function Cell({ getValue }: CellContext<DockerImage, string[]>) {
|
||||||
const repoTags = item.RepoTags;
|
const repoTags = getValue();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -9,7 +9,6 @@ import UpToDate from '@/assets/ico/icon_up-to-date.svg?c';
|
||||||
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
||||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||||
import { getDashboardRoute } from '@/react/portainer/environments/utils';
|
import { getDashboardRoute } from '@/react/portainer/environments/utils';
|
||||||
import { cleanGitRepoUrl } from '@/react/portainer/gitops/utils';
|
|
||||||
|
|
||||||
import { Button } from '@@/buttons';
|
import { Button } from '@@/buttons';
|
||||||
import { Icon } from '@@/Icon';
|
import { Icon } from '@@/Icon';
|
||||||
|
@ -188,9 +187,7 @@ function TargetVersionCell({
|
||||||
{row.original.TargetCommitHash ? (
|
{row.original.TargetCommitHash ? (
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href={`${cleanGitRepoUrl(row.original.GitConfigURL)}/commit/${
|
href={`${row.original.GitConfigURL}/commit/${row.original.TargetCommitHash}`}
|
||||||
row.original.TargetCommitHash
|
|
||||||
}`}
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
@ -239,9 +236,7 @@ function DeployedVersionCell({
|
||||||
<div>
|
<div>
|
||||||
{statusIcon}
|
{statusIcon}
|
||||||
<a
|
<a
|
||||||
href={`${cleanGitRepoUrl(row.original.GitConfigURL)}/commit/${
|
href={`${row.original.GitConfigURL}/commit/${row.original.TargetCommitHash}`}
|
||||||
row.original.TargetCommitHash
|
|
||||||
}`}
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,7 +3,6 @@ import _ from 'lodash';
|
||||||
|
|
||||||
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
||||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||||
import { cleanGitRepoUrl } from '@/react/portainer/gitops/utils';
|
|
||||||
|
|
||||||
import { buildNameColumn } from '@@/datatables/NameCell';
|
import { buildNameColumn } from '@@/datatables/NameCell';
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
|
@ -152,9 +151,7 @@ export const columns = _.compact([
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={`${cleanGitRepoUrl(item.GitConfig.URL)}/commit/${
|
href={`${item.GitConfig.URL}/commit/${item.GitConfig.ConfigHash}`}
|
||||||
item.GitConfig.ConfigHash
|
|
||||||
}`}
|
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{item.GitConfig.ConfigHash.slice(0, 7)}
|
{item.GitConfig.ConfigHash.slice(0, 7)}
|
||||||
|
|
|
@ -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 { Asterisk, File, FileCode, Key, Lock } from 'lucide-react';
|
||||||
|
|
||||||
import { Icon } from '@@/Icon';
|
import { Icon } from '@@/Icon';
|
||||||
|
@ -44,7 +44,7 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
|
||||||
{envVar.isInitContainer && (
|
{envVar.isInitContainer && (
|
||||||
<span>
|
<span>
|
||||||
<Icon icon={Asterisk} className="!ml-1" />
|
<Icon icon={Asterisk} className="!ml-1" />
|
||||||
{envVar.fieldPath} (
|
{envVar.valueFrom?.fieldRef?.fieldPath} (
|
||||||
<a
|
<a
|
||||||
href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/"
|
href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -56,13 +56,13 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td data-cy="k8sAppDetail-envVarName">{envVar.key || '-'}</td>
|
<td data-cy="k8sAppDetail-envVarName">{envVar.name}</td>
|
||||||
<td data-cy="k8sAppDetail-envVarValue">
|
<td data-cy="k8sAppDetail-envVarValue">
|
||||||
{envVar.value && <span>{envVar.value}</span>}
|
{envVar.value && <span>{envVar.value}</span>}
|
||||||
{envVar.fieldPath && (
|
{envVar.valueFrom?.fieldRef?.fieldPath && (
|
||||||
<span>
|
<span>
|
||||||
<Icon icon={Asterisk} className="!ml-1" />
|
<Icon icon={Asterisk} className="!ml-1" />
|
||||||
{envVar.fieldPath} (
|
{envVar.valueFrom.fieldRef.fieldPath} (
|
||||||
<a
|
<a
|
||||||
href="https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/"
|
href="https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -73,36 +73,50 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
|
||||||
)
|
)
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{envVar.key ? (
|
{envVar.valueFrom?.secretKeyRef?.key && (
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
<Icon icon={Key} className="!mr-1" />
|
<Icon icon={Key} className="!mr-1" />
|
||||||
{envVar.key}
|
{envVar.valueFrom.secretKeyRef.key}
|
||||||
</span>
|
</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>
|
||||||
<td data-cy="k8sAppDetail-configName">
|
<td data-cy="k8sAppDetail-configName">
|
||||||
{!envVar.resourseName && <span>-</span>}
|
{!envVar.valueFrom?.configMapKeyRef?.name &&
|
||||||
{envVar.resourseName && (
|
!envVar.valueFrom?.secretKeyRef?.name && <span>-</span>}
|
||||||
|
{envVar.valueFrom?.configMapKeyRef && (
|
||||||
<span>
|
<span>
|
||||||
<Link
|
<Link
|
||||||
to={
|
to="kubernetes.configmaps.configmap"
|
||||||
envVar.type === 'configMap'
|
|
||||||
? 'kubernetes.configmaps.configmap'
|
|
||||||
: 'kubernetes.secrets.secret'
|
|
||||||
}
|
|
||||||
params={{
|
params={{
|
||||||
name: envVar.resourseName,
|
name: envVar.valueFrom.configMapKeyRef.name,
|
||||||
namespace,
|
namespace,
|
||||||
}}
|
}}
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon icon={FileCode} className="!mr-1" />
|
||||||
icon={envVar.type === 'configMap' ? FileCode : Lock}
|
{envVar.valueFrom.configMapKeyRef.name}
|
||||||
className="!mr-1"
|
</Link>
|
||||||
/>
|
</span>
|
||||||
{envVar.resourseName}
|
)}
|
||||||
|
{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>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -116,14 +130,9 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContainerEnvVar {
|
interface ContainerEnvVar extends EnvVar {
|
||||||
key?: string;
|
|
||||||
value?: string;
|
|
||||||
fieldPath?: string;
|
|
||||||
containerName: string;
|
containerName: string;
|
||||||
isInitContainer: boolean;
|
isInitContainer: boolean;
|
||||||
type: 'configMap' | 'secret';
|
|
||||||
resourseName: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getApplicationEnvironmentVariables(
|
function getApplicationEnvironmentVariables(
|
||||||
|
@ -141,60 +150,23 @@ function getApplicationEnvironmentVariables(
|
||||||
|
|
||||||
// get all the environment variables for each container
|
// get all the environment variables for each container
|
||||||
const appContainersEnvVars =
|
const appContainersEnvVars =
|
||||||
appContainers?.flatMap((container) => {
|
appContainers?.flatMap(
|
||||||
const containerEnvVars: ContainerEnvVar[] =
|
(container) =>
|
||||||
container?.env?.map((envVar) => ({
|
container?.env?.map((envVar) => ({
|
||||||
key: envVar?.name,
|
...envVar,
|
||||||
fieldPath: envVar?.valueFrom?.fieldRef?.fieldPath,
|
|
||||||
containerName: container.name,
|
containerName: container.name,
|
||||||
isInitContainer: false,
|
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 =
|
const appInitContainersEnvVars =
|
||||||
appInitContainers?.flatMap((container) => {
|
appInitContainers?.flatMap(
|
||||||
const containerEnvVars: ContainerEnvVar[] =
|
(container) =>
|
||||||
container?.env?.map((envVar) => ({
|
container?.env?.map((envVar) => ({
|
||||||
key: envVar?.name,
|
...envVar,
|
||||||
fieldPath: envVar?.valueFrom?.fieldRef?.fieldPath,
|
|
||||||
containerName: container.name,
|
containerName: container.name,
|
||||||
isInitContainer: false,
|
isInitContainer: true,
|
||||||
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];
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
return [...appContainersEnvVars, ...appInitContainersEnvVars];
|
return [...appContainersEnvVars, ...appInitContainersEnvVars];
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,12 +250,7 @@ async function getApplicationsByKind<T extends ApplicationList>(
|
||||||
const { data } = await axios.get<T>(
|
const { data } = await axios.get<T>(
|
||||||
buildUrl(environmentId, namespace, `${appKind}s`)
|
buildUrl(environmentId, namespace, `${appKind}s`)
|
||||||
);
|
);
|
||||||
const items = (data.items || []).map((app) => ({
|
return data.items as T['items'];
|
||||||
...app,
|
|
||||||
kind: appKind,
|
|
||||||
apiVersion: data.apiVersion,
|
|
||||||
}));
|
|
||||||
return items as T['items'];
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw parseAxiosError(e as Error, `Unable to retrieve ${appKind}s`);
|
throw parseAxiosError(e as Error, `Unable to retrieve ${appKind}s`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,7 @@ export async function getNamespacePods(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const items = (data.items || []).map(
|
return data.items;
|
||||||
(pod) =>
|
|
||||||
<Pod>{
|
|
||||||
...pod,
|
|
||||||
kind: 'Pod',
|
|
||||||
apiVersion: data.apiVersion,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return items;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw parseAxiosError(e as Error, 'Unable to retrieve pods');
|
throw parseAxiosError(e as Error, 'Unable to retrieve pods');
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,12 @@ export function getIsConfigMapInUse(
|
||||||
? app?.spec
|
? app?.spec
|
||||||
: app?.spec?.template?.spec;
|
: app?.spec?.template?.spec;
|
||||||
|
|
||||||
const hasEnvVarReference = appSpec?.containers.some((container) => {
|
const hasEnvVarReference = appSpec?.containers.some((container) =>
|
||||||
const valueFromEnv = container.env?.some(
|
container.env?.some(
|
||||||
(envVar) =>
|
(envVar) =>
|
||||||
envVar.valueFrom?.configMapKeyRef?.name === configMap.metadata?.name
|
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(
|
const hasVolumeReference = appSpec?.volumes?.some(
|
||||||
(volume) => volume.configMap?.name === configMap.metadata?.name
|
(volume) => volume.configMap?.name === configMap.metadata?.name
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,16 +11,12 @@ export function getIsSecretInUse(secret: Secret, applications: Application[]) {
|
||||||
? app?.spec
|
? app?.spec
|
||||||
: app?.spec?.template?.spec;
|
: app?.spec?.template?.spec;
|
||||||
|
|
||||||
const hasEnvVarReference = appSpec?.containers.some((container) => {
|
const hasEnvVarReference = appSpec?.containers.some((container) =>
|
||||||
const valueFromEnv = container.env?.some(
|
container.env?.some(
|
||||||
(envVar) =>
|
(envVar) =>
|
||||||
envVar.valueFrom?.secretKeyRef?.name === secret.metadata?.name
|
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(
|
const hasVolumeReference = appSpec?.volumes?.some(
|
||||||
(volume) => volume.secret?.secretName === secret.metadata?.name
|
(volume) => volume.secret?.secretName === secret.metadata?.name
|
||||||
);
|
);
|
||||||
|
|
|
@ -165,8 +165,7 @@ export function gitAuthValidation(
|
||||||
NewCredentialName: string()
|
NewCredentialName: string()
|
||||||
.default('')
|
.default('')
|
||||||
.when(['RepositoryAuthentication', 'SaveCredential'], {
|
.when(['RepositoryAuthentication', 'SaveCredential'], {
|
||||||
is: (RepositoryAuthentication: boolean, SaveCredential: boolean) =>
|
is: true,
|
||||||
RepositoryAuthentication && SaveCredential && !isAuthEdit,
|
|
||||||
then: string()
|
then: string()
|
||||||
.required('Name is required')
|
.required('Name is required')
|
||||||
.test(
|
.test(
|
||||||
|
|
|
@ -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.',
|
'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
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Download } from 'lucide-react';
|
import { Download } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useLocalStorage } from '@/react/hooks/useLocalStorage';
|
|
||||||
|
|
||||||
import { Widget, WidgetBody, WidgetTitle } from '@@/Widget';
|
import { Widget, WidgetBody, WidgetTitle } from '@@/Widget';
|
||||||
import { FormSection } from '@@/form-components/FormSection';
|
import { FormSection } from '@@/form-components/FormSection';
|
||||||
|
@ -11,14 +10,11 @@ import { BackupFileForm } from './BackupFileForm';
|
||||||
import { BackupS3Form } from './BackupS3Form';
|
import { BackupS3Form } from './BackupS3Form';
|
||||||
|
|
||||||
export function BackupSettingsPanel() {
|
export function BackupSettingsPanel() {
|
||||||
const [backupType, setBackupType] = useLocalStorage(
|
const [backupType, setBackupType] = useState(options[0].value);
|
||||||
'settings_backup_type',
|
|
||||||
options[0].value
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget>
|
<Widget>
|
||||||
<WidgetTitle icon={Download} title="Back up Portainer" />
|
<WidgetTitle icon={Download} title="Backup up Portainer" />
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
<div className="form-horizontal">
|
<div className="form-horizontal">
|
||||||
<FormSection title="Backup configuration">
|
<FormSection title="Backup configuration">
|
||||||
|
|
|
@ -20,8 +20,8 @@ import { useUpdateSSLConfigMutation } from '../useUpdateSSLConfigMutation';
|
||||||
import { useSSLSettings } from '../../queries/useSSLSettings';
|
import { useSSLSettings } from '../../queries/useSSLSettings';
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
certFile?: File;
|
certFile: File | null;
|
||||||
keyFile?: File;
|
keyFile: File | null;
|
||||||
forceHTTPS: boolean;
|
forceHTTPS: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ function SSLSettingsPanel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialValues: FormValues = {
|
const initialValues: FormValues = {
|
||||||
certFile: undefined,
|
certFile: null,
|
||||||
keyFile: undefined,
|
keyFile: null,
|
||||||
forceHTTPS: !settingsQuery.data.httpEnabled,
|
forceHTTPS: !settingsQuery.data.httpEnabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ function SSLSettingsPanel() {
|
||||||
validationSchema={validation}
|
validationSchema={validation}
|
||||||
validateOnMount
|
validateOnMount
|
||||||
>
|
>
|
||||||
{({ values, setFieldValue, isValid, errors, dirty }) => (
|
{({ values, setFieldValue, isValid, errors }) => (
|
||||||
<Form className="form-horizontal">
|
<Form className="form-horizontal">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
|
@ -92,6 +92,7 @@ function SSLSettingsPanel() {
|
||||||
errors={errors.certFile}
|
errors={errors.certFile}
|
||||||
>
|
>
|
||||||
<FileUploadField
|
<FileUploadField
|
||||||
|
required={typeof errors.certFile !== 'undefined'}
|
||||||
inputId="ca-cert-field"
|
inputId="ca-cert-field"
|
||||||
name="certFile"
|
name="certFile"
|
||||||
onChange={(file) => setFieldValue('certFile', file)}
|
onChange={(file) => setFieldValue('certFile', file)}
|
||||||
|
@ -106,6 +107,7 @@ function SSLSettingsPanel() {
|
||||||
errors={errors.keyFile}
|
errors={errors.keyFile}
|
||||||
>
|
>
|
||||||
<FileUploadField
|
<FileUploadField
|
||||||
|
required={typeof errors.keyFile !== 'undefined'}
|
||||||
inputId="ca-cert-field"
|
inputId="ca-cert-field"
|
||||||
name="keyFile"
|
name="keyFile"
|
||||||
onChange={(file) => setFieldValue('keyFile', file)}
|
onChange={(file) => setFieldValue('keyFile', file)}
|
||||||
|
@ -117,7 +119,7 @@ function SSLSettingsPanel() {
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
isLoading={mutation.isLoading || reloadingPage}
|
isLoading={mutation.isLoading || reloadingPage}
|
||||||
disabled={!dirty || !isValid}
|
disabled={!isValid}
|
||||||
loadingText={reloadingPage ? 'Reloading' : 'Saving'}
|
loadingText={reloadingPage ? 'Reloading' : 'Saving'}
|
||||||
className="!ml-0"
|
className="!ml-0"
|
||||||
>
|
>
|
||||||
|
@ -133,12 +135,16 @@ function SSLSettingsPanel() {
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleSubmit({ certFile, forceHTTPS, keyFile }: FormValues) {
|
function handleSubmit({ certFile, forceHTTPS, keyFile }: FormValues) {
|
||||||
|
if (!certFile || !keyFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mutation.mutate(
|
mutation.mutate(
|
||||||
{ certFile, httpEnabled: !forceHTTPS, keyFile },
|
{ certFile, httpEnabled: !forceHTTPS, keyFile },
|
||||||
{
|
{
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 10000);
|
setTimeout(resolve, 5000);
|
||||||
});
|
});
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
setReloadingPage(true);
|
setReloadingPage(true);
|
||||||
|
@ -150,13 +156,10 @@ function SSLSettingsPanel() {
|
||||||
|
|
||||||
function validation(): SchemaOf<FormValues> {
|
function validation(): SchemaOf<FormValues> {
|
||||||
return object({
|
return object({
|
||||||
certFile: withFileExtension(file(), [
|
certFile: withFileExtension(file(), ['pem', 'crt', 'cer', 'cert']).required(
|
||||||
'pem',
|
''
|
||||||
'crt',
|
),
|
||||||
'cer',
|
keyFile: withFileExtension(file(), ['pem', 'key']).required(''),
|
||||||
'cert',
|
|
||||||
]).optional(),
|
|
||||||
keyFile: withFileExtension(file(), ['pem', 'key']).optional(),
|
|
||||||
forceHTTPS: bool().required(),
|
forceHTTPS: bool().required(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue