mirror of
https://github.com/portainer/portainer.git
synced 2025-08-08 15:25:22 +02:00
refactor(gitops): migrate git form to react [EE-4849] (#8268)
This commit is contained in:
parent
afe6cd6df0
commit
273a3f9a10
130 changed files with 3194 additions and 1190 deletions
|
@ -11,6 +11,7 @@ import servicesModule from './services';
|
|||
import { reactModule } from './react';
|
||||
import { sidebarModule } from './react/views/sidebar';
|
||||
import environmentsModule from './environments';
|
||||
import { helpersModule } from './helpers';
|
||||
|
||||
async function initAuthentication(authManager, Authentication, $rootScope, $state) {
|
||||
authManager.checkAuthOnRefresh();
|
||||
|
@ -41,6 +42,7 @@ angular
|
|||
reactModule,
|
||||
sidebarModule,
|
||||
environmentsModule,
|
||||
helpersModule,
|
||||
])
|
||||
.config([
|
||||
'$stateRegistryProvider',
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
class GitFormAdditionalFileItemController {
|
||||
onChangePath(value) {
|
||||
const fieldIsInvalid = typeof value === 'undefined';
|
||||
if (fieldIsInvalid) {
|
||||
return;
|
||||
}
|
||||
this.onChange(this.index, { value });
|
||||
}
|
||||
|
||||
removeValue() {
|
||||
this.onChange(this.index);
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.formName = `variableForm${this.index}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default GitFormAdditionalFileItemController;
|
|
@ -1,20 +0,0 @@
|
|||
<ng-form class="env-item form-horizontal" name="$ctrl.{{ $ctrl.formName }}">
|
||||
<div class="form-group col-sm-12">
|
||||
<div class="form-inline mt-3">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon required">path</span>
|
||||
<input type="text" name="name" class="form-control" ng-model="$ctrl.variable" ng-change="$ctrl.onChangePath($ctrl.variable)" required />
|
||||
</div>
|
||||
<button class="btn btn-dangerlight" type="button" ng-click="$ctrl.removeValue()" title="Remove">
|
||||
<pr-icon icon="'trash-2'" size="'md'"></pr-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div ng-show="$ctrl[$ctrl.formName].name.$invalid">
|
||||
<div class="small text-warning">
|
||||
<div ng-messages="$ctrl[$ctrl.formName].name.$error" class="mt-1">
|
||||
<p class="vertical-center" ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Path is required. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
|
@ -1,14 +0,0 @@
|
|||
import controller from './git-form-additional-file-item.controller.js';
|
||||
|
||||
export const gitFormAdditionalFileItem = {
|
||||
templateUrl: './git-form-additional-file-item.html',
|
||||
controller,
|
||||
|
||||
bindings: {
|
||||
variable: '<',
|
||||
index: '<',
|
||||
|
||||
onChange: '<',
|
||||
onRemove: '<',
|
||||
},
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
class GitFormAutoUpdateFieldsetController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.add = this.add.bind(this);
|
||||
this.onChangeVariable = this.onChangeVariable.bind(this);
|
||||
}
|
||||
|
||||
add() {
|
||||
this.model.AdditionalFiles.push('');
|
||||
}
|
||||
|
||||
onChangeVariable(index, variable) {
|
||||
if (!variable) {
|
||||
this.model.AdditionalFiles.splice(index, 1);
|
||||
} else {
|
||||
this.model.AdditionalFiles[index] = variable.value;
|
||||
}
|
||||
|
||||
this.onChange({
|
||||
...this.model,
|
||||
AdditionalFiles: this.model.AdditionalFiles,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default GitFormAutoUpdateFieldsetController;
|
|
@ -1,18 +0,0 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12 p-0">
|
||||
<div class="col-sm-3 col-lg-2">
|
||||
<label class="control-label text-left">Additional paths</label>
|
||||
</div>
|
||||
<div class="col-sm-9 pt-1">
|
||||
<span class="label label-default interactive vertical-center" ng-click="$ctrl.add()"> <pr-icon icon="'plus'" size="'sm'" mode="'alt'"></pr-icon> <span>add file</span> </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-inline">
|
||||
<git-form-additional-file-item
|
||||
ng-repeat="variable in $ctrl.model.AdditionalFiles track by $index"
|
||||
variable="variable"
|
||||
index="$index"
|
||||
on-change="($ctrl.onChangeVariable)"
|
||||
></git-form-additional-file-item>
|
||||
</div>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
import controller from './git-form-additional-files-panel.controller.js';
|
||||
|
||||
export const gitFormAdditionalFilesPanel = {
|
||||
templateUrl: './git-form-additional-files-panel.html',
|
||||
controller,
|
||||
bindings: {
|
||||
model: '<',
|
||||
onChange: '<',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,97 @@
|
|||
import { IFormController } from 'angular';
|
||||
import { FormikErrors } from 'formik';
|
||||
|
||||
import { notifyError } from '@/portainer/services/notifications';
|
||||
import { IAuthenticationService } from '@/portainer/services/types';
|
||||
import { GitAuthModel } from '@/react/portainer/gitops/types';
|
||||
import { gitAuthValidation } from '@/react/portainer/gitops/AuthFieldset';
|
||||
import { getGitCredentials } from '@/portainer/views/account/git-credential/gitCredential.service';
|
||||
import { GitCredential } from '@/portainer/views/account/git-credential/types';
|
||||
|
||||
import { validateForm } from '@@/form-components/validate-form';
|
||||
|
||||
export default class GitFormAuthFieldsetController {
|
||||
errors?: FormikErrors<GitAuthModel> = {};
|
||||
|
||||
$async: <T>(fn: () => Promise<T>) => Promise<T>;
|
||||
|
||||
gitFormAuthFieldset?: IFormController;
|
||||
|
||||
gitCredentials: Array<GitCredential> = [];
|
||||
|
||||
Authentication: IAuthenticationService;
|
||||
|
||||
value?: GitAuthModel;
|
||||
|
||||
onChange?: (value: GitAuthModel) => void;
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async: <T>(fn: () => Promise<T>) => Promise<T>,
|
||||
Authentication: IAuthenticationService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.Authentication = Authentication;
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.runGitValidation = this.runGitValidation.bind(this);
|
||||
}
|
||||
|
||||
async handleChange(newValues: Partial<GitAuthModel>) {
|
||||
// this should never happen, but just in case
|
||||
if (!this.value) {
|
||||
throw new Error('GitFormController: value is required');
|
||||
}
|
||||
|
||||
const value = {
|
||||
...this.value,
|
||||
...newValues,
|
||||
};
|
||||
this.onChange?.(value);
|
||||
await this.runGitValidation(value);
|
||||
}
|
||||
|
||||
async runGitValidation(value: GitAuthModel) {
|
||||
return this.$async(async () => {
|
||||
this.errors = {};
|
||||
this.gitFormAuthFieldset?.$setValidity(
|
||||
'gitFormAuth',
|
||||
true,
|
||||
this.gitFormAuthFieldset
|
||||
);
|
||||
|
||||
this.errors = await validateForm<GitAuthModel>(
|
||||
() => gitAuthValidation(this.gitCredentials),
|
||||
value
|
||||
);
|
||||
if (this.errors && Object.keys(this.errors).length > 0) {
|
||||
this.gitFormAuthFieldset?.$setValidity(
|
||||
'gitFormAuth',
|
||||
false,
|
||||
this.gitFormAuthFieldset
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
try {
|
||||
this.gitCredentials = await getGitCredentials(
|
||||
this.Authentication.getUserDetails().ID
|
||||
);
|
||||
} catch (err) {
|
||||
notifyError(
|
||||
'Failure',
|
||||
err as Error,
|
||||
'Unable to retrieve user saved git credentials'
|
||||
);
|
||||
}
|
||||
|
||||
// this should never happen, but just in case
|
||||
if (!this.value) {
|
||||
throw new Error('GitFormController: value is required');
|
||||
}
|
||||
|
||||
await this.runGitValidation(this.value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { IComponentOptions } from 'angular';
|
||||
|
||||
import controller from './git-form-auth-fieldset.controller';
|
||||
|
||||
export const gitFormAuthFieldset: IComponentOptions = {
|
||||
controller,
|
||||
template: `
|
||||
<ng-form name="$ctrl.gitFormAuthFieldset">
|
||||
<react-git-form-auth-fieldset
|
||||
value="$ctrl.value"
|
||||
on-change="$ctrl.handleChange"
|
||||
is-explanation-visible="$ctrl.isExplanationVisible"
|
||||
errors="$ctrl.errors">
|
||||
</react-git-form-auth-fieldset>
|
||||
</ng-form>`,
|
||||
bindings: {
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
isExplanationVisible: '<',
|
||||
},
|
||||
};
|
|
@ -1,55 +0,0 @@
|
|||
class GitFormComposeAuthFieldsetController {
|
||||
/* @ngInject */
|
||||
constructor($scope) {
|
||||
Object.assign(this, { $scope });
|
||||
|
||||
this.authValues = {
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.onChangeField = this.onChangeField.bind(this);
|
||||
this.onChangeAuth = this.onChangeAuth.bind(this);
|
||||
this.onChangeUsername = this.onChangeField('RepositoryUsername');
|
||||
this.onChangePassword = this.onChangeField('RepositoryPassword');
|
||||
}
|
||||
|
||||
handleChange(...args) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.onChange(...args);
|
||||
});
|
||||
}
|
||||
|
||||
onChangeField(field) {
|
||||
return (value) => {
|
||||
this.handleChange({
|
||||
...this.model,
|
||||
[field]: value,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
onChangeAuth(auth) {
|
||||
if (!auth) {
|
||||
this.authValues.username = this.model.RepositoryUsername;
|
||||
this.authValues.password = this.model.RepositoryPassword;
|
||||
}
|
||||
|
||||
this.handleChange({
|
||||
...this.model,
|
||||
RepositoryAuthentication: auth,
|
||||
RepositoryUsername: auth ? this.authValues.username : '',
|
||||
RepositoryPassword: auth ? this.authValues.password : '',
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
if (this.model.RepositoryAuthentication) {
|
||||
this.authValues.username = this.model.RepositoryUsername;
|
||||
this.authValues.password = this.model.RepositoryPassword;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GitFormComposeAuthFieldsetController;
|
|
@ -1,11 +0,0 @@
|
|||
.inline-label {
|
||||
display: inline-block;
|
||||
padding: 0 15px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.inline-input {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
width: calc(100% - 235px);
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
checked="$ctrl.model.RepositoryAuthentication"
|
||||
label="'Authentication'"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
name="'authSwitch'"
|
||||
on-change="($ctrl.onChangeAuth)"
|
||||
data-cy="'component-gitAuthToggle'"
|
||||
switch-values="{on:'Yes',off:'No'}"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small mt-1 mb-3" ng-if="$ctrl.model.RepositoryAuthentication && $ctrl.showAuthExplanation">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span class="text-muted">Enabling authentication will store the credentials and it is advisable to use a git service account</span>
|
||||
</div>
|
||||
<div ng-if="$ctrl.model.RepositoryAuthentication" class="row">
|
||||
<div class="form-group">
|
||||
<label for="repository_username" class="col-lg-2 col-sm-3 control-label required text-left"> Username </label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.model.RepositoryUsername"
|
||||
name="repository_username"
|
||||
placeholder="git username"
|
||||
ng-change="$ctrl.onChangeUsername($ctrl.model.RepositoryUsername)"
|
||||
data-cy="component-gitUsernameInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group flex">
|
||||
<label for="repository_password" class="col-lg-2 col-sm-3 control-label !pt-0 text-left">
|
||||
<div class="required"> Personal Access Token </div>
|
||||
<portainer-tooltip message="'Provide a personal access token or password'"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.model.RepositoryPassword"
|
||||
name="repository_password"
|
||||
placeholder="*******"
|
||||
ng-change="$ctrl.onChangePassword($ctrl.model.RepositoryPassword)"
|
||||
ng-required="!$ctrl.isEdit"
|
||||
data-cy="component-gitPasswordInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
import controller from './git-form-auth-fieldset.controller.js';
|
||||
import './git-form-auth-fieldset.css';
|
||||
|
||||
export const gitFormAuthFieldset = {
|
||||
templateUrl: './git-form-auth-fieldset.html',
|
||||
controller,
|
||||
bindings: {
|
||||
model: '<',
|
||||
onChange: '<',
|
||||
showAuthExplanation: '<',
|
||||
isEdit: '<',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,80 @@
|
|||
import { IFormController } from 'angular';
|
||||
import { FormikErrors } from 'formik';
|
||||
|
||||
import { IAuthenticationService } from '@/portainer/services/types';
|
||||
import { AutoUpdateModel } from '@/react/portainer/gitops/types';
|
||||
import { autoUpdateValidation } from '@/react/portainer/gitops/AutoUpdateFieldset/validation';
|
||||
|
||||
import { validateForm } from '@@/form-components/validate-form';
|
||||
|
||||
export default class GitFormAutoUpdateFieldsetController {
|
||||
errors?: FormikErrors<AutoUpdateModel> = {};
|
||||
|
||||
$async: <T>(fn: () => Promise<T>) => Promise<T>;
|
||||
|
||||
gitFormAutoUpdate?: IFormController;
|
||||
|
||||
Authentication: IAuthenticationService;
|
||||
|
||||
value?: AutoUpdateModel;
|
||||
|
||||
onChange?: (value: AutoUpdateModel) => void;
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async: <T>(fn: () => Promise<T>) => Promise<T>,
|
||||
Authentication: IAuthenticationService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.Authentication = Authentication;
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.runGitValidation = this.runGitValidation.bind(this);
|
||||
}
|
||||
|
||||
async handleChange(newValues: Partial<AutoUpdateModel>) {
|
||||
// this should never happen, but just in case
|
||||
if (!this.value) {
|
||||
throw new Error('GitFormController: value is required');
|
||||
}
|
||||
|
||||
const value = {
|
||||
...this.value,
|
||||
...newValues,
|
||||
};
|
||||
this.onChange?.(value);
|
||||
await this.runGitValidation(value);
|
||||
}
|
||||
|
||||
async runGitValidation(value: AutoUpdateModel) {
|
||||
return this.$async(async () => {
|
||||
this.errors = {};
|
||||
this.gitFormAutoUpdate?.$setValidity(
|
||||
'gitFormAuth',
|
||||
true,
|
||||
this.gitFormAutoUpdate
|
||||
);
|
||||
|
||||
this.errors = await validateForm<AutoUpdateModel>(
|
||||
() => autoUpdateValidation(),
|
||||
value
|
||||
);
|
||||
if (this.errors && Object.keys(this.errors).length > 0) {
|
||||
this.gitFormAutoUpdate?.$setValidity(
|
||||
'gitFormAuth',
|
||||
false,
|
||||
this.gitFormAutoUpdate
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
// this should never happen, but just in case
|
||||
if (!this.value) {
|
||||
throw new Error('GitFormController: value is required');
|
||||
}
|
||||
|
||||
await this.runGitValidation(this.value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { IComponentOptions } from 'angular';
|
||||
|
||||
import controller from './git-form-auto-update-fieldset.controller';
|
||||
|
||||
export const gitFormAutoUpdate: IComponentOptions = {
|
||||
template: `<ng-form name="$ctrl.gitFormAutoUpdate">
|
||||
<react-git-form-auto-update-fieldset
|
||||
value="$ctrl.value"
|
||||
on-change="$ctrl.handleChange"
|
||||
environment-type="$ctrl.environmentType"
|
||||
is-force-pull-visible="$ctrl.isForcePullVisible"
|
||||
base-webhook-url="$ctrl.baseWebhookUrl"
|
||||
errors="$ctrl.errors">
|
||||
</react-git-form-auto-update-fieldset>
|
||||
</ng-form>`,
|
||||
bindings: {
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
environmentType: '@',
|
||||
isForcePullVisible: '<',
|
||||
baseWebhookUrl: '@',
|
||||
},
|
||||
controller,
|
||||
};
|
|
@ -1,38 +0,0 @@
|
|||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
class GitFormAutoUpdateFieldsetController {
|
||||
/* @ngInject */
|
||||
constructor($scope, clipboard, StateManager) {
|
||||
Object.assign(this, { $scope, clipboard, StateManager });
|
||||
|
||||
this.onChangeAutoUpdate = this.onChangeField('RepositoryAutomaticUpdates');
|
||||
this.onChangeMechanism = this.onChangeField('RepositoryMechanism');
|
||||
this.onChangeInterval = this.onChangeField('RepositoryFetchInterval');
|
||||
|
||||
this.limitedFeature = FeatureId.FORCE_REDEPLOYMENT;
|
||||
this.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
}
|
||||
|
||||
copyWebhook() {
|
||||
this.clipboard.copyText(this.model.RepositoryWebhookURL);
|
||||
$('#copyNotification').show();
|
||||
$('#copyNotification').fadeOut(2000);
|
||||
}
|
||||
|
||||
onChangeField(field) {
|
||||
return (value) => {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.onChange({
|
||||
...this.model,
|
||||
[field]: value,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.environmentType = this.StateManager.getState().endpoint.mode.provider;
|
||||
}
|
||||
}
|
||||
|
||||
export default GitFormAutoUpdateFieldsetController;
|
|
@ -1,122 +0,0 @@
|
|||
<ng-form name="autoUpdateForm" class="form-group">
|
||||
<div class="small vertical-center mb-2">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span class="text-muted">
|
||||
When enabled, at each polling interval or webhook invocation, if the git repo differs from what was stored locally on the last git pull, the changes are deployed.</span
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
name="'autoUpdate'"
|
||||
checked="$ctrl.model.RepositoryAutomaticUpdates"
|
||||
label="'Automatic updates'"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
on-change="($ctrl.onChangeAutoUpdate)"
|
||||
switch-values="{on:'Yes',off:'No'}"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small vertical-center" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span class="text-muted"
|
||||
>Any changes to this stack or application that have been made locally via Portainer or directly in the cluster will be overwritten by the git repository content, which may
|
||||
cause service interruption.</span
|
||||
>
|
||||
</div>
|
||||
<div class="form-group mt-2" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
|
||||
<label for="repository_mechanism" class="col-lg-2 col-sm-3 control-label text-left"> Mechanism </label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group col-sm-10 input-group-sm">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-light" ng-click="$ctrl.onChangeMechanism($ctrl.model.RepositoryMechanism)" ng-model="$ctrl.model.RepositoryMechanism" uib-btn-radio="'Interval'"
|
||||
>Polling</label
|
||||
>
|
||||
<label class="btn btn-light" ng-click="$ctrl.onChangeMechanism($ctrl.model.RepositoryMechanism)" ng-model="$ctrl.model.RepositoryMechanism" uib-btn-radio="'Webhook'"
|
||||
>Webhook</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.model.RepositoryAutomaticUpdates && $ctrl.model.RepositoryMechanism === 'Webhook'">
|
||||
<label for="repository_mechanism" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Webhook
|
||||
<portainer-tooltip
|
||||
message="$ctrl.environmentType === 'KUBERNETES' ?
|
||||
'See <a href=\'https://docs.portainer.io/user/kubernetes/applications/webhooks\' target=\'_blank\' rel=\'noreferrer\'>Portainer documentation on webhook usage</a>.' :
|
||||
'See <a href=\'https://docs.portainer.io/user/docker/stacks/webhooks\' target=\'_blank\' rel=\'noreferrer\'>Portainer documentation on webhook usage</a>.'"
|
||||
set-html-message="true"
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<span class="text-muted"> {{ $ctrl.model.RepositoryWebhookURL | truncatelr }} </span>
|
||||
<button type="button" class="btn btn-sm btn-light btn-sm space-left vertical-center" ng-if="$ctrl.model.RepositoryWebhookURL" ng-click="$ctrl.copyWebhook()">
|
||||
<pr-icon icon="'copy'" size="'sm'"></pr-icon> Copy link
|
||||
</button>
|
||||
<span>
|
||||
<pr-icon icon="'check'" mode="'success'" style="display: none"></pr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.model.RepositoryAutomaticUpdates && $ctrl.model.RepositoryMechanism === 'Interval'">
|
||||
<label for="repository_fetch_interval" class="col-sm-3 col-lg-2 control-label required text-left">
|
||||
Fetch interval
|
||||
<portainer-tooltip
|
||||
message="'Specify how frequently polling occurs using syntax such as, 5m = 5 minutes, 24h = 24 hours, 6h40m = 6 hours and 40 minutes.'"
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-change="$ctrl.onChangeInterval($ctrl.model.RepositoryFetchInterval)"
|
||||
ng-model="$ctrl.model.RepositoryFetchInterval"
|
||||
name="repository_fetch_interval"
|
||||
placeholder="5m"
|
||||
required
|
||||
interval-format
|
||||
/>
|
||||
<div class="help-group">
|
||||
<div class="form-group col-md-12 pt-1" ng-show="autoUpdateForm.repository_fetch_interval.$touched && autoUpdateForm.repository_fetch_interval.$invalid">
|
||||
<div class="small text-warning">
|
||||
<div ng-messages="autoUpdateForm.repository_fetch_interval.$error">
|
||||
<p ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This field is required.</p>
|
||||
<p ng-message="invalidIntervalFormat"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Please enter a valid time interval.</p>
|
||||
<p ng-message="minimumInterval"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Minimum interval is 1m</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.showForcePullImage && $ctrl.model.RepositoryAutomaticUpdates">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
name="'forcePullImage'"
|
||||
feature-id="$ctrl.stackPullImageFeature"
|
||||
checked="$ctrl.model.ForcePullImage"
|
||||
label="'Re-pull image'"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
tooltip="'If enabled, then when redeploy is triggered via the webhook or polling, if there\'s a newer image with the tag that you\'ve specified (e.g. changeable development builds), it\'s pulled and redeployed. If you haven\'t specified a tag, or have specified \'latest\' as the tag, then the image with the tag \'latest\' is pulled and redeployed.'"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
name="'forceUpdate'"
|
||||
feature-id="$ctrl.limitedFeature"
|
||||
checked="$ctrl.model.RepositoryAutomaticUpdatesForce"
|
||||
label="$ctrl.environmentType === 'KUBERNETES' ? 'Always apply manifest' : 'Force redeployment'"
|
||||
tooltip="$ctrl.environmentType === 'KUBERNETES' ? '<p>If enabled, then when redeploy is triggered via the webhook or polling, kubectl apply is always performed, even if Portainer detects no difference between the git repo and what was stored locally on last git pull.</ p><p>This is useful if you want your git repo to be the source of truth and are fine with changes made directly to resources in the cluster being overwritten.</ p>' : ''"
|
||||
set-tooltip-html-message="true"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
on-change="($ctrl.onChangeAutoUpdateForce)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
|
@ -1,11 +0,0 @@
|
|||
import controller from './git-form-auto-update-fieldset.controller.js';
|
||||
|
||||
export const gitFormAutoUpdateFieldset = {
|
||||
templateUrl: './git-form-auto-update-fieldset.html',
|
||||
controller,
|
||||
bindings: {
|
||||
model: '<',
|
||||
onChange: '<',
|
||||
showForcePullImage: '<',
|
||||
},
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
<ng-form name="pathForm">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span
|
||||
>Indicate the path to the {{ $ctrl.deployMethod == 'compose' ? 'Compose' : 'Manifest' }} file from the root of your repository.
|
||||
<span ng-if="$ctrl.isDockerStandalone">
|
||||
To enable rebuilding of an image if already present on Docker standalone environments, include<code>pull_policy: build</code>in your compose file as per
|
||||
<a href="https://docs.docker.com/compose/compose-file/#pull_policy">Docker documentation</a>.</span
|
||||
></span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stack_repository_path" class="col-lg-2 col-sm-3 control-label required text-left">{{ $ctrl.deployMethod == 'compose' ? 'Compose' : 'Manifest' }} path</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="repoPathField"
|
||||
ng-model="$ctrl.value"
|
||||
ng-change="$ctrl.onChange($ctrl.value)"
|
||||
id="stack_repository_path"
|
||||
placeholder="{{ $ctrl.deployMethod == 'compose' ? 'docker-compose.yml' : 'manifest.yml' }}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
|
@ -1,9 +0,0 @@
|
|||
export const gitFormComposePathField = {
|
||||
templateUrl: './git-form-compose-path-field.html',
|
||||
bindings: {
|
||||
deployMethod: '@',
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
isDockerStandalone: '<',
|
||||
},
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
<div class="form-group" ng-class="$ctrl.className">
|
||||
<div class="col-sm-12">
|
||||
<p>
|
||||
This {{ $ctrl.type }} was deployed from the git repository <code>{{ $ctrl.url }}</code>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Update
|
||||
<code
|
||||
>{{ $ctrl.configFilePath }}<span ng-if="$ctrl.additionalFiles.length > 0">, {{ $ctrl.additionalFiles.join(',') }}</span></code
|
||||
>
|
||||
in git and pull from here to update the {{ $ctrl.type }}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
export const gitFormInfoPanel = {
|
||||
templateUrl: './git-form-info-panel.html',
|
||||
bindings: {
|
||||
url: '<',
|
||||
configFilePath: '<',
|
||||
additionalFiles: '<',
|
||||
className: '@',
|
||||
type: '@',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
import { IComponentOptions, IFormController } from 'angular';
|
||||
|
||||
import { GitFormModel } from '@/react/portainer/gitops/types';
|
||||
import { AsyncService } from '@/portainer/services/types';
|
||||
import { refFieldValidation } from '@/react/portainer/gitops/RefField/RefField';
|
||||
|
||||
import { validateForm } from '@@/form-components/validate-form';
|
||||
|
||||
class GitFormRefFieldController {
|
||||
$async: AsyncService;
|
||||
|
||||
value?: string;
|
||||
|
||||
onChange?: (value: string) => void;
|
||||
|
||||
gitFormRefField?: IFormController;
|
||||
|
||||
error?: string = '';
|
||||
|
||||
model?: GitFormModel;
|
||||
|
||||
stackId?: number = 0;
|
||||
|
||||
/* @ngInject */
|
||||
constructor($async: AsyncService) {
|
||||
this.$async = $async;
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.runValidation = this.runValidation.bind(this);
|
||||
}
|
||||
|
||||
async handleChange(value: string) {
|
||||
return this.$async(async () => {
|
||||
this.onChange?.(value);
|
||||
await this.runValidation(value);
|
||||
});
|
||||
}
|
||||
|
||||
async runValidation(value: string) {
|
||||
return this.$async(async () => {
|
||||
this.error = '';
|
||||
this.gitFormRefField?.$setValidity(
|
||||
'gitFormRefField',
|
||||
true,
|
||||
this.gitFormRefField
|
||||
);
|
||||
|
||||
this.error = await validateForm<string>(
|
||||
() => refFieldValidation(),
|
||||
value
|
||||
);
|
||||
if (this.error) {
|
||||
this.gitFormRefField?.$setValidity(
|
||||
'gitFormRefField',
|
||||
false,
|
||||
this.gitFormRefField
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const gitFormRefField: IComponentOptions = {
|
||||
controller: GitFormRefFieldController,
|
||||
template: `
|
||||
<ng-form name="$ctrl.gitFormRefField">
|
||||
<react-git-form-ref-field
|
||||
is-url-valid="$ctrl.isUrlValid"
|
||||
model="$ctrl.model"
|
||||
value="$ctrl.value"
|
||||
on-change="$ctrl.handleChange"
|
||||
stack-id="$ctrl.stackId"
|
||||
error="$ctrl.error">
|
||||
</react-git-form-ref-field>
|
||||
</ng-form>`,
|
||||
bindings: {
|
||||
isUrlValid: '<',
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
model: '<',
|
||||
stackId: '<',
|
||||
},
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>
|
||||
Specify a reference of the repository using the following syntax: branches with <code>refs/heads/branch_name</code> or tags with <code>refs/tags/tag_name</code>. If not
|
||||
specified, will use the default <code>HEAD</code> reference normally the <code>master</code> branch.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stack_repository_reference_name" class="col-lg-2 col-sm-3 control-label text-left">Repository reference</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.value"
|
||||
id="stack_repository_reference_name"
|
||||
placeholder="refs/heads/master"
|
||||
ng-change="$ctrl.onChange($ctrl.value)"
|
||||
data-cy="component-gitRefInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
export const gitFormRefField = {
|
||||
templateUrl: './git-form-ref-field.html',
|
||||
bindings: {
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
},
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small"> You can use the URL of a git repository. </span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stack_repository_url" class="col-lg-2 col-sm-3 control-label required text-left">Repository URL</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="text"
|
||||
name="repoUrlField"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.value"
|
||||
ng-change="$ctrl.onChange($ctrl.value)"
|
||||
id="stack_repository_url"
|
||||
placeholder="https://github.com/portainer/portainer-compose"
|
||||
data-cy="component-gitUrlInput"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
export const gitFormUrlField = {
|
||||
templateUrl: './git-form-url-field.html',
|
||||
bindings: {
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
},
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
export default class GitFormController {
|
||||
/* @ngInject */
|
||||
constructor(StateManager) {
|
||||
this.StateManager = StateManager;
|
||||
|
||||
this.onChangeField = this.onChangeField.bind(this);
|
||||
this.onChangeURL = this.onChangeField('RepositoryURL');
|
||||
this.onChangeRefName = this.onChangeField('RepositoryReferenceName');
|
||||
this.onChangeComposePath = this.onChangeField('ComposeFilePathInRepository');
|
||||
}
|
||||
|
||||
onChangeField(field) {
|
||||
return (value) => {
|
||||
this.onChange({
|
||||
...this.model,
|
||||
[field]: value,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.deployMethod = this.deployMethod || 'compose';
|
||||
this.isDockerStandalone = !this.hideRebuildInfo && this.StateManager.getState().endpoint.mode.provider === 'DOCKER_STANDALONE';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import { IFormController } from 'angular';
|
||||
import { FormikErrors } from 'formik';
|
||||
|
||||
import { GitFormModel } from '@/react/portainer/gitops/types';
|
||||
import { validateGitForm } from '@/react/portainer/gitops/GitForm';
|
||||
import { notifyError } from '@/portainer/services/notifications';
|
||||
import { IAuthenticationService } from '@/portainer/services/types';
|
||||
import { getGitCredentials } from '@/portainer/views/account/git-credential/gitCredential.service';
|
||||
import { GitCredential } from '@/portainer/views/account/git-credential/types';
|
||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
|
||||
export default class GitFormController {
|
||||
errors?: FormikErrors<GitFormModel>;
|
||||
|
||||
$async: <T>(fn: () => Promise<T>) => Promise<T>;
|
||||
|
||||
gitForm?: IFormController;
|
||||
|
||||
gitCredentials: Array<GitCredential> = [];
|
||||
|
||||
Authentication: IAuthenticationService;
|
||||
|
||||
value?: GitFormModel;
|
||||
|
||||
onChange?: (value: GitFormModel) => void;
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async: <T>(fn: () => Promise<T>) => Promise<T>,
|
||||
Authentication: IAuthenticationService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.Authentication = Authentication;
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.runGitFormValidation = this.runGitFormValidation.bind(this);
|
||||
}
|
||||
|
||||
async handleChange(newValues: Partial<GitFormModel>) {
|
||||
// this should never happen, but just in case
|
||||
if (!this.value) {
|
||||
throw new Error('GitFormController: value is required');
|
||||
}
|
||||
|
||||
const value = {
|
||||
...this.value,
|
||||
...newValues,
|
||||
};
|
||||
this.onChange?.(value);
|
||||
await this.runGitFormValidation(value);
|
||||
}
|
||||
|
||||
async runGitFormValidation(value: GitFormModel) {
|
||||
return this.$async(async () => {
|
||||
this.errors = {};
|
||||
this.gitForm?.$setValidity('gitForm', true, this.gitForm);
|
||||
|
||||
this.errors = await validateGitForm(this.gitCredentials, value);
|
||||
if (this.errors && Object.keys(this.errors).length > 0) {
|
||||
this.gitForm?.$setValidity('gitForm', false, this.gitForm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
if (isBE) {
|
||||
try {
|
||||
this.gitCredentials = await getGitCredentials(
|
||||
this.Authentication.getUserDetails().ID
|
||||
);
|
||||
} catch (err) {
|
||||
notifyError(
|
||||
'Failure',
|
||||
err as Error,
|
||||
'Unable to retrieve user saved git credentials'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// this should never happen, but just in case
|
||||
if (!this.value) {
|
||||
throw new Error('GitFormController: value is required');
|
||||
}
|
||||
|
||||
await this.runGitFormValidation(this.value);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<ng-form name="$ctrl.gitForm">
|
||||
<div class="col-sm-12 form-section-title"> Git repository </div>
|
||||
<git-form-auth-fieldset model="$ctrl.model" on-change="($ctrl.onChange)"></git-form-auth-fieldset>
|
||||
|
||||
<git-form-url-field value="$ctrl.model.RepositoryURL" on-change="($ctrl.onChangeURL)"></git-form-url-field>
|
||||
|
||||
<git-form-ref-field value="$ctrl.model.RepositoryReferenceName" on-change="($ctrl.onChangeRefName)"></git-form-ref-field>
|
||||
|
||||
<git-form-compose-path-field
|
||||
value="$ctrl.model.ComposeFilePathInRepository"
|
||||
on-change="($ctrl.onChangeComposePath)"
|
||||
deploy-method="{{ $ctrl.deployMethod }}"
|
||||
is-docker-standalone="$ctrl.isDockerStandalone"
|
||||
></git-form-compose-path-field>
|
||||
|
||||
<git-form-additional-files-panel ng-if="$ctrl.additionalFile" model="$ctrl.model" on-change="($ctrl.onChange)"></git-form-additional-files-panel>
|
||||
|
||||
<git-form-auto-update-fieldset
|
||||
ng-if="$ctrl.autoUpdate"
|
||||
model="$ctrl.model"
|
||||
on-change="($ctrl.onChange)"
|
||||
show-force-pull-image="$ctrl.showForcePullImage"
|
||||
></git-form-auto-update-fieldset>
|
||||
</ng-form>
|
|
@ -1,16 +0,0 @@
|
|||
import controller from './git-form.controller.js';
|
||||
|
||||
export const gitForm = {
|
||||
templateUrl: './git-form.html',
|
||||
controller,
|
||||
bindings: {
|
||||
deployMethod: '@',
|
||||
model: '<',
|
||||
onChange: '<',
|
||||
additionalFile: '<',
|
||||
autoUpdate: '<',
|
||||
showAuthExplanation: '<',
|
||||
showForcePullImage: '<',
|
||||
hideRebuildInfo: '@',
|
||||
},
|
||||
};
|
33
app/portainer/components/forms/git-form/git-form.ts
Normal file
33
app/portainer/components/forms/git-form/git-form.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { IComponentOptions } from 'angular';
|
||||
|
||||
import controller from './git-form.controller';
|
||||
|
||||
export const gitForm: IComponentOptions = {
|
||||
template: `
|
||||
<ng-form name="$ctrl.gitForm">
|
||||
<react-git-form
|
||||
value="$ctrl.value"
|
||||
on-change="$ctrl.handleChange"
|
||||
is-docker-standalone="$ctrl.isDockerStandalone"
|
||||
deploy-method="$ctrl.deployMethod"
|
||||
is-additional-files-field-visible="$ctrl.isAdditionalFilesFieldVisible"
|
||||
is-auto-update-visible="$ctrl.isAutoUpdateVisible"
|
||||
is-force-pull-visible="$ctrl.isForcePullVisible"
|
||||
is-auth-explanation-visible="$ctrl.isAuthExplanationVisible"
|
||||
base-webhook-url="$ctrl.baseWebhookUrl"
|
||||
errors="$ctrl.errors">
|
||||
</react-git-form>
|
||||
</ng-form>`,
|
||||
bindings: {
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
isDockerStandalone: '<',
|
||||
deployMethod: '@',
|
||||
baseWebhookUrl: '@',
|
||||
isAdditionalFilesFieldVisible: '<',
|
||||
isAutoUpdateVisible: '<',
|
||||
isForcePullVisible: '<',
|
||||
isAuthExplanationVisible: '<',
|
||||
},
|
||||
controller,
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { gitForm } from './git-form';
|
||||
import { gitFormAuthFieldset } from './git-form-auth-fieldset';
|
||||
import { gitFormAdditionalFilesPanel } from './git-form-additional-files-panel';
|
||||
import { gitFormAdditionalFileItem } from './/git-form-additional-files-panel/git-form-additional-file-item';
|
||||
import { gitFormAutoUpdateFieldset } from './git-form-auto-update-fieldset';
|
||||
import { gitFormComposePathField } from './git-form-compose-path-field';
|
||||
import { gitFormRefField } from './git-form-ref-field';
|
||||
import { gitFormUrlField } from './git-form-url-field';
|
||||
import { gitFormInfoPanel } from './git-form-info-panel';
|
||||
|
||||
export default angular
|
||||
.module('portainer.app.components.forms.git', [])
|
||||
.component('gitFormComposePathField', gitFormComposePathField)
|
||||
.component('gitFormRefField', gitFormRefField)
|
||||
.component('gitForm', gitForm)
|
||||
.component('gitFormUrlField', gitFormUrlField)
|
||||
.component('gitFormInfoPanel', gitFormInfoPanel)
|
||||
.component('gitFormAdditionalFilesPanel', gitFormAdditionalFilesPanel)
|
||||
.component('gitFormAdditionalFileItem', gitFormAdditionalFileItem)
|
||||
.component('gitFormAutoUpdateFieldset', gitFormAutoUpdateFieldset)
|
||||
.component('gitFormAuthFieldset', gitFormAuthFieldset).name;
|
13
app/portainer/components/forms/git-form/index.ts
Normal file
13
app/portainer/components/forms/git-form/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { gitForm } from './git-form';
|
||||
import { gitFormAuthFieldset } from './git-form-auth-fieldset';
|
||||
import { gitFormAutoUpdate } from './git-form-auto-update-fieldset';
|
||||
import { gitFormRefField } from './git-form-ref-field';
|
||||
|
||||
export const gitFormModule = angular
|
||||
.module('portainer.app.components.git-form', [])
|
||||
.component('gitForm', gitForm)
|
||||
.component('gitFormAuthFieldset', gitFormAuthFieldset)
|
||||
.component('gitFormAutoUpdateFieldset', gitFormAutoUpdate)
|
||||
.component('gitFormRefField', gitFormRefField).name;
|
|
@ -1,95 +0,0 @@
|
|||
import { ModalType } from '@@/modals';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
class KubernetesAppGitFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, StackService, Notifications) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.StackService = StackService;
|
||||
this.Notifications = Notifications;
|
||||
|
||||
this.state = {
|
||||
saveGitSettingsInProgress: false,
|
||||
redeployInProgress: false,
|
||||
showConfig: true,
|
||||
isEdit: false,
|
||||
};
|
||||
|
||||
this.formValues = {
|
||||
RefName: '',
|
||||
RepositoryAuthentication: false,
|
||||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
};
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onChangeRef = this.onChangeRef.bind(this);
|
||||
}
|
||||
|
||||
onChangeRef(value) {
|
||||
this.onChange({ RefName: value });
|
||||
}
|
||||
|
||||
onChange(values) {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
...values,
|
||||
};
|
||||
}
|
||||
|
||||
async pullAndRedeployApplication() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const confirmed = await confirm({
|
||||
title: 'Are you sure?',
|
||||
message: 'Any changes to this application will be overridden by the definition in git and may cause a service interruption. Do you wish to continue?',
|
||||
confirmButton: buildConfirmButton('Update', 'warning'),
|
||||
modalType: ModalType.Warn,
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.redeployInProgress = true;
|
||||
await this.StackService.updateKubeGit(this.stack.Id, this.stack.EndpointId, this.namespace, this.formValues);
|
||||
this.Notifications.success('Success', 'Pulled and redeployed stack successfully');
|
||||
await this.$state.reload(this.$state.current);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Failed redeploying application');
|
||||
} finally {
|
||||
this.state.redeployInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async saveGitSettings() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state.saveGitSettingsInProgress = true;
|
||||
await this.StackService.updateKubeStack({ EndpointId: this.stack.EndpointId, Id: this.stack.Id }, null, this.formValues);
|
||||
this.Notifications.success('Success', 'Save stack settings successfully');
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to save application settings');
|
||||
} finally {
|
||||
this.state.saveGitSettingsInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isSubmitButtonDisabled() {
|
||||
return this.state.saveGitSettingsInProgress || this.state.redeployInProgress;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.formValues.RefName = this.stack.GitConfig.ReferenceName;
|
||||
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
|
||||
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
|
||||
this.formValues.RepositoryAuthentication = true;
|
||||
this.state.isEdit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesAppGitFormController;
|
|
@ -1,57 +0,0 @@
|
|||
<form name="$ctrl.redeployGitForm">
|
||||
<div class="col-sm-12 form-section-title"> Redeploy from git repository </div>
|
||||
<div class="form-group text-muted">
|
||||
<div class="col-sm-12">
|
||||
<p> Pull the latest manifest from git and redeploy the application. </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<p>
|
||||
<a class="small interactive" ng-click="$ctrl.state.showConfig = !$ctrl.state.showConfig">
|
||||
<pr-icon ng-if="$ctrl.state.showConfig" icon="'minus'" class-name="'mr-1'"></pr-icon>
|
||||
<pr-icon ng-if="!$ctrl.state.showConfig" icon="'plus'" class-name="'mr-1'"></pr-icon>
|
||||
{{ $ctrl.state.showConfig ? 'Hide' : 'Advanced' }} configuration
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<git-form-ref-field ng-if="$ctrl.state.showConfig" value="$ctrl.formValues.RefName" on-change="($ctrl.onChangeRef)"></git-form-ref-field>
|
||||
<git-form-auth-fieldset
|
||||
ng-if="$ctrl.state.showConfig"
|
||||
model="$ctrl.formValues"
|
||||
is-edit="$ctrl.state.isEdit"
|
||||
on-change="($ctrl.onChange)"
|
||||
show-auth-explanation="true"
|
||||
></git-form-auth-fieldset>
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Actions </div>
|
||||
<!-- #Git buttons -->
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
ng-click="$ctrl.pullAndRedeployApplication()"
|
||||
ng-disabled="$ctrl.isSubmitButtonDisabled() || !$ctrl.redeployGitForm.$valid"
|
||||
style="margin-top: 7px; margin-left: 0"
|
||||
button-spinner="ctrl.state.redeployInProgress"
|
||||
analytics-on
|
||||
analytics-category="kubernetes"
|
||||
analytics-event="kubernetes-application-edit-git-pull"
|
||||
>
|
||||
<span ng-show="!$ctrl.state.redeployInProgress">
|
||||
<pr-icon icon="'refresh-cw'" class="!mr-1"></pr-icon>
|
||||
Pull and update application
|
||||
</span>
|
||||
<span ng-show="$ctrl.state.redeployInProgress">In progress...</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
ng-click="$ctrl.saveGitSettings()"
|
||||
ng-disabled="$ctrl.isSubmitButtonDisabled() || !$ctrl.redeployGitForm.$valid"
|
||||
style="margin-top: 7px; margin-left: 0"
|
||||
button-spinner="$ctrl.state.saveGitSettingsInProgress"
|
||||
>
|
||||
<span ng-show="!$ctrl.state.saveGitSettingsInProgress"> Save settings </span>
|
||||
<span ng-show="$ctrl.state.saveGitSettingsInProgress">In progress...</span>
|
||||
</button>
|
||||
</form>
|
|
@ -1,13 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import controller from './kubernetes-app-git-form.controller';
|
||||
|
||||
const kubernetesAppGitForm = {
|
||||
templateUrl: './kubernetes-app-git-form.html',
|
||||
controller,
|
||||
bindings: {
|
||||
namespace: '<',
|
||||
stack: '<',
|
||||
},
|
||||
};
|
||||
|
||||
angular.module('portainer.app').component('kubernetesAppGitForm', kubernetesAppGitForm);
|
|
@ -1,16 +1,17 @@
|
|||
import uuidv4 from 'uuid/v4';
|
||||
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { parseAutoUpdateResponse } from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
|
||||
import { baseStackWebhookUrl } from '@/portainer/helpers/webhookHelper';
|
||||
|
||||
class KubernetesRedeployAppGitFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, StackService, Notifications, WebhookHelper) {
|
||||
constructor($async, $state, StackService, Notifications) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.StackService = StackService;
|
||||
this.Notifications = Notifications;
|
||||
this.WebhookHelper = WebhookHelper;
|
||||
|
||||
this.state = {
|
||||
saveGitSettingsInProgress: false,
|
||||
|
@ -18,6 +19,7 @@ class KubernetesRedeployAppGitFormController {
|
|||
showConfig: false,
|
||||
isEdit: false,
|
||||
hasUnsavedChanges: false,
|
||||
baseWebhookUrl: baseStackWebhookUrl(),
|
||||
};
|
||||
|
||||
this.formValues = {
|
||||
|
@ -26,37 +28,44 @@ class KubernetesRedeployAppGitFormController {
|
|||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
// auto update
|
||||
AutoUpdate: {
|
||||
RepositoryAutomaticUpdates: false,
|
||||
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
|
||||
RepositoryFetchInterval: '5m',
|
||||
RepositoryWebhookURL: '',
|
||||
},
|
||||
AutoUpdate: parseAutoUpdateResponse(),
|
||||
};
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onChangeRef = this.onChangeRef.bind(this);
|
||||
this.onChangeAutoUpdate = this.onChangeAutoUpdate.bind(this);
|
||||
this.onChangeGitAuth = this.onChangeGitAuth.bind(this);
|
||||
}
|
||||
|
||||
onChangeRef(value) {
|
||||
this.onChange({ RefName: value });
|
||||
}
|
||||
|
||||
onChange(values) {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
...values,
|
||||
};
|
||||
this.state.hasUnsavedChanges = angular.toJson(this.savedFormValues) !== angular.toJson(this.formValues);
|
||||
async onChange(values) {
|
||||
return this.$async(async () => {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
...values,
|
||||
};
|
||||
|
||||
this.state.hasUnsavedChanges = angular.toJson(this.savedFormValues) !== angular.toJson(this.formValues);
|
||||
});
|
||||
}
|
||||
|
||||
onChangeAutoUpdate(values) {
|
||||
this.onChange({
|
||||
AutoUpdate: {
|
||||
...this.formValues.AutoUpdate,
|
||||
...values,
|
||||
},
|
||||
onChangeGitAuth(values) {
|
||||
return this.$async(async () => {
|
||||
this.onChange(values);
|
||||
});
|
||||
}
|
||||
|
||||
async onChangeAutoUpdate(values) {
|
||||
return this.$async(async () => {
|
||||
await this.onChange({
|
||||
AutoUpdate: {
|
||||
...this.formValues.AutoUpdate,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -126,22 +135,8 @@ class KubernetesRedeployAppGitFormController {
|
|||
|
||||
$onInit() {
|
||||
this.formValues.RefName = this.stack.GitConfig.ReferenceName;
|
||||
// Init auto update
|
||||
if (this.stack.AutoUpdate && (this.stack.AutoUpdate.Interval || this.stack.AutoUpdate.Webhook)) {
|
||||
this.formValues.AutoUpdate.RepositoryAutomaticUpdates = true;
|
||||
|
||||
if (this.stack.AutoUpdate.Interval) {
|
||||
this.formValues.AutoUpdate.RepositoryMechanism = RepositoryMechanismTypes.INTERVAL;
|
||||
this.formValues.AutoUpdate.RepositoryFetchInterval = this.stack.AutoUpdate.Interval;
|
||||
} else if (this.stack.AutoUpdate.Webhook) {
|
||||
this.formValues.AutoUpdate.RepositoryMechanism = RepositoryMechanismTypes.WEBHOOK;
|
||||
this.formValues.AutoUpdate.RepositoryWebhookURL = this.WebhookHelper.returnStackWebhookUrl(this.stack.AutoUpdate.Webhook);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.formValues.AutoUpdate.RepositoryWebhookURL) {
|
||||
this.formValues.AutoUpdate.RepositoryWebhookURL = this.WebhookHelper.returnStackWebhookUrl(uuidv4());
|
||||
}
|
||||
this.formValues.AutoUpdate = parseAutoUpdateResponse(this.stack.AutoUpdate);
|
||||
|
||||
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
|
||||
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
|
||||
|
|
|
@ -5,7 +5,14 @@
|
|||
<p> Pull the latest manifest from git and redeploy the application. </p>
|
||||
</div>
|
||||
</div>
|
||||
<git-form-auto-update-fieldset model="$ctrl.formValues.AutoUpdate" on-change="($ctrl.onChangeAutoUpdate)"></git-form-auto-update-fieldset>
|
||||
<git-form-auto-update-fieldset
|
||||
value="$ctrl.formValues.AutoUpdate"
|
||||
on-change="($ctrl.onChangeAutoUpdate)"
|
||||
environment-type="KUBERNETES"
|
||||
base-webhook-url="{{ $ctrl.state.baseWebhookUrl }}"
|
||||
></git-form-auto-update-fieldset>
|
||||
<time-window-display></time-window-display>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<p>
|
||||
|
@ -17,14 +24,14 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<git-form-ref-field ng-if="$ctrl.state.showConfig" value="$ctrl.formValues.RefName" on-change="($ctrl.onChangeRef)"></git-form-ref-field>
|
||||
<git-form-auth-fieldset
|
||||
|
||||
<git-form-ref-field
|
||||
ng-if="$ctrl.state.showConfig"
|
||||
value="$ctrl.formValues.RefName"
|
||||
on-change="($ctrl.onChangeRef)"
|
||||
model="$ctrl.formValues"
|
||||
is-edit="$ctrl.state.isEdit"
|
||||
on-change="($ctrl.onChange)"
|
||||
show-auth-explanation="true"
|
||||
></git-form-auth-fieldset>
|
||||
is-url-valid="true"
|
||||
></git-form-ref-field>
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Actions </div>
|
||||
<!-- #Git buttons -->
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import uuidv4 from 'uuid/v4';
|
||||
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { confirmStackUpdate } from '@/react/docker/stacks/common/confirm-stack-update';
|
||||
|
||||
import { parseAutoUpdateResponse } from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
|
||||
import { baseStackWebhookUrl } from '@/portainer/helpers/webhookHelper';
|
||||
|
||||
class StackRedeployGitFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $compile, $scope, StackService, Notifications, WebhookHelper, FormHelper) {
|
||||
constructor($async, $state, $compile, $scope, StackService, ModalService, Notifications, FormHelper) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$compile = $compile;
|
||||
this.$scope = $scope;
|
||||
this.StackService = StackService;
|
||||
this.Notifications = Notifications;
|
||||
this.WebhookHelper = WebhookHelper;
|
||||
this.FormHelper = FormHelper;
|
||||
$scope.stackPullImageFeature = FeatureId.STACK_PULL_IMAGE;
|
||||
this.state = {
|
||||
|
@ -21,6 +22,7 @@ class StackRedeployGitFormController {
|
|||
showConfig: false,
|
||||
isEdit: false,
|
||||
hasUnsavedChanges: false,
|
||||
baseWebhookUrl: baseStackWebhookUrl(),
|
||||
};
|
||||
|
||||
this.formValues = {
|
||||
|
@ -34,12 +36,7 @@ class StackRedeployGitFormController {
|
|||
Prune: false,
|
||||
},
|
||||
// auto update
|
||||
AutoUpdate: {
|
||||
RepositoryAutomaticUpdates: false,
|
||||
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
|
||||
RepositoryFetchInterval: '5m',
|
||||
RepositoryWebhookURL: '',
|
||||
},
|
||||
AutoUpdate: parseAutoUpdateResponse(),
|
||||
};
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
|
@ -47,6 +44,7 @@ class StackRedeployGitFormController {
|
|||
this.onChangeAutoUpdate = this.onChangeAutoUpdate.bind(this);
|
||||
this.onChangeEnvVar = this.onChangeEnvVar.bind(this);
|
||||
this.onChangeOption = this.onChangeOption.bind(this);
|
||||
this.onChangeGitAuth = this.onChangeGitAuth.bind(this);
|
||||
}
|
||||
|
||||
buildAnalyticsProperties() {
|
||||
|
@ -69,27 +67,19 @@ class StackRedeployGitFormController {
|
|||
}
|
||||
|
||||
onChange(values) {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
...values,
|
||||
};
|
||||
|
||||
this.state.hasUnsavedChanges = angular.toJson(this.savedFormValues) !== angular.toJson(this.formValues);
|
||||
return this.$async(async () => {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
...values,
|
||||
};
|
||||
this.state.hasUnsavedChanges = angular.toJson(this.savedFormValues) !== angular.toJson(this.formValues);
|
||||
});
|
||||
}
|
||||
|
||||
onChangeRef(value) {
|
||||
this.onChange({ RefName: value });
|
||||
}
|
||||
|
||||
onChangeAutoUpdate(values) {
|
||||
this.onChange({
|
||||
AutoUpdate: {
|
||||
...this.formValues.AutoUpdate,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onChangeEnvVar(value) {
|
||||
this.onChange({ Env: value });
|
||||
}
|
||||
|
@ -167,29 +157,28 @@ class StackRedeployGitFormController {
|
|||
return isEnabled !== wasEnabled;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
onChangeGitAuth(values) {
|
||||
this.onChange(values);
|
||||
}
|
||||
|
||||
onChangeAutoUpdate(values) {
|
||||
this.onChange({
|
||||
AutoUpdate: {
|
||||
...this.formValues.AutoUpdate,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
this.formValues.RefName = this.model.ReferenceName;
|
||||
this.formValues.Env = this.stack.Env;
|
||||
|
||||
if (this.stack.Option) {
|
||||
this.formValues.Option = this.stack.Option;
|
||||
}
|
||||
|
||||
// Init auto update
|
||||
if (this.stack.AutoUpdate && (this.stack.AutoUpdate.Interval || this.stack.AutoUpdate.Webhook)) {
|
||||
this.formValues.AutoUpdate.RepositoryAutomaticUpdates = true;
|
||||
|
||||
if (this.stack.AutoUpdate.Interval) {
|
||||
this.formValues.AutoUpdate.RepositoryMechanism = RepositoryMechanismTypes.INTERVAL;
|
||||
this.formValues.AutoUpdate.RepositoryFetchInterval = this.stack.AutoUpdate.Interval;
|
||||
} else if (this.stack.AutoUpdate.Webhook) {
|
||||
this.formValues.AutoUpdate.RepositoryMechanism = RepositoryMechanismTypes.WEBHOOK;
|
||||
this.formValues.AutoUpdate.RepositoryWebhookURL = this.WebhookHelper.returnStackWebhookUrl(this.stack.AutoUpdate.Webhook);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.formValues.AutoUpdate.RepositoryWebhookURL) {
|
||||
this.formValues.AutoUpdate.RepositoryWebhookURL = this.WebhookHelper.returnStackWebhookUrl(uuidv4());
|
||||
}
|
||||
this.formValues.AutoUpdate = parseAutoUpdateResponse(this.stack.AutoUpdate);
|
||||
|
||||
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
|
||||
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
<form name="$ctrl.redeployGitForm" class="form-horizontal my-8">
|
||||
<div class="col-sm-12 form-section-title"> Redeploy from git repository </div>
|
||||
<git-form-info-panel
|
||||
class-name="text-muted small"
|
||||
class-name="'text-muted small'"
|
||||
url="$ctrl.model.URL"
|
||||
type="stack"
|
||||
type="'stack'"
|
||||
config-file-path="$ctrl.model.ConfigFilePath"
|
||||
additional-files="$ctrl.stack.AdditionalFiles"
|
||||
></git-form-info-panel>
|
||||
|
||||
<git-form-auto-update-fieldset
|
||||
model="$ctrl.formValues.AutoUpdate"
|
||||
value="$ctrl.formValues.AutoUpdate"
|
||||
on-change="($ctrl.onChangeAutoUpdate)"
|
||||
show-force-pull-image="$ctrl.stack.Type !== 3"
|
||||
environment-type="DOCKER"
|
||||
is-force-pull-visible="$ctrl.stack.Type !== 3"
|
||||
base-webhook-url="{{ $ctrl.state.baseWebhookUrl }}"
|
||||
></git-form-auto-update-fieldset>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
|
@ -24,14 +26,17 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<git-form-ref-field ng-if="$ctrl.state.showConfig" value="$ctrl.formValues.RefName" on-change="($ctrl.onChangeRef)"></git-form-ref-field>
|
||||
<git-form-auth-fieldset
|
||||
|
||||
<git-form-ref-field
|
||||
ng-if="$ctrl.state.showConfig"
|
||||
is-edit="$ctrl.state.isEdit"
|
||||
value="$ctrl.formValues.RefName"
|
||||
on-change="($ctrl.onChangeRef)"
|
||||
model="$ctrl.formValues"
|
||||
on-change="($ctrl.onChange)"
|
||||
show-auth-explanation="true"
|
||||
></git-form-auth-fieldset>
|
||||
is-url-valid="true"
|
||||
stack-id="$ctrl.gitStackId"
|
||||
></git-form-ref-field>
|
||||
<git-form-auth-fieldset ng-if="$ctrl.state.showConfig" value="$ctrl.formValues" on-change="($ctrl.onChangeGitAuth)" is-auth-explanation-visible="true"></git-form-auth-fieldset>
|
||||
|
||||
<environment-variables-panel
|
||||
ng-model="$ctrl.formValues.Env"
|
||||
explanation="These values will be used as substitutions in the stack file"
|
||||
|
@ -39,6 +44,9 @@
|
|||
show-help-message="true"
|
||||
></environment-variables-panel>
|
||||
<option-panel ng-if="$ctrl.stack.Type === 1 && $ctrl.endpoint.apiVersion >= 1.27" ng-model="$ctrl.formValues.Option" on-change="($ctrl.onChangeOption)"></option-panel>
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Actions </div>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
ng-click="$ctrl.submit()"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import formComponentsModule from './form-components';
|
||||
import gitFormModule from './forms/git-form';
|
||||
import porAccessManagementModule from './accessManagement';
|
||||
import widgetModule from './widget';
|
||||
import { boxSelectorModule } from './BoxSelector';
|
||||
|
||||
import { beFeatureIndicator } from './BEFeatureIndicator';
|
||||
import { InformationPanelAngular } from './InformationPanel';
|
||||
import { gitFormModule } from './forms/git-form';
|
||||
|
||||
export default angular
|
||||
.module('portainer.app.components', [boxSelectorModule, widgetModule, gitFormModule, porAccessManagementModule, formComponentsModule])
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
export default class PortainerError extends Error {
|
||||
err?: Error;
|
||||
|
||||
isPortainerError = true;
|
||||
|
||||
constructor(msg: string, err?: Error) {
|
||||
super(msg);
|
||||
this.err = err;
|
||||
}
|
||||
}
|
||||
|
||||
export function isPortainerError(error: unknown): error is PortainerError {
|
||||
return (
|
||||
!!error &&
|
||||
typeof error === 'object' &&
|
||||
'isPortainerError' in error &&
|
||||
(error as PortainerError).isPortainerError
|
||||
);
|
||||
}
|
||||
|
|
7
app/portainer/helpers/index.ts
Normal file
7
app/portainer/helpers/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { WebhookHelperFactory } from './webhookHelper';
|
||||
|
||||
export const helpersModule = angular
|
||||
.module('portainer.app.helpers', [])
|
||||
.factory('WebhookHelper', WebhookHelperFactory).name;
|
|
@ -6,5 +6,5 @@
|
|||
*/
|
||||
export function baseHref() {
|
||||
const base = document.getElementById('base');
|
||||
return base ? base.getAttribute('href') : '/';
|
||||
return base ? base.getAttribute('href') || '/' : '/';
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import { baseHref } from '@/portainer/helpers/pathHelper';
|
||||
|
||||
angular.module('portainer.app').factory('WebhookHelper', [
|
||||
'$location',
|
||||
'API_ENDPOINT_WEBHOOKS',
|
||||
'API_ENDPOINT_STACKS',
|
||||
function WebhookHelperFactory($location, API_ENDPOINT_WEBHOOKS, API_ENDPOINT_STACKS) {
|
||||
'use strict';
|
||||
|
||||
var helper = {};
|
||||
let base;
|
||||
const protocol = $location.protocol().toLowerCase();
|
||||
|
||||
if (protocol !== 'file') {
|
||||
const host = $location.host();
|
||||
const port = $location.port();
|
||||
const displayPort = (protocol === 'http' && port === 80) || (protocol === 'https' && port === 443) ? '' : ':' + port;
|
||||
base = `${protocol}://${host}${displayPort}${baseHref()}`;
|
||||
} else {
|
||||
base = baseHref();
|
||||
}
|
||||
|
||||
helper.returnWebhookUrl = function (token) {
|
||||
return `${base}${API_ENDPOINT_WEBHOOKS}/${token}`;
|
||||
};
|
||||
|
||||
helper.returnStackWebhookUrl = function (token) {
|
||||
return `${base}${API_ENDPOINT_STACKS}/webhooks/${token}`;
|
||||
};
|
||||
|
||||
return helper;
|
||||
},
|
||||
]);
|
43
app/portainer/helpers/webhookHelper.ts
Normal file
43
app/portainer/helpers/webhookHelper.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { API_ENDPOINT_STACKS, API_ENDPOINT_WEBHOOKS } from '@/constants';
|
||||
|
||||
import { baseHref } from './pathHelper';
|
||||
|
||||
const baseUrl = getBaseUrl();
|
||||
|
||||
export function dockerWebhookUrl(token: string) {
|
||||
return `${baseUrl}${API_ENDPOINT_WEBHOOKS}/${token}`;
|
||||
}
|
||||
|
||||
export function baseStackWebhookUrl() {
|
||||
return `${baseUrl}${API_ENDPOINT_STACKS}/webhooks`;
|
||||
}
|
||||
|
||||
export function stackWebhookUrl(token: string) {
|
||||
return `${baseStackWebhookUrl()}/${token}`;
|
||||
}
|
||||
|
||||
/* @ngInject */
|
||||
export function WebhookHelperFactory() {
|
||||
return {
|
||||
returnWebhookUrl: dockerWebhookUrl,
|
||||
getBaseStackWebhookUrl: baseStackWebhookUrl,
|
||||
returnStackWebhookUrl: stackWebhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
function getBaseUrl() {
|
||||
const protocol = window.location.protocol.toLowerCase().replace(':', '');
|
||||
|
||||
if (protocol === 'file') {
|
||||
return baseHref();
|
||||
}
|
||||
|
||||
const { hostname } = window.location;
|
||||
const port = parseInt(window.location.port, 10);
|
||||
const displayPort =
|
||||
(protocol === 'http' && port === 80) ||
|
||||
(protocol === 'https' && port === 443)
|
||||
? ''
|
||||
: `:${port}`;
|
||||
return `${protocol}://${hostname}${displayPort}${baseHref()}`;
|
||||
}
|
|
@ -3,6 +3,8 @@ export function UserViewModel(data) {
|
|||
this.Username = data.Username;
|
||||
this.Role = data.Role;
|
||||
this.ThemeSettings = data.ThemeSettings;
|
||||
this.EndpointAuthorizations = data.EndpointAuthorizations;
|
||||
this.PortainerAuthorizations = data.PortainerAuthorizations;
|
||||
if (data.Role === 1) {
|
||||
this.RoleName = 'administrator';
|
||||
} else {
|
||||
|
@ -10,8 +12,6 @@ export function UserViewModel(data) {
|
|||
}
|
||||
this.AuthenticationMethod = data.AuthenticationMethod;
|
||||
this.Checked = false;
|
||||
this.EndpointAuthorizations = null;
|
||||
this.PortainerAuthorizations = null;
|
||||
}
|
||||
|
||||
export function UserTokenModel(data) {
|
||||
|
|
69
app/portainer/react/components/git-form.ts
Normal file
69
app/portainer/react/components/git-form.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { AutoUpdateFieldset } from '@/react/portainer/gitops/AutoUpdateFieldset';
|
||||
import { GitForm } from '@/react/portainer/gitops/GitForm';
|
||||
import { AuthFieldset } from '@/react/portainer/gitops/AuthFieldset';
|
||||
import { InfoPanel } from '@/react/portainer/gitops/InfoPanel';
|
||||
import { RefField } from '@/react/portainer/gitops/RefField';
|
||||
|
||||
export const gitFormModule = angular
|
||||
.module('portainer.app.components.forms.git', [])
|
||||
.component(
|
||||
'reactGitForm',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(GitForm))), [
|
||||
'value',
|
||||
'onChange',
|
||||
'isDockerStandalone',
|
||||
'deployMethod',
|
||||
'isAdditionalFilesFieldVisible',
|
||||
'isForcePullVisible',
|
||||
'isAuthExplanationVisible',
|
||||
'errors',
|
||||
'baseWebhookUrl',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'gitFormInfoPanel',
|
||||
r2a(InfoPanel, [
|
||||
'additionalFiles',
|
||||
'className',
|
||||
'configFilePath',
|
||||
'type',
|
||||
'url',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'reactGitFormAutoUpdateFieldset',
|
||||
r2a(AutoUpdateFieldset, [
|
||||
'value',
|
||||
'onChange',
|
||||
'environmentType',
|
||||
'isForcePullVisible',
|
||||
'errors',
|
||||
'baseWebhookUrl',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'reactGitFormAuthFieldset',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(AuthFieldset))), [
|
||||
'value',
|
||||
'isExplanationVisible',
|
||||
'onChange',
|
||||
'errors',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'reactGitFormRefField',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(RefField))), [
|
||||
'error',
|
||||
'model',
|
||||
'onChange',
|
||||
'stackId',
|
||||
'value',
|
||||
'isUrlValid',
|
||||
])
|
||||
).name;
|
|
@ -38,13 +38,18 @@ import { PortainerSelect } from '@@/form-components/PortainerSelect';
|
|||
import { Slider } from '@@/form-components/Slider';
|
||||
import { TagButton } from '@@/TagButton';
|
||||
import { BETeaserButton } from '@@/BETeaserButton';
|
||||
import { TimeWindowDisplay } from '@@/TimeWindowDisplay';
|
||||
|
||||
import { fileUploadField } from './file-upload-field';
|
||||
import { switchField } from './switch-field';
|
||||
import { customTemplatesModule } from './custom-templates';
|
||||
import { gitFormModule } from './git-form';
|
||||
|
||||
export const componentsModule = angular
|
||||
.module('portainer.app.react.components', [customTemplatesModule])
|
||||
.module('portainer.app.react.components', [
|
||||
customTemplatesModule,
|
||||
gitFormModule,
|
||||
])
|
||||
.component(
|
||||
'tagSelector',
|
||||
r2a(withUIRouter(withReactQuery(TagSelector)), [
|
||||
|
@ -232,4 +237,8 @@ export const componentsModule = angular
|
|||
'porAccessManagementUsersSelector',
|
||||
r2a(PorAccessManagementUsersSelector, ['onChange', 'options', 'value'])
|
||||
)
|
||||
.component('edgeKeyDisplay', r2a(EdgeKeyDisplay, ['edgeKey'])).name;
|
||||
.component('edgeKeyDisplay', r2a(EdgeKeyDisplay, ['edgeKey']))
|
||||
.component(
|
||||
'timeWindowDisplay',
|
||||
r2a(withReactQuery(withUIRouter(TimeWindowDisplay)), [])
|
||||
).name;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash-es';
|
||||
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
||||
import { transformAutoUpdateViewModel } from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
|
||||
import { StackViewModel, OrphanedStackViewModel } from '../../models/stack';
|
||||
|
||||
angular.module('portainer.app').factory('StackService', [
|
||||
|
@ -278,17 +278,8 @@ angular.module('portainer.app').factory('StackService', [
|
|||
StackFileContent: stackFile,
|
||||
};
|
||||
} else {
|
||||
const autoUpdate = {};
|
||||
if (gitConfig.AutoUpdate && gitConfig.AutoUpdate.RepositoryAutomaticUpdates) {
|
||||
if (gitConfig.AutoUpdate.RepositoryMechanism === RepositoryMechanismTypes.INTERVAL) {
|
||||
autoUpdate.Interval = gitConfig.AutoUpdate.RepositoryFetchInterval;
|
||||
} else if (gitConfig.AutoUpdate.RepositoryMechanism === RepositoryMechanismTypes.WEBHOOK) {
|
||||
autoUpdate.Webhook = gitConfig.AutoUpdate.RepositoryWebhookURL.split('/').reverse()[0];
|
||||
}
|
||||
}
|
||||
|
||||
payload = {
|
||||
AutoUpdate: autoUpdate,
|
||||
AutoUpdate: transformAutoUpdateViewModel(gitConfig.AutoUpdate),
|
||||
RepositoryReferenceName: gitConfig.RefName,
|
||||
RepositoryAuthentication: gitConfig.RepositoryAuthentication,
|
||||
RepositoryUsername: gitConfig.RepositoryUsername,
|
||||
|
@ -465,21 +456,10 @@ angular.module('portainer.app').factory('StackService', [
|
|||
}
|
||||
|
||||
service.updateGitStackSettings = function (id, endpointId, env, gitConfig) {
|
||||
// prepare auto update
|
||||
const autoUpdate = {};
|
||||
|
||||
if (gitConfig.AutoUpdate.RepositoryAutomaticUpdates) {
|
||||
if (gitConfig.AutoUpdate.RepositoryMechanism === RepositoryMechanismTypes.INTERVAL) {
|
||||
autoUpdate.Interval = gitConfig.AutoUpdate.RepositoryFetchInterval;
|
||||
} else if (gitConfig.AutoUpdate.RepositoryMechanism === RepositoryMechanismTypes.WEBHOOK) {
|
||||
autoUpdate.Webhook = gitConfig.AutoUpdate.RepositoryWebhookURL.split('/').reverse()[0];
|
||||
}
|
||||
}
|
||||
|
||||
return Stack.updateGitStackSettings(
|
||||
{ endpointId, id },
|
||||
{
|
||||
AutoUpdate: autoUpdate,
|
||||
AutoUpdate: transformAutoUpdateViewModel(gitConfig.AutoUpdate),
|
||||
Env: env,
|
||||
RepositoryReferenceName: gitConfig.RefName,
|
||||
RepositoryAuthentication: gitConfig.RepositoryAuthentication,
|
||||
|
|
|
@ -3,3 +3,9 @@ import { Environment } from '@/react/portainer/environments/types';
|
|||
export interface StateManager {
|
||||
updateEndpointState(endpoint: Environment): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAuthenticationService {
|
||||
getUserDetails(): { ID: number };
|
||||
}
|
||||
|
||||
export type AsyncService = <T>(fn: () => Promise<T>) => Promise<T>;
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { success as notifySuccess } from '@/portainer/services/notifications';
|
||||
|
||||
import {
|
||||
CreateGitCredentialPayload,
|
||||
GitCredential,
|
||||
UpdateGitCredentialPayload,
|
||||
} from './types';
|
||||
|
||||
export async function createGitCredential(
|
||||
gitCredential: CreateGitCredentialPayload
|
||||
) {
|
||||
try {
|
||||
await axios.post(buildGitUrl(gitCredential.userId), gitCredential);
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Unable to create git credential');
|
||||
}
|
||||
}
|
||||
|
||||
export async function getGitCredentials(userId: number) {
|
||||
try {
|
||||
const { data } = await axios.get<GitCredential[]>(buildGitUrl(userId));
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Unable to get git credentials');
|
||||
}
|
||||
}
|
||||
|
||||
export async function getGitCredential(userId: number, id: number) {
|
||||
try {
|
||||
const { data } = await axios.get<GitCredential>(buildGitUrl(userId, id));
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Unable to get git credential');
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteGitCredential(credential: GitCredential) {
|
||||
try {
|
||||
await axios.delete<GitCredential[]>(
|
||||
buildGitUrl(credential.userId, credential.id)
|
||||
);
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Unable to delete git credential');
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateGitCredential(
|
||||
credential: Partial<UpdateGitCredentialPayload>,
|
||||
userId: number,
|
||||
id: number
|
||||
) {
|
||||
try {
|
||||
const { data } = await axios.put(buildGitUrl(userId, id), credential);
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Unable to update credential');
|
||||
}
|
||||
}
|
||||
|
||||
export function useUpdateGitCredentialMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
({
|
||||
credential,
|
||||
userId,
|
||||
id,
|
||||
}: {
|
||||
credential: UpdateGitCredentialPayload;
|
||||
userId: number;
|
||||
id: number;
|
||||
}) => updateGitCredential(credential, userId, id),
|
||||
{
|
||||
onSuccess: (_, data) => {
|
||||
notifySuccess(
|
||||
'Git credential updated successfully',
|
||||
data.credential.name
|
||||
);
|
||||
return queryClient.invalidateQueries(['gitcredentials']);
|
||||
},
|
||||
meta: {
|
||||
error: {
|
||||
title: 'Failure',
|
||||
message: 'Unable to update credential',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function useDeleteGitCredentialMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(deleteGitCredential, {
|
||||
onSuccess: (_, credential) => {
|
||||
notifySuccess('Git Credential deleted successfully', credential.name);
|
||||
return queryClient.invalidateQueries(['gitcredentials']);
|
||||
},
|
||||
meta: {
|
||||
error: {
|
||||
title: 'Failure',
|
||||
message: 'Unable to delete git credential',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useGitCredentials(userId: number) {
|
||||
return useQuery('gitcredentials', () => getGitCredentials(userId), {
|
||||
staleTime: 20,
|
||||
meta: {
|
||||
error: {
|
||||
title: 'Failure',
|
||||
message: 'Unable to retrieve git credentials',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useGitCredential(userId: number, id: number) {
|
||||
return useQuery(['gitcredentials', id], () => getGitCredential(userId, id), {
|
||||
meta: {
|
||||
error: {
|
||||
title: 'Failure',
|
||||
message: 'Unable to retrieve git credential',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateGitCredentialMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(createGitCredential, {
|
||||
onSuccess: (_, payload) => {
|
||||
notifySuccess('Credentials created successfully', payload.name);
|
||||
return queryClient.invalidateQueries(['gitcredentials']);
|
||||
},
|
||||
meta: {
|
||||
error: {
|
||||
title: 'Failure',
|
||||
message: 'Unable to create credential',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildGitUrl(userId: number, credentialId?: number) {
|
||||
return credentialId
|
||||
? `/users/${userId}/gitcredentials/${credentialId}`
|
||||
: `/users/${userId}/gitcredentials`;
|
||||
}
|
35
app/portainer/views/account/git-credential/types.ts
Normal file
35
app/portainer/views/account/git-credential/types.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import {
|
||||
PaginationTableSettings,
|
||||
SortableTableSettings,
|
||||
} from '@@/datatables/types';
|
||||
|
||||
export interface GitCredentialTableSettings
|
||||
extends SortableTableSettings,
|
||||
PaginationTableSettings {}
|
||||
|
||||
export interface GitCredentialFormValues {
|
||||
name: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface CreateGitCredentialPayload {
|
||||
userId: number;
|
||||
name: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface UpdateGitCredentialPayload {
|
||||
name: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export type GitCredential = {
|
||||
id: number;
|
||||
userId: number;
|
||||
name: string;
|
||||
username: string;
|
||||
creationDate: number;
|
||||
};
|
|
@ -62,7 +62,12 @@
|
|||
</div>
|
||||
<!-- !upload -->
|
||||
<!-- repository -->
|
||||
<git-form ng-if="$ctrl.state.Method === 'repository'" model="$ctrl.formValues" on-change="($ctrl.handleChange)"></git-form>
|
||||
<git-form
|
||||
ng-if="$ctrl.state.Method === 'repository'"
|
||||
value="$ctrl.formValues"
|
||||
on-change="($ctrl.handleChange)"
|
||||
is-docker-standalone="$ctrl.isDockerStandalone"
|
||||
></git-form>
|
||||
|
||||
<div class="form-group" ng-if="!$ctrl.state.isTemplateValid">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -206,6 +206,7 @@ class CreateCustomTemplateViewController {
|
|||
this.state.endpointMode = applicationState.endpoint.mode;
|
||||
let stackType = 0;
|
||||
if (this.state.endpointMode.provider === 'DOCKER_STANDALONE') {
|
||||
this.isDockerStandalone = true;
|
||||
stackType = 2;
|
||||
} else if (this.state.endpointMode.provider === 'DOCKER_SWARM_MODE') {
|
||||
stackType = 1;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import angular from 'angular';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
|
||||
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { STACK_NAME_VALIDATION_REGEX } from '@/constants';
|
||||
|
@ -9,6 +8,8 @@ import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
|||
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
|
||||
import { editor, upload, git, customTemplate } from '@@/BoxSelector/common-options/build-methods';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { parseAutoUpdateResponse, transformAutoUpdateViewModel } from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
|
||||
import { baseStackWebhookUrl } from '@/portainer/helpers/webhookHelper';
|
||||
|
||||
angular
|
||||
.module('portainer.app')
|
||||
|
@ -29,8 +30,6 @@ angular
|
|||
ContainerHelper,
|
||||
CustomTemplateService,
|
||||
ContainerService,
|
||||
WebhookHelper,
|
||||
clipboard,
|
||||
endpoint
|
||||
) {
|
||||
$scope.onChangeTemplateId = onChangeTemplateId;
|
||||
|
@ -55,11 +54,9 @@ angular
|
|||
AdditionalFiles: [],
|
||||
ComposeFilePathInRepository: 'docker-compose.yml',
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
RepositoryAutomaticUpdates: false,
|
||||
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
|
||||
RepositoryFetchInterval: '5m',
|
||||
RepositoryWebhookURL: WebhookHelper.returnStackWebhookUrl(uuidv4()),
|
||||
EnableWebhook: false,
|
||||
Variables: {},
|
||||
AutoUpdate: parseAutoUpdateResponse(),
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
|
@ -72,6 +69,7 @@ angular
|
|||
isEditorDirty: false,
|
||||
selectedTemplate: null,
|
||||
selectedTemplateId: null,
|
||||
baseWebhookUrl: baseStackWebhookUrl(),
|
||||
};
|
||||
|
||||
$window.onbeforeunload = () => {
|
||||
|
@ -99,14 +97,6 @@ angular
|
|||
});
|
||||
};
|
||||
|
||||
$scope.addAdditionalFiles = function () {
|
||||
$scope.formValues.AdditionalFiles.push('');
|
||||
};
|
||||
|
||||
$scope.removeAdditionalFiles = function (index) {
|
||||
$scope.formValues.AdditionalFiles.splice(index, 1);
|
||||
};
|
||||
|
||||
function buildAnalyticsProperties() {
|
||||
const metadata = { type: methodLabel($scope.state.Method) };
|
||||
|
||||
|
@ -163,7 +153,6 @@ angular
|
|||
function createSwarmStack(name, method) {
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
const endpointId = +$state.params.endpointId;
|
||||
|
||||
if (method === 'template' || method === 'editor') {
|
||||
var stackFileContent = $scope.formValues.StackFileContent;
|
||||
return StackService.createSwarmStackFromFileContent(name, stackFileContent, env, endpointId);
|
||||
|
@ -183,25 +172,13 @@ angular
|
|||
RepositoryAuthentication: $scope.formValues.RepositoryAuthentication,
|
||||
RepositoryUsername: $scope.formValues.RepositoryUsername,
|
||||
RepositoryPassword: $scope.formValues.RepositoryPassword,
|
||||
AutoUpdate: transformAutoUpdateViewModel($scope.formValues.AutoUpdate),
|
||||
};
|
||||
|
||||
getAutoUpdatesProperty(repositoryOptions);
|
||||
|
||||
return StackService.createSwarmStackFromGitRepository(name, repositoryOptions, env, endpointId);
|
||||
}
|
||||
}
|
||||
|
||||
function getAutoUpdatesProperty(repositoryOptions) {
|
||||
if ($scope.formValues.RepositoryAutomaticUpdates) {
|
||||
repositoryOptions.AutoUpdate = {};
|
||||
if ($scope.formValues.RepositoryMechanism === RepositoryMechanismTypes.INTERVAL) {
|
||||
repositoryOptions.AutoUpdate.Interval = $scope.formValues.RepositoryFetchInterval;
|
||||
} else if ($scope.formValues.RepositoryMechanism === RepositoryMechanismTypes.WEBHOOK) {
|
||||
repositoryOptions.AutoUpdate.Webhook = $scope.formValues.RepositoryWebhookURL.split('/').reverse()[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createComposeStack(name, method) {
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
const endpointId = +$state.params.endpointId;
|
||||
|
@ -221,20 +198,13 @@ angular
|
|||
RepositoryAuthentication: $scope.formValues.RepositoryAuthentication,
|
||||
RepositoryUsername: $scope.formValues.RepositoryUsername,
|
||||
RepositoryPassword: $scope.formValues.RepositoryPassword,
|
||||
AutoUpdate: transformAutoUpdateViewModel($scope.formValues.AutoUpdate),
|
||||
};
|
||||
|
||||
getAutoUpdatesProperty(repositoryOptions);
|
||||
|
||||
return StackService.createComposeStackFromGitRepository(name, repositoryOptions, env, endpointId);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.copyWebhook = function () {
|
||||
clipboard.copyText($scope.formValues.RepositoryWebhookURL);
|
||||
$('#copyNotification').show();
|
||||
$('#copyNotification').fadeOut(2000);
|
||||
};
|
||||
|
||||
$scope.handleEnvVarChange = handleEnvVarChange;
|
||||
function handleEnvVarChange(value) {
|
||||
$scope.formValues.Env = value;
|
||||
|
@ -348,6 +318,7 @@ angular
|
|||
async function initView() {
|
||||
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||
$scope.state.StackType = 2;
|
||||
$scope.isDockerStandalone = endpointMode.provider === 'DOCKER_STANDALONE';
|
||||
if (endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER') {
|
||||
$scope.state.StackType = 1;
|
||||
}
|
||||
|
@ -369,11 +340,13 @@ angular
|
|||
|
||||
initView();
|
||||
|
||||
function onChangeFormValues(values) {
|
||||
$scope.formValues = {
|
||||
...$scope.formValues,
|
||||
...values,
|
||||
};
|
||||
function onChangeFormValues(newValues) {
|
||||
return $async(async () => {
|
||||
$scope.formValues = {
|
||||
...$scope.formValues,
|
||||
...newValues,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -79,14 +79,14 @@
|
|||
<!-- !upload -->
|
||||
<git-form
|
||||
ng-if="state.Method === 'repository'"
|
||||
model="formValues"
|
||||
value="formValues"
|
||||
on-change="(onChangeFormValues)"
|
||||
additional-file="true"
|
||||
auto-update="true"
|
||||
show-force-pull-image="true"
|
||||
show-auth-explanation="true"
|
||||
path-text-title="Compose path"
|
||||
path-placeholder="docker-compose.yml"
|
||||
is-docker-standalone="isDockerStandalone"
|
||||
is-additional-files-field-visible="true"
|
||||
is-auto-update-visible="true"
|
||||
is-auth-explanation-visible="true"
|
||||
is-force-pull-visible="true"
|
||||
base-webhook-url="{{ state.baseWebhookUrl }}"
|
||||
></git-form>
|
||||
|
||||
<div ng-show="state.Method === 'template'">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue