diff --git a/app/assets/css/app.css b/app/assets/css/app.css index b38aaae88..8d946337d 100644 --- a/app/assets/css/app.css +++ b/app/assets/css/app.css @@ -626,88 +626,6 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active { margin-left: 21px; } -.boxselector_wrapper { - display: flex; - flex-flow: row wrap; - margin: 0.5rem; -} - -.boxselector_wrapper > div { - flex: 1; - padding: 0.5rem; -} - -.boxselector_wrapper .boxselector_header { - font-size: 14px; - margin-bottom: 5px; - font-weight: bold; -} - -.boxselector_header .fa, -.fab { - font-weight: normal; -} - -.boxselector_wrapper input[type='radio'] { - display: none; -} - -.boxselector_wrapper input[type='radio']:not(:disabled) ~ label { - cursor: pointer; -} - -.boxselector_wrapper label { - font-weight: normal; - font-size: 12px; - display: block; - background: white; - border: 1px solid #333333; - border-radius: 2px; - padding: 10px 10px 0 10px; - text-align: center; - box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); - position: relative; -} -.boxselector_wrapper label.boxselector_disabled { - background: #cacaca; - border-color: #787878; - color: #787878; - cursor: not-allowed; -} - -.boxselector_wrapper input[type='radio']:checked + label { - background: #337ab7; - color: white; - padding-top: 2rem; - border-color: #337ab7; -} - -.boxselector_wrapper input[type='radio']:checked + label::after { - color: #337ab7; - font-family: 'Font Awesome 5 Free'; - border: 2px solid #337ab7; - content: '\f00c'; - font-size: 16px; - font-weight: bold; - position: absolute; - top: -15px; - left: 50%; - transform: translateX(-50%); - height: 30px; - width: 30px; - line-height: 26px; - text-align: center; - border-radius: 50%; - background: white; - box-shadow: 0 2px 5px -2px rgba(0, 0, 0, 0.25); -} - -@media only screen and (max-width: 700px) { - .boxselector_wrapper { - flex-direction: column; - } -} - .visualizer_container { display: flex; flex-direction: row; diff --git a/app/kubernetes/views/deploy/deploy.html b/app/kubernetes/views/deploy/deploy.html index 69904ae60..9ce051b75 100644 --- a/app/kubernetes/views/deploy/deploy.html +++ b/app/kubernetes/views/deploy/deploy.html @@ -22,110 +22,24 @@ - +
Deployment type
-
-
-
-
- - -
-
- - -
-
-
- + -
Build method
-
-
-
-
- - -
-
- - -
-
-
- - +
Git repository
-
- - You can use the URL of a git repository. - -
-
- -
- -
-
-
- - Specify a reference of the repository using the following syntax: branches with - refs/heads/branch_name or tags with refs/tags/tag_name. If not specified, will use the default HEAD reference normally - the master branch. - -
-
- -
- -
-
+ +
Indicate the path to the yaml file from the root of your repository. @@ -137,41 +51,21 @@
-
-
- - -
-
-
- - If your git account has 2FA enabled, you may receive an - authentication required error when deploying your stack. In this case, you will need to provide a personal-access token instead of your password. - -
-
- -
- -
- -
- -
-
+ -
-
- Web editor -
-
+ +

@@ -196,20 +90,9 @@ official documentation.

-
+ + -
-
- -
-
-
diff --git a/app/kubernetes/views/deploy/deployController.js b/app/kubernetes/views/deploy/deployController.js index 21d754b82..a22329aaa 100644 --- a/app/kubernetes/views/deploy/deployController.js +++ b/app/kubernetes/views/deploy/deployController.js @@ -1,8 +1,9 @@ import angular from 'angular'; import _ from 'lodash-es'; import stripAnsi from 'strip-ansi'; -import { KubernetesDeployManifestTypes, KubernetesDeployBuildMethods, KubernetesDeployRequestMethods } from 'Kubernetes/models/deploy'; +import { KubernetesDeployManifestTypes, KubernetesDeployBuildMethods, KubernetesDeployRequestMethods } from 'Kubernetes/models/deploy'; +import { buildOption } from '@/portainer/components/box-selector'; class KubernetesDeployController { /* @ngInject */ constructor($async, $state, $window, ModalService, Notifications, EndpointProvider, KubernetesResourcePoolService, StackService) { @@ -15,11 +16,37 @@ class KubernetesDeployController { this.KubernetesResourcePoolService = KubernetesResourcePoolService; this.StackService = StackService; + this.deployOptions = [ + buildOption('method_kubernetes', 'fa fa-cubes', 'Kubernetes', 'Kubernetes manifest format', KubernetesDeployManifestTypes.KUBERNETES), + buildOption('method_compose', 'fa fa-docker', 'Compose', 'docker-compose format', KubernetesDeployManifestTypes.COMPOSE), + ]; + + this.methodOptions = [ + buildOption('method_repo', 'fab fa-github', 'Git Repository', 'Use a git repository', KubernetesDeployBuildMethods.GIT), + buildOption('method_editor', 'fa fa-edit', 'Web editor', 'Use our Web editor', KubernetesDeployBuildMethods.WEB_EDITOR), + ]; + + this.state = { + DeployType: KubernetesDeployManifestTypes.KUBERNETES, + BuildMethod: KubernetesDeployBuildMethods.GIT, + tabLogsDisabled: true, + activeTab: 0, + viewReady: false, + isEditorDirty: false, + }; + + this.formValues = {}; + this.ManifestDeployTypes = KubernetesDeployManifestTypes; + this.BuildMethods = KubernetesDeployBuildMethods; + this.endpointId = this.EndpointProvider.endpointID(); + this.onInit = this.onInit.bind(this); this.deployAsync = this.deployAsync.bind(this); - this.editorUpdate = this.editorUpdate.bind(this); - this.editorUpdateAsync = this.editorUpdateAsync.bind(this); + this.onChangeFileContent = this.onChangeFileContent.bind(this); this.getNamespacesAsync = this.getNamespacesAsync.bind(this); + this.onChangeFormValues = this.onChangeFormValues.bind(this); + this.onRepoUrlChange = this.onRepoUrlChange.bind(this); + this.onRepoRefChange = this.onRepoRefChange.bind(this); } disableDeploy() { @@ -33,13 +60,24 @@ class KubernetesDeployController { return isGitFormInvalid || isWebEditorInvalid || _.isEmpty(this.formValues.Namespace) || this.state.actionInProgress; } - async editorUpdateAsync(cm) { - this.formValues.EditorContent = cm.getValue(); - this.state.isEditorDirty = true; + onChangeFormValues(values) { + this.formValues = { + ...this.formValues, + ...values, + }; } - editorUpdate(cm) { - return this.$async(this.editorUpdateAsync, cm); + onRepoUrlChange(value) { + this.onChangeFormValues({ RepositoryURL: value }); + } + + onRepoRefChange(value) { + this.onChangeFormValues({ RepositoryReferenceName: value }); + } + + onChangeFileContent(value) { + this.formValues.EditorContent = value; + this.state.isEditorDirty = true; } displayErrorLog(log) { @@ -120,20 +158,6 @@ class KubernetesDeployController { } } async onInit() { - this.state = { - DeployType: KubernetesDeployManifestTypes.KUBERNETES, - BuildMethod: KubernetesDeployBuildMethods.GIT, - tabLogsDisabled: true, - activeTab: 0, - viewReady: false, - isEditorDirty: false, - }; - - this.formValues = {}; - this.ManifestDeployTypes = KubernetesDeployManifestTypes; - this.BuildMethods = KubernetesDeployBuildMethods; - this.endpointId = this.EndpointProvider.endpointID(); - await this.getNamespaces(); this.state.viewReady = true; diff --git a/app/portainer/components/box-selector/box-selector-item/box-selector-item.html b/app/portainer/components/box-selector/box-selector-item/box-selector-item.html new file mode 100644 index 000000000..398f65563 --- /dev/null +++ b/app/portainer/components/box-selector/box-selector-item/box-selector-item.html @@ -0,0 +1,10 @@ +
+ + +
diff --git a/app/portainer/components/box-selector/box-selector-item/index.js b/app/portainer/components/box-selector/box-selector-item/index.js new file mode 100644 index 000000000..979f91116 --- /dev/null +++ b/app/portainer/components/box-selector/box-selector-item/index.js @@ -0,0 +1,11 @@ +import angular from 'angular'; + +angular.module('portainer.app').component('boxSelectorItem', { + templateUrl: './box-selector-item.html', + bindings: { + radioName: '@', + isChecked: '<', + option: '<', + onChange: '<', + }, +}); diff --git a/app/portainer/components/box-selector/box-selector.controller.js b/app/portainer/components/box-selector/box-selector.controller.js new file mode 100644 index 000000000..3b3c60d49 --- /dev/null +++ b/app/portainer/components/box-selector/box-selector.controller.js @@ -0,0 +1,17 @@ +export default class BoxSelectorController { + constructor() { + this.isChecked = this.isChecked.bind(this); + this.change = this.change.bind(this); + } + + change(value) { + this.ngModel = value; + if (this.onChange) { + this.onChange(value); + } + } + + isChecked(value) { + return this.ngModel === value; + } +} diff --git a/app/portainer/components/box-selector/box-selector.css b/app/portainer/components/box-selector/box-selector.css new file mode 100644 index 000000000..b359cb4df --- /dev/null +++ b/app/portainer/components/box-selector/box-selector.css @@ -0,0 +1,82 @@ +.boxselector_wrapper { + display: flex; + flex-flow: row wrap; + margin: 0.5rem; +} + +.boxselector_wrapper > div, +.boxselector_wrapper box-selector-item { + flex: 1; + padding: 0.5rem; +} + +.boxselector_wrapper .boxselector_header { + font-size: 14px; + margin-bottom: 5px; + font-weight: bold; +} + +.boxselector_header .fa, +.fab { + font-weight: normal; +} + +.boxselector_wrapper input[type='radio'] { + display: none; +} + +.boxselector_wrapper input[type='radio']:not(:disabled) ~ label { + cursor: pointer; +} + +.boxselector_wrapper label { + font-weight: normal; + font-size: 12px; + display: block; + background: white; + border: 1px solid #333333; + border-radius: 2px; + padding: 10px 10px 0 10px; + text-align: center; + box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); + position: relative; +} +.boxselector_wrapper label.boxselector_disabled { + background: #cacaca; + border-color: #787878; + color: #787878; + cursor: not-allowed; +} + +.boxselector_wrapper input[type='radio']:checked + label { + background: #337ab7; + color: white; + padding-top: 2rem; + border-color: #337ab7; +} + +.boxselector_wrapper input[type='radio']:checked + label::after { + color: #337ab7; + font-family: 'Font Awesome 5 Free'; + border: 2px solid #337ab7; + content: '\f00c'; + font-size: 16px; + font-weight: bold; + position: absolute; + top: -15px; + left: 50%; + transform: translateX(-50%); + height: 30px; + width: 30px; + line-height: 26px; + text-align: center; + border-radius: 50%; + background: white; + box-shadow: 0 2px 5px -2px rgba(0, 0, 0, 0.25); +} + +@media only screen and (max-width: 700px) { + .boxselector_wrapper { + flex-direction: column; + } +} diff --git a/app/portainer/components/box-selector/box-selector.html b/app/portainer/components/box-selector/box-selector.html new file mode 100644 index 000000000..78452bb71 --- /dev/null +++ b/app/portainer/components/box-selector/box-selector.html @@ -0,0 +1,13 @@ +
+ +
+
+ +
+
diff --git a/app/portainer/components/box-selector/index.js b/app/portainer/components/box-selector/index.js new file mode 100644 index 000000000..70ae7d768 --- /dev/null +++ b/app/portainer/components/box-selector/index.js @@ -0,0 +1,20 @@ +import angular from 'angular'; + +import './box-selector.css'; + +import controller from './box-selector.controller'; + +angular.module('portainer.app').component('boxSelector', { + templateUrl: './box-selector.html', + controller, + bindings: { + radioName: '@', + ngModel: '=', + options: '<', + onChange: '<', + }, +}); + +export function buildOption(id, icon, label, description, value) { + return { id, icon, label, description, value }; +}