mirror of https://github.com/portainer/portainer
refactor(custom-templates): migrate list view to react [EE-2256] (#11611)
parent
5c6c66f010
commit
94c91035a7
|
@ -1,17 +1,6 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import { r2a } from '@/react-tools/react2angular';
|
export const templatesModule = angular.module(
|
||||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
'portainer.docker.react.components.templates',
|
||||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
[]
|
||||||
import { StackFromCustomTemplateFormWidget } from '@/react/docker/templates/StackFromCustomTemplateFormWidget';
|
).name;
|
||||||
|
|
||||||
export const templatesModule = angular
|
|
||||||
.module('portainer.docker.react.components.templates', [])
|
|
||||||
|
|
||||||
.component(
|
|
||||||
'stackFromCustomTemplateFormWidget',
|
|
||||||
r2a(withUIRouter(withCurrentUser(StackFromCustomTemplateFormWidget)), [
|
|
||||||
'template',
|
|
||||||
'unselect',
|
|
||||||
])
|
|
||||||
).name;
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ angular
|
||||||
url: '/custom',
|
url: '/custom',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
component: 'edgeCustomTemplatesView',
|
component: 'customTemplatesView',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import { r2a } from '@/react-tools/react2angular';
|
export const templatesModule = angular.module(
|
||||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
'portainer.edge.react.views.templates',
|
||||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
[]
|
||||||
import { ListView } from '@/react/edge/templates/custom-templates/ListView';
|
).name;
|
||||||
|
|
||||||
export const templatesModule = angular
|
|
||||||
.module('portainer.edge.react.views.templates', [])
|
|
||||||
.component(
|
|
||||||
'edgeCustomTemplatesView',
|
|
||||||
r2a(withCurrentUser(withUIRouter(ListView)), [])
|
|
||||||
).name;
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import { kubeCustomTemplatesView } from './kube-custom-templates-view';
|
export default angular.module('portainer.kubernetes.custom-templates', []).config(config).name;
|
||||||
|
|
||||||
export default angular.module('portainer.kubernetes.custom-templates', []).config(config).component('kubeCustomTemplatesView', kubeCustomTemplatesView).name;
|
|
||||||
|
|
||||||
function config($stateRegistryProvider) {
|
function config($stateRegistryProvider) {
|
||||||
const templates = {
|
const templates = {
|
||||||
|
@ -17,7 +15,7 @@ function config($stateRegistryProvider) {
|
||||||
|
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
component: 'kubeCustomTemplatesView',
|
component: 'customTemplatesView',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import controller from './kube-custom-templates-view.controller.js';
|
|
||||||
|
|
||||||
export const kubeCustomTemplatesView = {
|
|
||||||
templateUrl: './kube-custom-templates-view.html',
|
|
||||||
controller,
|
|
||||||
};
|
|
|
@ -1,83 +0,0 @@
|
||||||
import _ from 'lodash-es';
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
|
|
||||||
export default class KubeCustomTemplatesViewController {
|
|
||||||
/* @ngInject */
|
|
||||||
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, Notifications) {
|
|
||||||
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, Notifications });
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
selectedTemplate: null,
|
|
||||||
formValidationError: '',
|
|
||||||
actionInProgress: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.currentUser = {
|
|
||||||
isAdmin: false,
|
|
||||||
id: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isEditAllowed = this.isEditAllowed.bind(this);
|
|
||||||
this.getTemplates = this.getTemplates.bind(this);
|
|
||||||
this.validateForm = this.validateForm.bind(this);
|
|
||||||
this.confirmDelete = this.confirmDelete.bind(this);
|
|
||||||
this.selectTemplate = this.selectTemplate.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectTemplate(templateId) {
|
|
||||||
this.$state.go('kubernetes.deploy', { templateId });
|
|
||||||
}
|
|
||||||
|
|
||||||
isEditAllowed(template) {
|
|
||||||
// todo - check if current user is admin/endpointadmin/owner
|
|
||||||
return this.currentUser.isAdmin || this.currentUser.id === template.CreatedByUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTemplates() {
|
|
||||||
return this.$async(async () => {
|
|
||||||
try {
|
|
||||||
const templates = await this.CustomTemplateService.customTemplates(3);
|
|
||||||
this.templates = templates.filter((t) => !t.EdgeTemplate);
|
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Failed loading templates', err, 'Unable to load custom templates');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
validateForm(accessControlData, isAdmin) {
|
|
||||||
this.state.formValidationError = '';
|
|
||||||
const error = this.FormValidator.validateAccessControl(accessControlData, isAdmin);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
this.state.formValidationError = error;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmDelete(templateId) {
|
|
||||||
return this.$async(async () => {
|
|
||||||
const confirmed = await confirmDelete('Are you sure that you want to delete this template?');
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var template = _.find(this.templates, { Id: templateId });
|
|
||||||
await this.CustomTemplateService.remove(templateId);
|
|
||||||
this.Notifications.success('Template successfully deleted', template && template.Title);
|
|
||||||
this.templates = this.templates.filter((template) => template.Id !== templateId);
|
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Failure', err, 'Failed to delete template');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
this.getTemplates();
|
|
||||||
|
|
||||||
this.currentUser.isAdmin = this.Authentication.isAdmin();
|
|
||||||
const user = this.Authentication.getUserDetails();
|
|
||||||
this.currentUser.id = user.ID;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
<page-header title="'Custom Templates'" breadcrumbs="['Custom Templates']" reload="true"></page-header>
|
|
||||||
|
|
||||||
<custom-templates-list
|
|
||||||
templates="$ctrl.templates"
|
|
||||||
on-select="($ctrl.selectTemplate)"
|
|
||||||
on-delete="($ctrl.confirmDelete)"
|
|
||||||
selected-id="$ctrl.state.selectedTemplate.Id"
|
|
||||||
storage-key="'kube-custom-templates'"
|
|
||||||
></custom-templates-list>
|
|
|
@ -4,16 +4,6 @@ import { r2a } from '@/react-tools/react2angular';
|
||||||
import { CustomTemplatesVariablesDefinitionField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField';
|
import { CustomTemplatesVariablesDefinitionField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField';
|
||||||
import { CustomTemplatesVariablesField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField';
|
import { CustomTemplatesVariablesField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField';
|
||||||
import { withControlledInput } from '@/react-tools/withControlledInput';
|
import { withControlledInput } from '@/react-tools/withControlledInput';
|
||||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
|
||||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
|
||||||
import {
|
|
||||||
CommonFields,
|
|
||||||
validation as commonFieldsValidation,
|
|
||||||
} from '@/react/portainer/custom-templates/components/CommonFields';
|
|
||||||
import { PlatformField } from '@/react/portainer/custom-templates/components/PlatformSelector';
|
|
||||||
import { TemplateTypeSelector } from '@/react/portainer/custom-templates/components/TemplateTypeSelector';
|
|
||||||
import { withFormValidation } from '@/react-tools/withFormValidation';
|
|
||||||
import { CustomTemplatesList } from '@/react/portainer/templates/custom-templates/ListView/CustomTemplatesList';
|
|
||||||
|
|
||||||
import { VariablesFieldAngular } from './variables-field';
|
import { VariablesFieldAngular } from './variables-field';
|
||||||
|
|
||||||
|
@ -37,33 +27,6 @@ export const ngModule = angular
|
||||||
'errors',
|
'errors',
|
||||||
'isVariablesNamesFromParent',
|
'isVariablesNamesFromParent',
|
||||||
])
|
])
|
||||||
)
|
|
||||||
.component(
|
|
||||||
'customTemplatesList',
|
|
||||||
r2a(withUIRouter(withCurrentUser(CustomTemplatesList)), [
|
|
||||||
'onDelete',
|
|
||||||
'onSelect',
|
|
||||||
'templates',
|
|
||||||
'selectedId',
|
|
||||||
'templateLinkParams',
|
|
||||||
'storageKey',
|
|
||||||
])
|
|
||||||
)
|
|
||||||
.component(
|
|
||||||
'customTemplatesPlatformSelector',
|
|
||||||
r2a(PlatformField, ['onChange', 'value'])
|
|
||||||
)
|
|
||||||
.component(
|
|
||||||
'customTemplatesTypeSelector',
|
|
||||||
r2a(TemplateTypeSelector, ['onChange', 'value'])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
withFormValidation(
|
|
||||||
ngModule,
|
|
||||||
withControlledInput(CommonFields, { values: 'onChange' }),
|
|
||||||
'customTemplatesCommonFields',
|
|
||||||
[],
|
|
||||||
commonFieldsValidation
|
|
||||||
);
|
|
||||||
|
|
||||||
export const customTemplatesModule = ngModule.name;
|
export const customTemplatesModule = ngModule.name;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||||
import { CreateView } from '@/react/portainer/templates/custom-templates/CreateView';
|
import { CreateView } from '@/react/portainer/templates/custom-templates/CreateView';
|
||||||
import { EditView } from '@/react/portainer/templates/custom-templates/EditView';
|
import { EditView } from '@/react/portainer/templates/custom-templates/EditView';
|
||||||
import { AppTemplatesView } from '@/react/portainer/templates/app-templates/AppTemplatesView';
|
import { AppTemplatesView } from '@/react/portainer/templates/app-templates/AppTemplatesView';
|
||||||
|
import { ListView } from '@/react/portainer/templates/custom-templates/ListView/ListView';
|
||||||
|
|
||||||
export const templatesModule = angular
|
export const templatesModule = angular
|
||||||
.module('portainer.app.react.views.templates', [])
|
.module('portainer.app.react.views.templates', [])
|
||||||
|
@ -13,6 +14,10 @@ export const templatesModule = angular
|
||||||
'appTemplatesView',
|
'appTemplatesView',
|
||||||
r2a(withCurrentUser(withUIRouter(AppTemplatesView)), [])
|
r2a(withCurrentUser(withUIRouter(AppTemplatesView)), [])
|
||||||
)
|
)
|
||||||
|
.component(
|
||||||
|
'customTemplatesView',
|
||||||
|
r2a(withCurrentUser(withUIRouter(ListView)), [])
|
||||||
|
)
|
||||||
.component(
|
.component(
|
||||||
'createCustomTemplatesView',
|
'createCustomTemplatesView',
|
||||||
r2a(withCurrentUser(withUIRouter(CreateView)), [])
|
r2a(withCurrentUser(withUIRouter(CreateView)), [])
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<page-header title="'Custom Templates'" breadcrumbs="['Custom Templates']" reload="true"> </page-header>
|
|
||||||
|
|
||||||
<stack-from-custom-template-form-widget
|
|
||||||
ng-if="$ctrl.state.selectedTemplate"
|
|
||||||
template="$ctrl.state.selectedTemplate"
|
|
||||||
unselect="$ctrl.unselectTemplate"
|
|
||||||
></stack-from-custom-template-form-widget>
|
|
||||||
|
|
||||||
<custom-templates-list
|
|
||||||
templates="$ctrl.templates"
|
|
||||||
on-select="($ctrl.selectTemplate)"
|
|
||||||
on-delete="($ctrl.confirmDelete)"
|
|
||||||
selected-id="$ctrl.state.selectedTemplate.Id"
|
|
||||||
storage-key="'docker-custom-templates'"
|
|
||||||
></custom-templates-list>
|
|
|
@ -1,305 +0,0 @@
|
||||||
import _ from 'lodash-es';
|
|
||||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
|
||||||
import { isTemplateVariablesEnabled, renderTemplate } from '@/react/portainer/custom-templates/components/utils';
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { getVariablesFieldDefaultValues } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField';
|
|
||||||
import { TEMPLATE_NAME_VALIDATION_REGEX } from '@/react/portainer/custom-templates/components/CommonFields';
|
|
||||||
|
|
||||||
class CustomTemplatesViewController {
|
|
||||||
/* @ngInject */
|
|
||||||
constructor(
|
|
||||||
$anchorScroll,
|
|
||||||
$async,
|
|
||||||
$rootScope,
|
|
||||||
$state,
|
|
||||||
Authentication,
|
|
||||||
CustomTemplateService,
|
|
||||||
FormValidator,
|
|
||||||
NetworkService,
|
|
||||||
Notifications,
|
|
||||||
ResourceControlService,
|
|
||||||
StackService,
|
|
||||||
StateManager
|
|
||||||
) {
|
|
||||||
this.$anchorScroll = $anchorScroll;
|
|
||||||
this.$async = $async;
|
|
||||||
this.$rootScope = $rootScope;
|
|
||||||
this.$state = $state;
|
|
||||||
this.Authentication = Authentication;
|
|
||||||
this.CustomTemplateService = CustomTemplateService;
|
|
||||||
this.FormValidator = FormValidator;
|
|
||||||
this.NetworkService = NetworkService;
|
|
||||||
this.Notifications = Notifications;
|
|
||||||
this.ResourceControlService = ResourceControlService;
|
|
||||||
this.StateManager = StateManager;
|
|
||||||
this.StackService = StackService;
|
|
||||||
|
|
||||||
this.isTemplateVariablesEnabled = isTemplateVariablesEnabled;
|
|
||||||
|
|
||||||
this.DOCKER_STANDALONE = 'DOCKER_STANDALONE';
|
|
||||||
this.DOCKER_SWARM_MODE = 'DOCKER_SWARM_MODE';
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
selectedTemplate: null,
|
|
||||||
showAdvancedOptions: false,
|
|
||||||
formValidationError: '',
|
|
||||||
actionInProgress: false,
|
|
||||||
deployable: false,
|
|
||||||
templateNameRegex: TEMPLATE_NAME_VALIDATION_REGEX,
|
|
||||||
templateContent: '',
|
|
||||||
templateLoadFailed: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.currentUser = {
|
|
||||||
isAdmin: false,
|
|
||||||
id: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.formValues = {
|
|
||||||
network: '',
|
|
||||||
name: '',
|
|
||||||
fileContent: '',
|
|
||||||
AccessControlData: new AccessControlFormData(),
|
|
||||||
variables: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getTemplates = this.getTemplates.bind(this);
|
|
||||||
this.getTemplatesAsync = this.getTemplatesAsync.bind(this);
|
|
||||||
this.removeTemplates = this.removeTemplates.bind(this);
|
|
||||||
this.removeTemplatesAsync = this.removeTemplatesAsync.bind(this);
|
|
||||||
this.validateForm = this.validateForm.bind(this);
|
|
||||||
this.createStack = this.createStack.bind(this);
|
|
||||||
this.createStackAsync = this.createStackAsync.bind(this);
|
|
||||||
this.selectTemplate = this.selectTemplate.bind(this);
|
|
||||||
this.selectTemplateAsync = this.selectTemplateAsync.bind(this);
|
|
||||||
this.unselectTemplate = this.unselectTemplate.bind(this);
|
|
||||||
this.unselectTemplateAsync = this.unselectTemplateAsync.bind(this);
|
|
||||||
this.getNetworks = this.getNetworks.bind(this);
|
|
||||||
this.getNetworksAsync = this.getNetworksAsync.bind(this);
|
|
||||||
this.confirmDelete = this.confirmDelete.bind(this);
|
|
||||||
this.confirmDeleteAsync = this.confirmDeleteAsync.bind(this);
|
|
||||||
this.editorUpdate = this.editorUpdate.bind(this);
|
|
||||||
this.isEditAllowed = this.isEditAllowed.bind(this);
|
|
||||||
this.onChangeFormValues = this.onChangeFormValues.bind(this);
|
|
||||||
this.onChangeTemplateVariables = this.onChangeTemplateVariables.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
isEditAllowed(template) {
|
|
||||||
return this.currentUser.isAdmin || this.currentUser.id === template.CreatedByUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTemplates() {
|
|
||||||
return this.$async(this.getTemplatesAsync);
|
|
||||||
}
|
|
||||||
async getTemplatesAsync() {
|
|
||||||
try {
|
|
||||||
const templates = await this.CustomTemplateService.customTemplates([1, 2]);
|
|
||||||
this.templates = templates.filter((t) => !t.EdgeTemplate);
|
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Failed loading templates', err, 'Unable to load custom templates');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeTemplates(templates) {
|
|
||||||
return this.$async(this.removeTemplatesAsync, templates);
|
|
||||||
}
|
|
||||||
async removeTemplatesAsync(templates) {
|
|
||||||
for (let template of templates) {
|
|
||||||
try {
|
|
||||||
await this.CustomTemplateService.remove(template.id);
|
|
||||||
this.Notifications.success('Success', 'Removed template successfully');
|
|
||||||
_.remove(this.templates, template);
|
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Failed removing template', err, 'Unable to remove custom template');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeTemplateVariables(variables) {
|
|
||||||
this.onChangeFormValues({ variables });
|
|
||||||
|
|
||||||
this.renderTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTemplate() {
|
|
||||||
if (!this.isTemplateVariablesEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileContent = renderTemplate(this.state.templateContent, this.formValues.variables, this.state.selectedTemplate.Variables);
|
|
||||||
this.onChangeFormValues({ fileContent });
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeFormValues(values) {
|
|
||||||
this.formValues = {
|
|
||||||
...this.formValues,
|
|
||||||
...values,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
validateForm(accessControlData, isAdmin) {
|
|
||||||
this.state.formValidationError = '';
|
|
||||||
const error = this.FormValidator.validateAccessControl(accessControlData, isAdmin);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
this.state.formValidationError = error;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
createStack() {
|
|
||||||
return this.$async(this.createStackAsync);
|
|
||||||
}
|
|
||||||
async createStackAsync() {
|
|
||||||
const userId = this.currentUser.id;
|
|
||||||
const accessControlData = this.formValues.AccessControlData;
|
|
||||||
|
|
||||||
if (!this.validateForm(accessControlData, this.currentUser.isAdmin)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const stackName = this.formValues.name;
|
|
||||||
|
|
||||||
const endpointId = this.endpoint.Id;
|
|
||||||
|
|
||||||
this.state.actionInProgress = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const file = this.formValues.fileContent;
|
|
||||||
const createAction = this.state.selectedTemplate.Type === 1 ? this.StackService.createSwarmStackFromFileContent : this.StackService.createComposeStackFromFileContent;
|
|
||||||
const { ResourceControl: resourceControl } = await createAction(stackName, file, [], endpointId);
|
|
||||||
await this.ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
|
||||||
this.Notifications.success('Success', 'Stack successfully deployed');
|
|
||||||
this.$state.go('docker.stacks');
|
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Deployment error', err, 'Failed to deploy stack');
|
|
||||||
} finally {
|
|
||||||
this.state.actionInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unselectTemplate() {
|
|
||||||
// wrapping unselect with async to make a digest cycle run between unselect to select
|
|
||||||
return this.$async(this.unselectTemplateAsync);
|
|
||||||
}
|
|
||||||
async unselectTemplateAsync() {
|
|
||||||
this.state.selectedTemplate = null;
|
|
||||||
|
|
||||||
this.formValues = {
|
|
||||||
network: '',
|
|
||||||
name: '',
|
|
||||||
fileContent: '',
|
|
||||||
AccessControlData: new AccessControlFormData(),
|
|
||||||
variables: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
selectTemplate(templateId) {
|
|
||||||
return this.$async(this.selectTemplateAsync, templateId);
|
|
||||||
}
|
|
||||||
async selectTemplateAsync(templateId) {
|
|
||||||
if (this.state.selectedTemplate) {
|
|
||||||
await this.unselectTemplate(this.state.selectedTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
const template = _.find(this.templates, { Id: templateId });
|
|
||||||
|
|
||||||
const isGit = template.GitConfig !== null;
|
|
||||||
this.state.isEditorReadOnly = isGit;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.state.templateContent = this.formValues.fileContent = await this.CustomTemplateService.customTemplateFile(template.Id, template.GitConfig !== null);
|
|
||||||
} catch (err) {
|
|
||||||
this.state.templateLoadFailed = true;
|
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve custom template data');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.formValues.network = _.find(this.availableNetworks, function (o) {
|
|
||||||
return o.Name === 'bridge';
|
|
||||||
});
|
|
||||||
|
|
||||||
this.formValues.name = template.Title ? template.Title : '';
|
|
||||||
this.state.selectedTemplate = template;
|
|
||||||
this.$anchorScroll('view-top');
|
|
||||||
const applicationState = this.StateManager.getState();
|
|
||||||
this.state.deployable = this.isDeployable(applicationState.endpoint, template.Type);
|
|
||||||
|
|
||||||
if (template.Variables && template.Variables.length > 0) {
|
|
||||||
const variables = getVariablesFieldDefaultValues(template.Variables);
|
|
||||||
this.onChangeTemplateVariables(variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
|
|
||||||
getNetworks(provider, apiVersion) {
|
|
||||||
return this.$async(this.getNetworksAsync, provider, apiVersion);
|
|
||||||
}
|
|
||||||
async getNetworksAsync(provider, apiVersion) {
|
|
||||||
try {
|
|
||||||
const networks = await this.NetworkService.networks(
|
|
||||||
provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE',
|
|
||||||
false,
|
|
||||||
provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
|
|
||||||
);
|
|
||||||
this.availableNetworks = networks;
|
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Failure', err, 'Failed to load networks.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmDelete(templateId) {
|
|
||||||
return this.$async(this.confirmDeleteAsync, templateId);
|
|
||||||
}
|
|
||||||
async confirmDeleteAsync(templateId) {
|
|
||||||
const confirmed = await confirmDelete('Are you sure that you want to delete this template?');
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var template = _.find(this.templates, { Id: templateId });
|
|
||||||
await this.CustomTemplateService.remove(templateId);
|
|
||||||
this.Notifications.success('Template successfully deleted', template && template.Title);
|
|
||||||
this.templates = this.templates.filter((template) => template.Id !== templateId);
|
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Failure', err, 'Failed to delete template');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editorUpdate(value) {
|
|
||||||
this.formValues.fileContent = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
isDeployable(endpoint, templateType) {
|
|
||||||
let deployable = false;
|
|
||||||
switch (templateType) {
|
|
||||||
case 1:
|
|
||||||
deployable = endpoint.mode.provider === this.DOCKER_SWARM_MODE;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
deployable = endpoint.mode.provider === this.DOCKER_STANDALONE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return deployable;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
const applicationState = this.StateManager.getState();
|
|
||||||
|
|
||||||
const {
|
|
||||||
endpoint: { mode: endpointMode },
|
|
||||||
apiVersion,
|
|
||||||
} = applicationState;
|
|
||||||
|
|
||||||
this.getTemplates();
|
|
||||||
this.getNetworks(endpointMode.provider, apiVersion);
|
|
||||||
|
|
||||||
this.currentUser.isAdmin = this.Authentication.isAdmin();
|
|
||||||
const user = this.Authentication.getUserDetails();
|
|
||||||
this.currentUser.id = user.ID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CustomTemplatesViewController;
|
|
|
@ -1,9 +0,0 @@
|
||||||
import CustomTemplatesViewController from './customTemplatesViewController.js';
|
|
||||||
|
|
||||||
angular.module('portainer.app').component('customTemplatesView', {
|
|
||||||
templateUrl: './customTemplatesView.html',
|
|
||||||
controller: CustomTemplatesViewController,
|
|
||||||
bindings: {
|
|
||||||
endpoint: '<',
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -25,6 +25,7 @@ export function FormActions({
|
||||||
<FormSection title="Actions">
|
<FormSection title="Actions">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
|
<div className="flex item-center gap-3">
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
className="!ml-0"
|
className="!ml-0"
|
||||||
loadingText={loadingText}
|
loadingText={loadingText}
|
||||||
|
@ -34,10 +35,10 @@ export function FormActions({
|
||||||
>
|
>
|
||||||
{submitLabel}
|
{submitLabel}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { notifySuccess } from '@/portainer/services/notifications';
|
|
||||||
import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates';
|
|
||||||
import { useDeleteTemplateMutation } from '@/react/portainer/templates/custom-templates/queries/useDeleteTemplateMutation';
|
|
||||||
import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
|
|
||||||
import { CustomTemplatesList } from '@/react/portainer/templates/custom-templates/ListView/CustomTemplatesList';
|
|
||||||
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
|
|
||||||
export function ListView() {
|
|
||||||
const templatesQuery = useCustomTemplates({
|
|
||||||
params: {
|
|
||||||
edge: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const deleteMutation = useDeleteTemplateMutation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader title="Custom Templates" breadcrumbs="Custom Templates" />
|
|
||||||
|
|
||||||
<CustomTemplatesList
|
|
||||||
templates={templatesQuery.data}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
templateLinkParams={(template) => ({
|
|
||||||
to: 'edge.stacks.new',
|
|
||||||
params: { templateId: template.Id, templateType: 'custom' },
|
|
||||||
})}
|
|
||||||
storageKey="edge-custom-templates"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
async function handleDelete(templateId: CustomTemplate['Id']) {
|
|
||||||
if (
|
|
||||||
!(await confirmDelete('Are you sure you want to delete this template?'))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteMutation.mutate(templateId, {
|
|
||||||
onSuccess: () => {
|
|
||||||
notifySuccess('Success', 'Template deleted');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export { ListView } from './ListView';
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { transformGitAuthenticationViewModel } from '@/react/portainer/gitops/AuthFieldset/utils';
|
|
||||||
import { GitFormModel } from '@/react/portainer/gitops/types';
|
|
||||||
|
|
||||||
export function toGitRequest(
|
|
||||||
gitConfig: GitFormModel,
|
|
||||||
credentialId: number | undefined
|
|
||||||
): GitFormModel {
|
|
||||||
return {
|
|
||||||
...gitConfig,
|
|
||||||
...getGitAuthValues(gitConfig, credentialId),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGitAuthValues(
|
|
||||||
gitConfig: GitFormModel | undefined,
|
|
||||||
credentialId: number | undefined
|
|
||||||
) {
|
|
||||||
if (!credentialId) {
|
|
||||||
return gitConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
const authModel = transformGitAuthenticationViewModel({
|
|
||||||
...gitConfig,
|
|
||||||
RepositoryGitCredentialID: credentialId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return authModel
|
|
||||||
? {
|
|
||||||
RepositoryAuthentication: true,
|
|
||||||
RepositoryGitCredentialID: authModel.GitCredentialID,
|
|
||||||
RepositoryPassword: authModel.Password,
|
|
||||||
RepositoryUsername: authModel.Username,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
}
|
|
|
@ -15,20 +15,20 @@ import { CustomTemplatesListItem } from './CustomTemplatesListItem';
|
||||||
|
|
||||||
export function CustomTemplatesList({
|
export function CustomTemplatesList({
|
||||||
templates,
|
templates,
|
||||||
onSelect,
|
|
||||||
onDelete,
|
onDelete,
|
||||||
selectedId,
|
selectedId,
|
||||||
templateLinkParams,
|
templateLinkParams,
|
||||||
storageKey,
|
storageKey,
|
||||||
}: {
|
}: {
|
||||||
templates?: CustomTemplate[];
|
templates?: CustomTemplate[];
|
||||||
onSelect?: (template: CustomTemplate['Id']) => void;
|
onDelete: (templateId: CustomTemplate['Id']) => void;
|
||||||
onDelete: (template: CustomTemplate['Id']) => void;
|
|
||||||
selectedId?: CustomTemplate['Id'];
|
selectedId?: CustomTemplate['Id'];
|
||||||
templateLinkParams?: (template: CustomTemplate) => {
|
templateLinkParams?: (template: CustomTemplate) =>
|
||||||
|
| {
|
||||||
to: string;
|
to: string;
|
||||||
params: object;
|
params: object;
|
||||||
};
|
}
|
||||||
|
| undefined;
|
||||||
storageKey: string;
|
storageKey: string;
|
||||||
}) {
|
}) {
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
@ -68,7 +68,6 @@ export function CustomTemplatesList({
|
||||||
<CustomTemplatesListItem
|
<CustomTemplatesListItem
|
||||||
key={template.Id}
|
key={template.Id}
|
||||||
template={template}
|
template={template}
|
||||||
onSelect={onSelect}
|
|
||||||
isSelected={template.Id === selectedId}
|
isSelected={template.Id === selectedId}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
linkParams={templateLinkParams?.(template)}
|
linkParams={templateLinkParams?.(template)}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { notifySuccess } from '@/portainer/services/notifications';
|
||||||
|
import { useParamState } from '@/react/hooks/useParamState';
|
||||||
|
|
||||||
|
import { PageHeader } from '@@/PageHeader';
|
||||||
|
import { confirmDelete } from '@@/modals/confirm';
|
||||||
|
|
||||||
|
import { useCustomTemplates } from '../queries/useCustomTemplates';
|
||||||
|
import { useDeleteTemplateMutation } from '../queries/useDeleteTemplateMutation';
|
||||||
|
import { CustomTemplate } from '../types';
|
||||||
|
|
||||||
|
import { StackFromCustomTemplateFormWidget } from './StackFromCustomTemplateFormWidget';
|
||||||
|
import { CustomTemplatesList } from './CustomTemplatesList';
|
||||||
|
import { useViewParams } from './useViewParams';
|
||||||
|
|
||||||
|
export function ListView() {
|
||||||
|
const { params, getTemplateLinkParams, storageKey, viewType } =
|
||||||
|
useViewParams();
|
||||||
|
|
||||||
|
const templatesQuery = useCustomTemplates({
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
const deleteMutation = useDeleteTemplateMutation();
|
||||||
|
const [selectedTemplateId] = useParamState<number>('template', (param) =>
|
||||||
|
param ? parseInt(param, 10) : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageHeader title="Custom Templates" breadcrumbs="Custom Templates" />
|
||||||
|
|
||||||
|
{viewType === 'docker' && !!selectedTemplateId && (
|
||||||
|
<StackFromCustomTemplateFormWidget templateId={selectedTemplateId} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CustomTemplatesList
|
||||||
|
templates={templatesQuery.data}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
templateLinkParams={getTemplateLinkParams}
|
||||||
|
storageKey={storageKey}
|
||||||
|
selectedId={selectedTemplateId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleDelete(templateId: CustomTemplate['Id']) {
|
||||||
|
if (
|
||||||
|
!(await confirmDelete('Are you sure you want to delete this template?'))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMutation.mutate(templateId, {
|
||||||
|
onSuccess: () => {
|
||||||
|
notifySuccess('Success', 'Template deleted');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,26 +23,24 @@ import {
|
||||||
import { StackType } from '@/react/common/stacks/types';
|
import { StackType } from '@/react/common/stacks/types';
|
||||||
import { toGitFormModel } from '@/react/portainer/gitops/types';
|
import { toGitFormModel } from '@/react/portainer/gitops/types';
|
||||||
import { AdvancedSettings } from '@/react/portainer/templates/app-templates/DeployFormWidget/AdvancedSettings';
|
import { AdvancedSettings } from '@/react/portainer/templates/app-templates/DeployFormWidget/AdvancedSettings';
|
||||||
|
import { useSwarmId } from '@/react/docker/proxy/queries/useSwarm';
|
||||||
|
|
||||||
import { Button } from '@@/buttons';
|
import { Button } from '@@/buttons';
|
||||||
import { FormActions } from '@@/form-components/FormActions';
|
import { FormActions } from '@@/form-components/FormActions';
|
||||||
import { FormSection } from '@@/form-components/FormSection';
|
import { FormSection } from '@@/form-components/FormSection';
|
||||||
import { WebEditorForm } from '@@/WebEditorForm';
|
import { WebEditorForm } from '@@/WebEditorForm';
|
||||||
|
import { Link } from '@@/Link';
|
||||||
import { useSwarmId } from '../../proxy/queries/useSwarm';
|
|
||||||
|
|
||||||
import { FormValues } from './types';
|
import { FormValues } from './types';
|
||||||
import { useValidation } from './useValidation';
|
import { useValidation } from './useValidation';
|
||||||
|
|
||||||
export function DeployForm({
|
export function DeployForm({
|
||||||
template,
|
template,
|
||||||
unselect,
|
|
||||||
templateFile,
|
templateFile,
|
||||||
isDeployable,
|
isDeployable,
|
||||||
}: {
|
}: {
|
||||||
template: CustomTemplate;
|
template: CustomTemplate;
|
||||||
templateFile: string;
|
templateFile: string;
|
||||||
unselect: () => void;
|
|
||||||
isDeployable: boolean;
|
isDeployable: boolean;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -157,7 +155,12 @@ export function DeployForm({
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
type="reset"
|
type="reset"
|
||||||
onClick={() => unselect()}
|
as={Link}
|
||||||
|
props={{
|
||||||
|
to: '.',
|
||||||
|
'data-cy': 'cancel-stack-creation',
|
||||||
|
params: { template: null },
|
||||||
|
}}
|
||||||
color="default"
|
color="default"
|
||||||
data-cy="cancel-stack-creation"
|
data-cy="cancel-stack-creation"
|
||||||
>
|
>
|
|
@ -1,6 +1,7 @@
|
||||||
import { DeployWidget } from '@/react/portainer/templates/components/DeployWidget';
|
import { DeployWidget } from '@/react/portainer/templates/components/DeployWidget';
|
||||||
import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
|
import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
|
||||||
import { useCustomTemplateFile } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplateFile';
|
import { useCustomTemplateFile } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplateFile';
|
||||||
|
import { useCustomTemplate } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplate';
|
||||||
|
|
||||||
import { TextTip } from '@@/Tip/TextTip';
|
import { TextTip } from '@@/Tip/TextTip';
|
||||||
|
|
||||||
|
@ -9,19 +10,21 @@ import { DeployForm } from './DeployForm';
|
||||||
import { TemplateLoadError } from './TemplateLoadError';
|
import { TemplateLoadError } from './TemplateLoadError';
|
||||||
|
|
||||||
export function StackFromCustomTemplateFormWidget({
|
export function StackFromCustomTemplateFormWidget({
|
||||||
template,
|
templateId,
|
||||||
unselect,
|
|
||||||
}: {
|
}: {
|
||||||
template: CustomTemplate;
|
templateId: CustomTemplate['Id'];
|
||||||
unselect: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
const isDeployable = useIsDeployable(template.Type);
|
const templateQuery = useCustomTemplate(templateId);
|
||||||
const fileQuery = useCustomTemplateFile(template.Id);
|
|
||||||
|
|
||||||
if (fileQuery.isLoading) {
|
const isDeployable = useIsDeployable(templateQuery.data?.Type);
|
||||||
|
const fileQuery = useCustomTemplateFile(templateId);
|
||||||
|
|
||||||
|
if (fileQuery.isLoading || !templateQuery.data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const template = templateQuery.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DeployWidget
|
<DeployWidget
|
||||||
logo={template.Logo}
|
logo={template.Logo}
|
||||||
|
@ -44,10 +47,10 @@ export function StackFromCustomTemplateFormWidget({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{fileQuery.isSuccess && isDeployable && (
|
{fileQuery.isSuccess && isDeployable && (
|
||||||
<DeployForm
|
<DeployForm
|
||||||
template={template}
|
template={template}
|
||||||
unselect={unselect}
|
|
||||||
templateFile={fileQuery.data}
|
templateFile={fileQuery.data}
|
||||||
isDeployable={isDeployable}
|
isDeployable={isDeployable}
|
||||||
/>
|
/>
|
|
@ -1,9 +1,8 @@
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
import { StackType } from '@/react/common/stacks/types';
|
import { StackType } from '@/react/common/stacks/types';
|
||||||
|
import { useIsSwarm } from '@/react/docker/proxy/queries/useInfo';
|
||||||
|
|
||||||
import { useIsSwarm } from '../../proxy/queries/useInfo';
|
export function useIsDeployable(type: StackType | undefined) {
|
||||||
|
|
||||||
export function useIsDeployable(type: StackType) {
|
|
||||||
const environmentId = useEnvironmentId();
|
const environmentId = useEnvironmentId();
|
||||||
|
|
||||||
const isSwarm = useIsSwarm(environmentId);
|
const isSwarm = useIsSwarm(environmentId);
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { StackType } from '@/react/common/stacks/types';
|
||||||
|
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
|
import { CustomTemplatesListParams } from '../queries/useCustomTemplates';
|
||||||
|
import { CustomTemplate } from '../types';
|
||||||
|
import { TemplateViewType, useViewType } from '../useViewType';
|
||||||
|
|
||||||
|
export function useViewParams(): {
|
||||||
|
viewType: TemplateViewType;
|
||||||
|
params: CustomTemplatesListParams;
|
||||||
|
getTemplateLinkParams?: (template: CustomTemplate) => {
|
||||||
|
to: string;
|
||||||
|
params: object;
|
||||||
|
};
|
||||||
|
storageKey: string;
|
||||||
|
} {
|
||||||
|
const viewType = useViewType();
|
||||||
|
|
||||||
|
const isAllowedDeploymentKubeQuery = useAuthorizations(
|
||||||
|
'K8sApplicationsAdvancedDeploymentRW'
|
||||||
|
);
|
||||||
|
const isAllowedDeploymentDockerQuery = useAuthorizations([
|
||||||
|
'DockerContainerCreate',
|
||||||
|
'PortainerStackCreate',
|
||||||
|
]);
|
||||||
|
|
||||||
|
switch (viewType) {
|
||||||
|
case 'kube':
|
||||||
|
return {
|
||||||
|
viewType,
|
||||||
|
params: { edge: false, type: [StackType.Kubernetes] },
|
||||||
|
getTemplateLinkParams: isAllowedDeploymentKubeQuery.authorized
|
||||||
|
? (template: CustomTemplate) => ({
|
||||||
|
to: 'kubernetes.deploy',
|
||||||
|
params: { templateId: template.Id, templateType: 'custom' },
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
storageKey: 'kube-custom-templates',
|
||||||
|
};
|
||||||
|
case 'edge':
|
||||||
|
return {
|
||||||
|
viewType,
|
||||||
|
params: { edge: true },
|
||||||
|
getTemplateLinkParams: (template: CustomTemplate) => ({
|
||||||
|
to: 'edge.stacks.new',
|
||||||
|
params: { templateId: template.Id, templateType: 'custom' },
|
||||||
|
}),
|
||||||
|
storageKey: 'edge-custom-templates',
|
||||||
|
};
|
||||||
|
case 'docker':
|
||||||
|
return {
|
||||||
|
viewType,
|
||||||
|
params: {
|
||||||
|
edge: false,
|
||||||
|
type: [StackType.DockerCompose, StackType.DockerSwarm],
|
||||||
|
},
|
||||||
|
getTemplateLinkParams: isAllowedDeploymentDockerQuery.authorized
|
||||||
|
? (template: CustomTemplate) => ({
|
||||||
|
to: '.',
|
||||||
|
params: { template: template.Id },
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
storageKey: 'docker-custom-templates',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
viewType,
|
||||||
|
params: {},
|
||||||
|
getTemplateLinkParams: undefined,
|
||||||
|
storageKey: 'custom-templates',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ type Params = {
|
||||||
edge?: boolean;
|
edge?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { type Params as CustomTemplatesListParams };
|
||||||
|
|
||||||
export function useCustomTemplates<T = Array<CustomTemplate>>({
|
export function useCustomTemplates<T = Array<CustomTemplate>>({
|
||||||
select,
|
select,
|
||||||
params,
|
params,
|
||||||
|
@ -38,6 +40,9 @@ async function getCustomTemplates({ type, edge }: Params = {}) {
|
||||||
type,
|
type,
|
||||||
edge,
|
edge,
|
||||||
},
|
},
|
||||||
|
paramsSerializer: {
|
||||||
|
indexes: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -4,15 +4,19 @@ export type TemplateViewType = 'kube' | 'docker' | 'edge';
|
||||||
|
|
||||||
export function useViewType(): TemplateViewType {
|
export function useViewType(): TemplateViewType {
|
||||||
const {
|
const {
|
||||||
state: { name },
|
state: { name = '' },
|
||||||
} = useCurrentStateAndParams();
|
} = useCurrentStateAndParams();
|
||||||
if (name?.includes('kubernetes')) {
|
if (name.includes('kubernetes')) {
|
||||||
return 'kube';
|
return 'kube';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name?.includes('docker')) {
|
if (name.includes('docker')) {
|
||||||
return 'docker';
|
return 'docker';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name.includes('edge')) {
|
||||||
return 'edge';
|
return 'edge';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown view type: ${name}`);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue