Compare commits
38 Commits
release/2.
...
2.15.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
103fbbfe6e | ||
|
|
c8044cc125 | ||
|
|
154f2ed189 | ||
|
|
1a2591f0cf | ||
|
|
dacd188fa0 | ||
|
|
4be094cb29 | ||
|
|
2c9328d765 | ||
|
|
ede9bc6111 | ||
|
|
2abb6650ae | ||
|
|
cc73724351 | ||
|
|
6fd5ebbf55 | ||
|
|
aad0b139fc | ||
|
|
a8157caa67 | ||
|
|
f69a6129e3 | ||
|
|
e7d0bd6b65 | ||
|
|
956a6751a2 | ||
|
|
de6ef0de83 | ||
|
|
8f8e89af3e | ||
|
|
9d45a25e99 | ||
|
|
c471b23bc5 | ||
|
|
391d53cdc2 | ||
|
|
3fe8697159 | ||
|
|
4dabe333f9 | ||
|
|
05e3634594 | ||
|
|
de3c75e701 | ||
|
|
de5dc93b5f | ||
|
|
39896a6eaf | ||
|
|
8b6bd59003 | ||
|
|
750a37b861 | ||
|
|
8a6593c7f5 | ||
|
|
6efd3c6a57 | ||
|
|
8b21023c9d | ||
|
|
0aebe38c31 | ||
|
|
28c4b333ce | ||
|
|
e4c7561dfc | ||
|
|
750bb9cf87 | ||
|
|
c5f5269366 | ||
|
|
b9b8d78fcc |
@@ -5,17 +5,17 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
netUrl "net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/url"
|
||||
)
|
||||
|
||||
// GetAgentVersionAndPlatform returns the agent version and platform
|
||||
//
|
||||
// it sends a ping to the agent and parses the version and platform from the headers
|
||||
func GetAgentVersionAndPlatform(url string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) {
|
||||
func GetAgentVersionAndPlatform(endpointUrl string, tlsConfig *tls.Config) (portainer.AgentPlatform, string, error) {
|
||||
httpCli := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func GetAgentVersionAndPlatform(url string, tlsConfig *tls.Config) (portainer.Ag
|
||||
}
|
||||
}
|
||||
|
||||
parsedURL, err := netUrl.Parse(fmt.Sprintf("%s/ping", url))
|
||||
parsedURL, err := url.ParseURL(endpointUrl + "/ping")
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
@@ -259,9 +259,6 @@ func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portain
|
||||
endpointType := portainer.DockerEnvironment
|
||||
var agentVersion string
|
||||
if payload.EndpointCreationType == agentEnvironment {
|
||||
|
||||
payload.URL = "tcp://" + normalizeAgentAddress(payload.URL)
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
if payload.TLS {
|
||||
tlsConfig, err = crypto.CreateTLSConfigurationFromBytes(payload.TLSCACertFile, payload.TLSCertFile, payload.TLSKeyFile, payload.TLSSkipVerify, payload.TLSSkipClientVerify)
|
||||
|
||||
@@ -105,12 +105,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
}
|
||||
|
||||
if payload.URL != nil {
|
||||
if endpoint.Type == portainer.AgentOnDockerEnvironment ||
|
||||
endpoint.Type == portainer.AgentOnKubernetesEnvironment {
|
||||
endpoint.URL = normalizeAgentAddress(*payload.URL)
|
||||
} else {
|
||||
endpoint.URL = *payload.URL
|
||||
}
|
||||
endpoint.URL = *payload.URL
|
||||
}
|
||||
|
||||
if payload.PublicURL != nil {
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
package endpoints
|
||||
|
||||
import "strings"
|
||||
|
||||
func BoolAddr(b bool) *bool {
|
||||
boolVar := b
|
||||
return &boolVar
|
||||
}
|
||||
|
||||
func normalizeAgentAddress(url string) string {
|
||||
// Case insensitive strip http or https scheme if URL entered
|
||||
index := strings.Index(url, "://")
|
||||
if index >= 0 {
|
||||
return url[index+3:]
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
@@ -181,6 +181,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.StripPrefix("/api", h.EndpointGroupHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/kubernetes"):
|
||||
http.StripPrefix("/api", h.KubernetesHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/docker"):
|
||||
http.StripPrefix("/api/docker", h.DockerHandler).ServeHTTP(w, r)
|
||||
|
||||
// Helm subpath under kubernetes -> /api/endpoints/{id}/kubernetes/helm
|
||||
case strings.HasPrefix(r.URL.Path, "/api/endpoints/") && strings.Contains(r.URL.Path, "/kubernetes/helm"):
|
||||
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/agent"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/internal/url"
|
||||
)
|
||||
|
||||
// ProxyServer provide an extended proxy with a local server to forward requests
|
||||
@@ -34,7 +33,7 @@ func (factory *ProxyFactory) NewAgentProxy(endpoint *portainer.Endpoint) (*Proxy
|
||||
urlString = fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
|
||||
}
|
||||
|
||||
endpointURL, err := parseURL(urlString)
|
||||
endpointURL, err := url.ParseURL(urlString)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed parsing url %s", endpoint.URL)
|
||||
}
|
||||
@@ -99,15 +98,3 @@ func (proxy *ProxyServer) Close() {
|
||||
proxy.server.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// parseURL parses the endpointURL using url.Parse.
|
||||
//
|
||||
// to prevent an error when url has port but no protocol prefix
|
||||
// we add `//` prefix if needed
|
||||
func parseURL(endpointURL string) (*url.URL, error) {
|
||||
if !strings.HasPrefix(endpointURL, "http") && !strings.HasPrefix(endpointURL, "tcp") && !strings.HasPrefix(endpointURL, "//") {
|
||||
endpointURL = fmt.Sprintf("//%s", endpointURL)
|
||||
}
|
||||
|
||||
return url.Parse(endpointURL)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/docker"
|
||||
"github.com/portainer/portainer/api/internal/url"
|
||||
)
|
||||
|
||||
func (factory *ProxyFactory) newDockerProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
@@ -23,7 +23,7 @@ func (factory *ProxyFactory) newDockerProxy(endpoint *portainer.Endpoint) (http.
|
||||
}
|
||||
|
||||
func (factory *ProxyFactory) newDockerLocalProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
endpointURL, err := url.Parse(endpoint.URL)
|
||||
endpointURL, err := url.ParseURL(endpoint.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func (factory *ProxyFactory) newDockerHTTPProxy(endpoint *portainer.Endpoint) (h
|
||||
rawURL = fmt.Sprintf("http://127.0.0.1:%d", tunnel.Port)
|
||||
}
|
||||
|
||||
endpointURL, err := url.Parse(rawURL)
|
||||
endpointURL, err := url.ParseURL(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
19
api/internal/url/url.go
Normal file
19
api/internal/url/url.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseURL parses the endpointURL using url.Parse.
|
||||
//
|
||||
// to prevent an error when url has port but no protocol prefix
|
||||
// we add `//` prefix if needed
|
||||
func ParseURL(endpointURL string) (*url.URL, error) {
|
||||
if !strings.HasPrefix(endpointURL, "http") && !strings.HasPrefix(endpointURL, "tcp") && !strings.HasPrefix(endpointURL, "//") {
|
||||
endpointURL = fmt.Sprintf("//%s", endpointURL)
|
||||
}
|
||||
|
||||
return url.Parse(endpointURL)
|
||||
}
|
||||
@@ -52,7 +52,11 @@ func (d *stackDeployer) DeployComposeStack(stack *portainer.Stack, endpoint *por
|
||||
d.swarmStackManager.Login(registries, endpoint)
|
||||
defer d.swarmStackManager.Logout(endpoint)
|
||||
|
||||
return d.composeStackManager.Up(context.TODO(), stack, endpoint, forceRereate)
|
||||
err := d.composeStackManager.Up(context.TODO(), stack, endpoint, forceRereate)
|
||||
if err != nil {
|
||||
d.composeStackManager.Down(context.TODO(), stack, endpoint)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *stackDeployer) DeployKubernetesStack(stack *portainer.Stack, endpoint *portainer.Endpoint, user *portainer.User) error {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<button ng-if="!$ctrl.state.uploadInProgress" type="button" ngf-select="$ctrl.onFileSelected($file)" class="btn ng-scope">
|
||||
<button ng-if="!$ctrl.state.uploadInProgress" type="button" ngf-select="$ctrl.onFileSelected($file)" class="btn btn-light ng-scope">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
</button>
|
||||
<button ng-if="$ctrl.state.uploadInProgress" type="button" class="btn ng-scope" button-spinner="$ctrl.state.uploadInProgress"> </button>
|
||||
<button ng-if="$ctrl.state.uploadInProgress" type="button" class="btn btn-sm btn-light" button-spinner="$ctrl.state.uploadInProgress"></button>
|
||||
|
||||
@@ -117,10 +117,6 @@ input[type='checkbox'] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a[ng-click] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.space-right {
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -481,7 +477,7 @@ a[ng-click] {
|
||||
|
||||
:root[theme='dark'] .bootbox-checkbox-list,
|
||||
:root[theme='highcontrast'] .bootbox-checkbox-list {
|
||||
background-color: var(--black-color);
|
||||
background-color: var(--bg-modal-content-color);
|
||||
}
|
||||
|
||||
.small-select {
|
||||
|
||||
19
app/assets/css/bootstrap-override.css
vendored
19
app/assets/css/bootstrap-override.css
vendored
@@ -12,8 +12,11 @@
|
||||
}
|
||||
|
||||
.control-label {
|
||||
color: var(--ui-gray-7);
|
||||
font-weight: 500;
|
||||
@apply inline-flex items-center;
|
||||
@apply font-medium;
|
||||
@apply text-gray-7;
|
||||
@apply th-dark:text-gray-warm-3;
|
||||
@apply th-highcontrast:text-white;
|
||||
}
|
||||
|
||||
.vertical-center {
|
||||
@@ -68,6 +71,10 @@
|
||||
|
||||
.switch input[type='checkbox']:disabled + .slider {
|
||||
background-color: var(--ui-gray-3);
|
||||
@apply th-dark:before:bg-gray-warm-8;
|
||||
@apply th-highcontrast:before:bg-gray-warm-8;
|
||||
@apply th-dark:bg-gray-warm-9;
|
||||
@apply th-highcontrast:bg-gray-warm-9;
|
||||
}
|
||||
|
||||
.switch-values {
|
||||
@@ -88,6 +95,8 @@
|
||||
background-color: var(--bg-switch-box-color);
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
@apply th-dark:bg-gray-warm-9;
|
||||
@apply th-highcontrast:bg-gray-warm-9;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
@@ -104,6 +113,8 @@
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--ui-blue-8);
|
||||
@apply th-dark:bg-blue-9;
|
||||
@apply th-highcontrast:bg-blue-9;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
@@ -355,10 +366,6 @@ input:checked + .slider:before {
|
||||
color: var(--ui-error-9);
|
||||
}
|
||||
|
||||
.control-label {
|
||||
@apply inline-flex items-center;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -25,14 +25,14 @@ fieldset[disabled] .btn {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
.btn.btn-primary {
|
||||
@apply text-white bg-blue-8 border-blue-8;
|
||||
@apply hover:text-white hover:bg-blue-9 hover:border-blue-9;
|
||||
@apply th-dark:hover:bg-blue-7 th-dark:hover:border-blue-7;
|
||||
}
|
||||
|
||||
.btn-primary:active,
|
||||
.btn-primary.active,
|
||||
.btn.btn-primary:active,
|
||||
.btn.btn-primary.active,
|
||||
.open > .dropdown-toggle.btn-primary {
|
||||
@apply bg-blue-9 border-blue-5;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ fieldset[disabled] .btn {
|
||||
}
|
||||
|
||||
/* Button Secondary */
|
||||
.btn-secondary {
|
||||
.btn.btn-secondary {
|
||||
@apply border border-solid;
|
||||
|
||||
@apply text-blue-9 bg-blue-2 border-blue-8;
|
||||
@@ -54,18 +54,18 @@ fieldset[disabled] .btn {
|
||||
@apply th-dark:hover:bg-blue-11;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
.btn.btn-danger {
|
||||
@apply bg-error-8 border-error-8;
|
||||
@apply hover:bg-error-7 hover:border-error-7 hover:text-white;
|
||||
}
|
||||
|
||||
.btn-danger:active,
|
||||
.btn-danger.active,
|
||||
.btn.btn-danger:active,
|
||||
.btn.btn-danger.active,
|
||||
.open > .dropdown-toggle.btn-danger {
|
||||
@apply bg-error-8 text-white border-blue-5;
|
||||
}
|
||||
|
||||
.btn-dangerlight {
|
||||
.btn.btn-dangerlight {
|
||||
@apply text-error-9 th-dark:text-white;
|
||||
@apply bg-error-3 th-dark:bg-error-9;
|
||||
@apply hover:bg-error-2 th-dark:hover:bg-error-11;
|
||||
@@ -73,39 +73,38 @@ fieldset[disabled] .btn {
|
||||
@apply border border-solid;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
.btn.btn-success {
|
||||
background-color: var(--ui-success-7);
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
.btn.btn-success:hover {
|
||||
color: var(--white-color);
|
||||
}
|
||||
|
||||
/* secondary-grey */
|
||||
.btn-default,
|
||||
.btn-light {
|
||||
.btn.btn-default,
|
||||
.btn.btn-light {
|
||||
@apply bg-white border-gray-5 text-gray-9;
|
||||
@apply hover:bg-gray-3 hover:border-gray-5 hover:text-gray-10;
|
||||
|
||||
/* dark mode */
|
||||
@apply th-dark:bg-gray-warm-10 th-dark:border-gray-warm-7 th-dark:text-gray-warm-4;
|
||||
@apply th-dark:hover:bg-gray-warm-9 th-dark:hover:border-gray-6 th-dark:hover:text-gray-warm-4;
|
||||
|
||||
@apply th-highcontrast:bg-black th-highcontrast:border-gray-2 th-highcontrast:text-white;
|
||||
@apply th-highcontrast:hover:bg-gray-9 th-highcontrast:hover:border-gray-6 th-highcontrast:hover:text-gray-warm-4;
|
||||
}
|
||||
|
||||
.btn-light:active,
|
||||
.btn-light.active,
|
||||
.btn.btn-light:active,
|
||||
.btn.btn-light.active,
|
||||
.open > .dropdown-toggle.btn-light {
|
||||
background-color: var(--ui-gray-3);
|
||||
}
|
||||
|
||||
.hyperlink,
|
||||
.hyperlink:focus {
|
||||
color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.hyperlink:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--ui-blue-9);
|
||||
.btn.btn-link {
|
||||
@apply text-blue-8 hover:text-blue-9 disabled:text-gray-5;
|
||||
@apply th-dark:text-blue-8 th-dark:hover:text-blue-7;
|
||||
@apply th-highcontrast:text-blue-8 th-highcontrast:hover:text-blue-7;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
@@ -120,30 +119,43 @@ fieldset[disabled] .btn {
|
||||
|
||||
/* focus */
|
||||
|
||||
.btn-primary:focus,
|
||||
.btn-secondary:focus,
|
||||
.btn-light:focus {
|
||||
.btn.btn-primary:focus,
|
||||
.btn.btn-secondary:focus,
|
||||
.btn.btn-light:focus {
|
||||
@apply border-blue-5;
|
||||
}
|
||||
|
||||
.btn-danger:focus,
|
||||
.btn-dangerlight:focus {
|
||||
.btn.btn-danger:focus,
|
||||
.btn.btn-dangerlight:focus {
|
||||
@apply border-blue-6;
|
||||
}
|
||||
|
||||
.btn-primary:focus,
|
||||
.btn-secondary:focus,
|
||||
.btn-light:focus,
|
||||
.btn-danger:focus,
|
||||
.btn-dangerlight:focus {
|
||||
.btn.btn-primary:focus,
|
||||
.btn.btn-secondary:focus,
|
||||
.btn.btn-light:focus,
|
||||
.btn.btn-danger:focus,
|
||||
.btn.btn-dangerlight:focus {
|
||||
--btn-focus-color: var(--ui-blue-3);
|
||||
box-shadow: 0px 0px 0px 4px var(--btn-focus-color);
|
||||
}
|
||||
|
||||
[theme='dark'] .btn-primary:focus,
|
||||
[theme='dark'] .btn-secondary:focus,
|
||||
[theme='dark'] .btn-light:focus,
|
||||
[theme='dark'] .btn-danger:focus,
|
||||
[theme='dark'] .btn-dangerlight:focus {
|
||||
[theme='dark'] .btn.btn-primary:focus,
|
||||
[theme='dark'] .btn.btn-secondary:focus,
|
||||
[theme='dark'] .btn.btn-light:focus,
|
||||
[theme='dark'] .btn.btn-danger:focus,
|
||||
[theme='dark'] .btn.btn-dangerlight:focus {
|
||||
--btn-focus-color: var(--ui-blue-11);
|
||||
}
|
||||
|
||||
a.no-link,
|
||||
a[ng-click] {
|
||||
@apply text-current;
|
||||
@apply hover:no-underline hover:text-current;
|
||||
@apply focus:no-underline focus:text-current;
|
||||
}
|
||||
|
||||
a,
|
||||
a.hyperlink {
|
||||
@apply text-blue-8 hover:text-blue-9;
|
||||
@apply hover:underline cursor-pointer;
|
||||
}
|
||||
|
||||
@@ -31,17 +31,6 @@
|
||||
border-top: 1px solid var(--border-table-top-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
color: var(--text-input-group-addon-color);
|
||||
background-color: var(--bg-input-group-addon-color);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<svg width="51" height="57" viewBox="0 0 51 57" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.4176 17.75L43.882 0.819107H7.28089L1.74531 17.75C1.74531 17.75 -4.79215 34.8935 10.4787 45.4348C24.3922 55.0404 25.5814 56.2077 25.5814 56.2077C25.5814 56.2077 26.7707 55.038 40.6842 45.4348C55.955 34.8935 49.4176 17.75 49.4176 17.75Z" fill="#F4552A"/>
|
||||
<path d="M25.5851 0.984695L31.4835 17.6745L49.4453 18.0361L35.1283 28.7097L40.3323 45.6217L25.5851 35.5293L10.838 45.6217L16.042 28.7097L1.72498 18.0361L19.6868 17.6745L25.5851 0.984695Z" fill="white"/>
|
||||
</svg>
|
||||
<svg width="auto" height="auto" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M22.9751 15.3177L20.4526 7.52911H3.77358L1.25103 15.3177C1.25103 15.3177 -1.72806 23.2041 5.23082 28.0533C11.5711 32.4721 12.1131 33.0091 12.1131 33.0091C12.1131 33.0091 12.655 32.471 18.9953 28.0533C25.9542 23.2041 22.9751 15.3177 22.9751 15.3177Z"
|
||||
fill="#F4552A" />
|
||||
<path
|
||||
d="M12.1152 7.60529L14.803 15.283L22.9882 15.4493L16.4639 20.3594L18.8354 28.1393L12.1152 23.4966L5.39497 28.1393L7.76642 20.3594L1.24219 15.4493L9.42731 15.283L12.1152 7.60529Z"
|
||||
fill="white" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 604 B |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
@@ -31,7 +31,7 @@ export const KUBERNETES_SYSTEM_NAMESPACES = ['kube-system', 'kube-public', 'kube
|
||||
export const PORTAINER_FADEOUT = 1500;
|
||||
export const STACK_NAME_VALIDATION_REGEX = '^[-_a-z0-9]+$';
|
||||
export const TEMPLATE_NAME_VALIDATION_REGEX = '^[-_a-z0-9]+$';
|
||||
export const BROWSER_OS_PLATFORM = navigator.userAgent.indexOf('Windows NT') > -1 ? 'win' : 'lin';
|
||||
export const BROWSER_OS_PLATFORM = navigator.userAgent.indexOf('Windows') > -1 ? 'win' : navigator.userAgent.indexOf('Mac') > -1 ? 'mac' : 'lin';
|
||||
export const NEW_LINE_BREAKER = BROWSER_OS_PLATFORM === 'win' ? '\r\n' : '\n';
|
||||
|
||||
// don't declare new constants, either:
|
||||
@@ -66,4 +66,5 @@ angular
|
||||
.constant('PAGINATION_MAX_ITEMS', PAGINATION_MAX_ITEMS)
|
||||
.constant('APPLICATION_CACHE_VALIDITY', APPLICATION_CACHE_VALIDITY)
|
||||
.constant('CONSOLE_COMMANDS_LABEL_PREFIX', CONSOLE_COMMANDS_LABEL_PREFIX)
|
||||
.constant('PREDEFINED_NETWORKS', PREDEFINED_NETWORKS);
|
||||
.constant('PREDEFINED_NETWORKS', PREDEFINED_NETWORKS)
|
||||
.constant('BROWSER_OS_PLATFORM', BROWSER_OS_PLATFORM);
|
||||
|
||||
@@ -376,6 +376,17 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
|
||||
},
|
||||
};
|
||||
|
||||
var stackContainer = {
|
||||
name: 'docker.stacks.stack.container',
|
||||
url: '/:id?nodeName',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: '~@/docker/views/containers/edit/container.html',
|
||||
controller: 'ContainerController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var stackCreation = {
|
||||
name: 'docker.stacks.newstack',
|
||||
url: '/newstack',
|
||||
@@ -553,6 +564,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
|
||||
$stateRegistryProvider.register(serviceLogs);
|
||||
$stateRegistryProvider.register(stacks);
|
||||
$stateRegistryProvider.register(stack);
|
||||
$stateRegistryProvider.register(stackContainer);
|
||||
$stateRegistryProvider.register(stackCreation);
|
||||
$stateRegistryProvider.register(swarm);
|
||||
$stateRegistryProvider.register(swarmVisualizer);
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
ng-click="$ctrl.expandItem(value, !value.Expanded)"
|
||||
>
|
||||
<td>
|
||||
<a ng-if="$ctrl.itemCanExpand(value)">
|
||||
<button class="btn btn-none" ng-if="$ctrl.itemCanExpand(value)" type="button">
|
||||
<i ng-class="{ 'fas fa-angle-down': value.Expanded, 'fas fa-angle-right': !value.Expanded }" class="space-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</button>
|
||||
<a ui-sref="docker.networks.network({ id: key, nodeName: $ctrl.nodeName })">{{ key }}</a>
|
||||
</td>
|
||||
<td>{{ value.IPAddress || '-' }}</td>
|
||||
|
||||
@@ -127,15 +127,20 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th uib-dropdown dropdown-append-to-body auto-close="disabled" popover-placement="bottom-left" is-open="$ctrl.filters.state.open">
|
||||
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Id')">
|
||||
Id
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<div class="flex gap-1 items-center">
|
||||
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<table-column-header
|
||||
col-title="'Id'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'Id'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'Id' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('Id')"
|
||||
></table-column-header>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span uib-dropdown-toggle class="table-filter" ng-if="!$ctrl.filters.state.enabled">Filter <i class="fa fa-filter" aria-hidden="true"></i></span>
|
||||
<span uib-dropdown-toggle class="table-filter filter-active" ng-if="$ctrl.filters.state.enabled">Filter <i class="fa fa-check" aria-hidden="true"></i></span>
|
||||
@@ -160,32 +165,40 @@
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('RepoTags')">
|
||||
Tags
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RepoTags' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<table-column-header
|
||||
col-title="'Tags'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'RepoTags'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'RepoTags' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('RepoTags')"
|
||||
></table-column-header>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('VirtualSize')">
|
||||
Size
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'VirtualSize' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<table-column-header
|
||||
col-title="'Size'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'VirtualSize'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'VirtualSize' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('VirtualSize')"
|
||||
></table-column-header>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Created')">
|
||||
Created
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Created' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<table-column-header
|
||||
col-title="'Created'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'Created'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'Created' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('Created')"
|
||||
></table-column-header>
|
||||
</th>
|
||||
<th ng-if="$ctrl.showHostColumn">
|
||||
<a ng-click="$ctrl.changeOrderBy('NodeName')">
|
||||
Host
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
<table-column-header
|
||||
col-title="'Host'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'NodeName'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'NodeName' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('NodeName')"
|
||||
></table-column-header>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event); $event.stopPropagation()" />
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a><i ng-class="{ 'fas fa-angle-down': item.Expanded, 'fas fa-angle-right': !item.Expanded }" class="space-right" aria-hidden="true"></i></a>
|
||||
<i ng-class="{ 'fas fa-angle-down': item.Expanded, 'fas fa-angle-right': !item.Expanded }" class="space-right" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td>
|
||||
<a ui-sref="docker.services.service({id: item.Id})" ng-click="$event.stopPropagation()">{{ item.Name }}</a>
|
||||
|
||||
@@ -190,11 +190,17 @@
|
||||
item.Id | truncate: 40
|
||||
}}</a>
|
||||
<span ng-if="$ctrl.offlineMode">{{ item.Id | truncate: 40 }}</span>
|
||||
<btn authorization="DockerAgentBrowseList" ng-if="$ctrl.showBrowseAction && !$ctrl.offlineMode">
|
||||
<a ui-sref="docker.volumes.volume.browse({ id: item.Id, nodeName: item.NodeName })" class="btn btn-xs btn-primary space-left">
|
||||
<pr-icon icon="'search'" feather="true"></pr-icon> browse
|
||||
</a>
|
||||
</btn>
|
||||
|
||||
<button
|
||||
ng-if="$ctrl.showBrowseAction && !$ctrl.offlineMode"
|
||||
type="button"
|
||||
ui-sref="docker.volumes.volume.browse({ id: item.Id, nodeName: item.NodeName })"
|
||||
class="btn btn-xs btn-primary space-left"
|
||||
authorization="DockerAgentBrowseList"
|
||||
>
|
||||
<pr-icon icon="'search'" feather="true"></pr-icon> browse
|
||||
</button>
|
||||
|
||||
<span style="margin-left: 10px" class="label label-warning image-tag space-left" ng-if="item.dangling">Unused</span>
|
||||
</td>
|
||||
<td>{{ item.StackName ? item.StackName : '-' }}</td>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
<div class="nopadding">
|
||||
<a class="btn btn-secondary btn-sm pull-right" ng-click="$ctrl.addLabel(node)"> <pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> label </a>
|
||||
<a class="btn btn-secondary btn-sm pull-right" ng-click="$ctrl.addLabel(node)"> <pr-icon icon="'plus'" feather="true"></pr-icon> label </a>
|
||||
</div>
|
||||
Node Labels
|
||||
</td>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="logs_since" class="col-sm-1 control-label text-left"> Fetch </label>
|
||||
<label for="logs_since" class="col-sm-2 control-label text-left"> Fetch </label>
|
||||
<div class="col-sm-2">
|
||||
<select class="form-control" ng-model="$ctrl.sinceTimestamp" id="logs_since">
|
||||
<option selected value="">All logs</option>
|
||||
@@ -40,20 +40,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="logs_search" class="col-sm-1 control-label text-left"> Search </label>
|
||||
<div class="col-sm-11">
|
||||
<label for="logs_search" class="col-sm-2 control-label text-left"> Search </label>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control" type="text" name="logs_search" ng-model="$ctrl.state.search" ng-change="$ctrl.state.selectedLines.length = 0;" placeholder="Filter..." />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lines_count" class="col-sm-1 control-label text-left"> Lines </label>
|
||||
<div class="col-sm-11">
|
||||
<label for="lines_count" class="col-sm-2 control-label text-left"> Lines </label>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control" type="number" name="lines_count" ng-model="$ctrl.lineCount" placeholder="Enter no of lines..." />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.state.copySupported">
|
||||
<label class="col-sm-1 control-label text-left"> Actions </label>
|
||||
<div class="col-sm-11">
|
||||
<label class="col-sm-2 control-label text-left"> Actions </label>
|
||||
<div class="col-sm-10">
|
||||
<button class="btn btn-primary btn-sm" type="button" ng-click="$ctrl.downloadLogs()" style="margin-left: 0"
|
||||
><pr-icon icon="'download'" feather="true"></pr-icon> Download logs</button
|
||||
>
|
||||
|
||||
@@ -812,8 +812,8 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
function (d) {
|
||||
var containers = d;
|
||||
$scope.runningContainers = containers;
|
||||
$scope.gpuUseAll = $scope.endpoint.Snapshots[0].GpuUseAll;
|
||||
$scope.gpuUseList = $scope.endpoint.Snapshots[0].GpuUseList;
|
||||
$scope.gpuUseAll = _.get($scope, 'endpoint.Snapshots[0].GpuUseAll', false);
|
||||
$scope.gpuUseList = _.get($scope, 'endpoint.Snapshots[0].GpuUseList', []);
|
||||
if ($transition$.params().from) {
|
||||
loadFromContainerSpec();
|
||||
} else {
|
||||
|
||||
@@ -640,7 +640,7 @@
|
||||
</div>
|
||||
<!-- !runtimes -->
|
||||
</form>
|
||||
<form class="form-horizontal" style="margin-top: 15px">
|
||||
<form class="form-horizontal" style="margin-top: 15px" name="resourceForm">
|
||||
<!-- devices -->
|
||||
<div ng-if="showDeviceMapping" class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px">
|
||||
@@ -712,9 +712,9 @@
|
||||
<div ng-class="{ 'edit-resources': state.mode == 'duplicate' }">
|
||||
<div class="col-sm-12 form-section-title"> Resources </div>
|
||||
<!-- memory-reservation-input -->
|
||||
<div class="form-group">
|
||||
<label for="memory-reservation" class="col-sm-3 col-lg-2 control-label text-left mt-8"> Memory reservation </label>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group flex">
|
||||
<label for="memory-reservation" class="col-sm-3 col-lg-2 control-label text-left vertical-center"> Memory reservation (MB) </label>
|
||||
<div class="col-sm-6">
|
||||
<slider
|
||||
on-change="(handleResourceChange)"
|
||||
model="formValues.MemoryReservation"
|
||||
@@ -724,18 +724,34 @@
|
||||
ng-if="state.sliderMaxMemory"
|
||||
></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation" />
|
||||
<div class="col-sm-2 vertical-center">
|
||||
<input
|
||||
name="memory_reservation"
|
||||
type="number"
|
||||
min="0"
|
||||
max="{{ state.sliderMaxMemory }}"
|
||||
class="form-control"
|
||||
ng-model="formValues.MemoryReservation"
|
||||
id="memory-reservation"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<p class="small text-muted mt-2"> Memory soft limit (<b>MB</b>) </p>
|
||||
</div>
|
||||
<div class="form-group" ng-show="resourceForm.memory_reservation.$invalid">
|
||||
<div class="col-sm-3 col-lg-2"></div>
|
||||
<div class="col-sm-8 small text-muted">
|
||||
<div ng-messages="resourceForm.memory-reservation.$error">
|
||||
<p class="vertical-center text-warning">
|
||||
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> Value must be between 0 and {{ state.sliderMaxMemory }}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !memory-reservation-input -->
|
||||
<!-- memory-limit-input -->
|
||||
<div class="form-group">
|
||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left mt-8"> Memory limit </label>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group flex">
|
||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left vertical-center"> Memory limit (MB) </label>
|
||||
<div class="col-sm-6">
|
||||
<slider
|
||||
on-change="(handleResourceChange)"
|
||||
model="formValues.MemoryLimit"
|
||||
@@ -745,31 +761,44 @@
|
||||
ng-if="state.sliderMaxMemory"
|
||||
></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit" />
|
||||
<div class="col-sm-2 vertical-center">
|
||||
<input
|
||||
name="memory_Limit"
|
||||
type="number"
|
||||
min="0"
|
||||
max="{{ state.sliderMaxMemory }}"
|
||||
class="form-control"
|
||||
ng-model="formValues.MemoryLimit"
|
||||
id="memory-limit"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<p class="small text-muted mt-7"> Memory limit (<b>MB</b>) </p>
|
||||
</div>
|
||||
<div class="form-group" ng-show="resourceForm.memory_Limit.$invalid">
|
||||
<div class="col-sm-3 col-lg-2"></div>
|
||||
<div class="col-sm-8 small text-muted">
|
||||
<div ng-messages="resourceForm.memory-limit.$error">
|
||||
<p class="vertical-center text-warning">
|
||||
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> Value must be between 0 and {{ state.sliderMaxMemory }}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !memory-limit-input -->
|
||||
<!-- cpu-limit-input -->
|
||||
<div class="form-group">
|
||||
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left mt-8"> CPU limit </label>
|
||||
<div class="col-sm-5">
|
||||
<div class="form-group flex">
|
||||
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left vertical-center"> Maximum CPU usage </label>
|
||||
<div class="col-sm-8">
|
||||
<slider
|
||||
on-change="(handleResourceChange)"
|
||||
model="formValues.CpuLimit"
|
||||
floor="0"
|
||||
ceil="state.sliderMaxCpu"
|
||||
step="0.25"
|
||||
step="0.1"
|
||||
precision="2"
|
||||
ng-if="state.sliderMaxCpu"
|
||||
></slider>
|
||||
</div>
|
||||
<div class="col-sm-4 mt-8">
|
||||
<p class="small text-muted"> Maximum CPU usage </p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !cpu-limit-input -->
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td class="col-xs-6 col-sm-4 col-md-3 col-lg-3">ID</td>
|
||||
<td>{{ container.Id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -77,29 +77,29 @@
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid mx-4">
|
||||
<a ui-sref="docker.stacks" ng-if="showStacks">
|
||||
<dashboard-item feather-icon="true" icon="'layers'" feather-icon="true" type="'Stack'" value="stackCount"></dashboard-item>
|
||||
<a class="no-link" ui-sref="docker.stacks" ng-if="showStacks">
|
||||
<dashboard-item feather-icon="true" icon="'layers'" type="'Stack'" value="stackCount"></dashboard-item>
|
||||
</a>
|
||||
|
||||
<div ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="docker.services">
|
||||
<a class="no-link" ui-sref="docker.services">
|
||||
<dashboard-item feather-icon="true" icon="'shuffle'" type="'Service'" value="serviceCount"></dashboard-item>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a ng-if="containers" ui-sref="docker.containers">
|
||||
<a class="no-link" ng-if="containers" ui-sref="docker.containers">
|
||||
<dashboard-item feather-icon="true" icon="'box'" type="'Container'" value="containers.length" children="containerStatusComponent"></dashboard-item>
|
||||
</a>
|
||||
|
||||
<a ng-if="images" ui-sref="docker.images">
|
||||
<a class="no-link" ng-if="images" ui-sref="docker.images">
|
||||
<dashboard-item feather-icon="true" icon="'list'" type="'Image'" value="images.length" children="imagesTotalSizeComponent"></dashboard-item>
|
||||
</a>
|
||||
|
||||
<a ui-sref="docker.volumes">
|
||||
<a class="no-link" ui-sref="docker.volumes">
|
||||
<dashboard-item feather-icon="true" icon="'database'" type="'Volume'" value="volumeCount"></dashboard-item>
|
||||
</a>
|
||||
|
||||
<a ui-sref="docker.networks">
|
||||
<a class="no-link" ui-sref="docker.networks">
|
||||
<dashboard-item feather-icon="true" icon="'share2'" type="'Network'" value="networkCount"></dashboard-item>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<form class="form-horizontal" name="$ctrl.form">
|
||||
<div class="col-sm-12 form-section-title"> Host and Filesystem </div>
|
||||
<div ng-if="!$ctrl.isAgent" class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
<span class="col-sm-12 text-muted small vertical-center">
|
||||
<pr-icon icon="'info'" feather="true" mode="'primary'" class-name="space-right"></pr-icon>
|
||||
These features are only available for an Agent enabled environments.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -76,37 +76,39 @@
|
||||
<!-- build-method -->
|
||||
<div class="col-sm-12 form-section-title"> Build method </div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" class="mb-0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="state.BuildType" value="editor" ng-click="toggleEditor()" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="state.BuildType" value="upload" ng-click="saveEditorContent()" />
|
||||
<label for="method_upload">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload a tarball or a Dockerfile from your computer</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_url" ng-model="state.BuildType" value="url" ng-click="saveEditorContent()" />
|
||||
<label for="method_url">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'globe'" feather="true"></pr-icon>
|
||||
URL
|
||||
</div>
|
||||
<p>Specify a URL to a file</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="state.BuildType" value="editor" ng-click="toggleEditor()" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="state.BuildType" value="upload" ng-click="saveEditorContent()" />
|
||||
<label for="method_upload">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload a tarball or a Dockerfile from your computer</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_url" ng-model="state.BuildType" value="url" ng-click="saveEditorContent()" />
|
||||
<label for="method_url">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'globe'" feather="true"></pr-icon>
|
||||
URL
|
||||
</div>
|
||||
<p>Specify a URL to a file</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
<div class="row">
|
||||
<div class="pull-left" ng-repeat="tag in image.RepoTags" style="display: table">
|
||||
<div class="input-group col-md-1 !pr-3.5 !pl-3.5">
|
||||
<span class="input-group-addon" style="border-right: 1px solid var(--border-input-group-addon-color); border-radius: 4px">{{ tag }}</span>
|
||||
<span class="input-group-addon" style="border-right: 1px solid var(--border-input-group-addon-color); border-radius: 4px" data-cy="image-tag-{{ tag }}">{{
|
||||
tag
|
||||
}}</span>
|
||||
<span class="input-group-btn" style="padding: 0px 5px">
|
||||
<span style="margin: 0px 5px" authorization="DockerImagePush">
|
||||
<a data-toggle="tooltip" class="btn btn-primary interactive" title="Push to registry" ng-click="pushTag(tag)">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<select class="form-control" ng-options="config.Name for config in configs | orderBy: 'Name'" ng-model="newConfig">
|
||||
<option selected disabled hidden value="">Select a config</option>
|
||||
</select>
|
||||
<a class="btn btn-default btn-sm" ng-click="addConfig(service, newConfig)"> <pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> add config </a>
|
||||
<a class="btn btn-default btn-sm" ng-click="addConfig(service, newConfig)"> <pr-icon icon="'plus'" feather="true"></pr-icon> add config </a>
|
||||
</div>
|
||||
<table class="table" style="margin-top: 5px">
|
||||
<thead>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<rd-widget-header icon="list" feather-icon="true" title-text="Container labels">
|
||||
<div class="nopadding" authorization="DockerServiceUpdate">
|
||||
<a class="btn btn-secondary btn-sm pull-right" ng-click="isUpdating ||addContainerLabel(service)" ng-disabled="isUpdating">
|
||||
<pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> container label
|
||||
<pr-icon icon="'plus'" feather="true"></pr-icon> container label
|
||||
</a>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<rd-widget-header icon="list" feather-icon="true" title-text="Environment variables">
|
||||
<div class="nopadding" authorization="DockerServiceUpdate">
|
||||
<a class="btn btn-secondary btn-sm pull-right" ng-click="isUpdating || addEnvironmentVariable(service)" ng-disabled="isUpdating">
|
||||
<pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> environment variable
|
||||
<pr-icon icon="'plus'" feather="true"></pr-icon> environment variable
|
||||
</a>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<option value="none">none</option>
|
||||
</select>
|
||||
<a class="btn btn-default btn-sm" ng-click="!service.LogDriverName || service.LogDriverName === 'none' || addLogDriverOpt(service)">
|
||||
<pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> add logging driver option
|
||||
<pr-icon icon="'plus'" feather="true"></pr-icon> add logging driver option
|
||||
</a>
|
||||
</div>
|
||||
<table class="table">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<rd-widget-header icon="list" feather-icon="true" title-text="Mounts">
|
||||
<div class="nopadding" authorization="DockerServiceUpdate">
|
||||
<a class="btn btn-secondary btn-sm pull-right" ng-click="isUpdating ||addMount(service)" ng-disabled="isUpdating">
|
||||
<pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> mount
|
||||
<pr-icon icon="'plus'" feather="true"></pr-icon> mount
|
||||
</a>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<label class="btn btn-light" ng-model="state.addSecret.override" uib-btn-radio="false">Default location</label>
|
||||
<label class="btn btn-light" ng-model="state.addSecret.override" uib-btn-radio="true">Override</label>
|
||||
</div>
|
||||
<a class="btn btn-default btn-sm" ng-click="addSecret(service, state.addSecret)"> <pr-icon icon="'plus'" mode="'alt'" feather="true"></pr-icon> add secret </a>
|
||||
<a class="btn btn-default btn-sm" ng-click="addSecret(service, state.addSecret)"> <pr-icon icon="'plus'" feather="true"></pr-icon> add secret </a>
|
||||
</div>
|
||||
<table class="table" style="margin-top: 5px">
|
||||
<thead>
|
||||
|
||||
@@ -34,27 +34,29 @@
|
||||
<!-- edge-job-method-select -->
|
||||
<div class="col-sm-12 form-section-title"> Edge job configuration </div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group px-4">
|
||||
<div class="boxselector_wrapper !mt-0">
|
||||
<div>
|
||||
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" />
|
||||
<label for="config_basic">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'calendar'" feather="true"></pr-icon>
|
||||
Basic configuration
|
||||
</div>
|
||||
<p>Select date from calendar</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="config_advanced" ng-model="$ctrl.formValues.cronMethod" value="advanced" />
|
||||
<label for="config_advanced">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Advanced configuration
|
||||
</div>
|
||||
<p>Write your own cron rule</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper !mt-0">
|
||||
<div>
|
||||
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" />
|
||||
<label for="config_basic">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'calendar'" feather="true"></pr-icon>
|
||||
Basic configuration
|
||||
</div>
|
||||
<p>Select date from calendar</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="config_advanced" ng-model="$ctrl.formValues.cronMethod" value="advanced" />
|
||||
<label for="config_advanced">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Advanced configuration
|
||||
</div>
|
||||
<p>Write your own cron rule</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,28 +153,29 @@
|
||||
<!-- execution-method -->
|
||||
<div ng-if="!$ctrl.model.Id">
|
||||
<div class="col-sm-12 form-section-title"> Job content </div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group px-4">
|
||||
<div class="boxselector_wrapper !mt-0">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="$ctrl.formValues.method" value="editor" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="$ctrl.formValues.method" value="upload" />
|
||||
<label for="method_upload">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload from your computer</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="$ctrl.formValues.method" value="editor" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="$ctrl.formValues.method" value="upload" />
|
||||
<label for="method_upload">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload from your computer</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { compose, kubernetes } from '@@/BoxSelector/common-options/deployment-methods';
|
||||
|
||||
export default class EdgeStackDeploymentTypeSelectorController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.deploymentOptions = [
|
||||
{ id: 'deployment_compose', icon: 'fab fa-docker', label: 'Compose', description: 'Docker compose format', value: 0 },
|
||||
{
|
||||
id: 'deployment_kube',
|
||||
icon: 'fa fa-cubes',
|
||||
label: 'Kubernetes',
|
||||
description: 'Kubernetes manifest format',
|
||||
...compose,
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
...kubernetes,
|
||||
value: 1,
|
||||
disabled: () => {
|
||||
return this.hasDockerEndpoint();
|
||||
|
||||
@@ -26,27 +26,29 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Group type </div>
|
||||
<div class="col-sm-12 !px-0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="static-group" ng-model="$ctrl.model.Dynamic" ng-value="false" ng-checked="!$ctrl.model.Dynamic" />
|
||||
<label for="static-group">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'list'" feather="true"></pr-icon>
|
||||
Static
|
||||
</div>
|
||||
<p>Manually select Edge environments</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="dynamic-group" ng-model="$ctrl.model.Dynamic" ng-value="true" ng-checked="$ctrl.model.Dynamic" />
|
||||
<label for="dynamic-group">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'tag'" feather="true" className="'feather'"></pr-icon>
|
||||
Dynamic
|
||||
</div>
|
||||
<p>Automatically associate environments via tags</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="static-group" ng-model="$ctrl.model.Dynamic" ng-value="false" ng-checked="!$ctrl.model.Dynamic" />
|
||||
<label for="static-group">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'list'" feather="true"></pr-icon>
|
||||
Static
|
||||
</div>
|
||||
<p>Manually select Edge environments</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="dynamic-group" ng-model="$ctrl.model.Dynamic" ng-value="true" ng-checked="$ctrl.model.Dynamic" />
|
||||
<label for="dynamic-group">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'tag'" feather="true"></pr-icon>
|
||||
Dynamic
|
||||
</div>
|
||||
<p>Automatically associate environments via tags</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,27 +80,29 @@
|
||||
<!-- DynamicGroup -->
|
||||
<div ng-if="$ctrl.model.Dynamic">
|
||||
<div class="col-sm-12 form-section-title"> Tags </div>
|
||||
<div ng-if="$ctrl.tags.length" class="form-group col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="or-selector" ng-model="$ctrl.model.PartialMatch" ng-value="true" ng-checked="$ctrl.model.PartialMatch" />
|
||||
<label for="or-selector">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'tag'" feather="true"></pr-icon>
|
||||
Partial match
|
||||
</div>
|
||||
<p>Associate any environment matching at least one of the selected tags</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="and-selector" ng-model="$ctrl.model.PartialMatch" ng-value="false" ng-checked="!$ctrl.model.PartialMatch" />
|
||||
<label for="and-selector">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'tag'" feather="true" className="'feather'"></pr-icon>
|
||||
Full match
|
||||
</div>
|
||||
<p>Associate any environment matching all of the selected tags</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="or-selector" ng-model="$ctrl.model.PartialMatch" ng-value="true" ng-checked="$ctrl.model.PartialMatch" />
|
||||
<label for="or-selector">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'tag'" feather="true"></pr-icon>
|
||||
Partial match
|
||||
</div>
|
||||
<p>Associate any environment matching at least one of the selected tags</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="boxselector">
|
||||
<input type="radio" id="and-selector" ng-model="$ctrl.model.PartialMatch" ng-value="false" ng-checked="!$ctrl.model.PartialMatch" />
|
||||
<label for="and-selector">
|
||||
<div class="boxselector_header vertical-center">
|
||||
<pr-icon icon="'tag'" feather="true"></pr-icon>
|
||||
Full match
|
||||
</div>
|
||||
<p>Associate any environment matching all of the selected tags</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { editor, git, template, upload } from '@@/BoxSelector/common-options/build-methods';
|
||||
|
||||
class DockerComposeFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, EdgeTemplateService, Notifications) {
|
||||
Object.assign(this, { $async, EdgeTemplateService, Notifications });
|
||||
|
||||
this.methodOptions = [
|
||||
{ id: 'method_editor', icon: 'edit', featherIcon: true, label: 'Web editor', description: 'Use our Web editor', value: 'editor' },
|
||||
{ id: 'method_upload', icon: 'upload', featherIcon: true, label: 'Upload', description: 'Upload from your computer', value: 'upload' },
|
||||
{ id: 'method_repository', icon: 'github', featherIcon: true, label: 'Repository', description: 'Use a git repository', value: 'repository' },
|
||||
{ id: 'method_template', icon: 'file-text', featherIcon: true, label: 'Template', description: 'Use an Edge stack template', value: 'template' },
|
||||
];
|
||||
this.methodOptions = [editor, upload, git, template];
|
||||
|
||||
this.selectedTemplate = null;
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { editor, git, upload } from '@@/BoxSelector/common-options/build-methods';
|
||||
|
||||
class KubeManifestFormController {
|
||||
/* @ngInject */
|
||||
constructor($async) {
|
||||
Object.assign(this, { $async });
|
||||
|
||||
this.methodOptions = [
|
||||
{ id: 'method_editor', icon: 'edit', featherIcon: true, label: 'Web editor', description: 'Use our Web editor', value: 'editor' },
|
||||
{ id: 'method_upload', icon: 'upload', featherIcon: true, label: 'Upload', description: 'Upload from your computer', value: 'upload' },
|
||||
{ id: 'method_repository', icon: 'github', featherIcon: true, label: 'Repository', description: 'Use a git repository', value: 'repository' },
|
||||
];
|
||||
this.methodOptions = [editor, upload, git];
|
||||
|
||||
this.onChangeFileContent = this.onChangeFileContent.bind(this);
|
||||
this.onChangeFormValues = this.onChangeFormValues.bind(this);
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- data table content -->
|
||||
<div class="inner-datatable">
|
||||
<div ng-class="{ 'table-responsive': $ctrl.isPrimary, 'inner-datatable': !$ctrl.isPrimary }">
|
||||
<table class="table table-hover table-filters nowrap-cells" data-cy="k8sApp-appTable">
|
||||
<thead ng-class="{ 'secondary-heading': !$ctrl.isPrimary }">
|
||||
<tr role="row">
|
||||
@@ -238,13 +238,18 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a ng-if="item.KubernetesApplications" ui-sref="kubernetes.helm({ name: item.Name, namespace: item.ResourcePool })" ng-click="$event.stopPropagation()"
|
||||
<a
|
||||
ng-if="item.KubernetesApplications"
|
||||
ui-sref="kubernetes.helm({ name: item.Name, namespace: item.ResourcePool })"
|
||||
ng-click="$event.stopPropagation()"
|
||||
class="hyperlink"
|
||||
>{{ item.Name }}
|
||||
</a>
|
||||
<a
|
||||
ng-if="!item.KubernetesApplications"
|
||||
ui-sref="kubernetes.applications.application({ name: item.Name, namespace: item.ResourcePool })"
|
||||
ng-click="$event.stopPropagation()"
|
||||
class="hyperlink"
|
||||
>{{ item.Name }}
|
||||
</a>
|
||||
<span class="label label-info image-tag label-margins" ng-if="$ctrl.isSystemNamespace(item)">system</span>
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
|
||||
<div class="form-group !mx-0 !pl-0 col-sm-3 clear-both" ng-if="$ctrl.serviceType === $ctrl.KubernetesApplicationPublishingTypes.CLUSTER_IP && $ctrl.ingressType">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon">Route</span>
|
||||
<span class="input-group-addon required">Route</span>
|
||||
<input
|
||||
class="form-control"
|
||||
name="ingress_route_{{ $index }}"
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.isCreation">
|
||||
<div class="col-sm-12">
|
||||
<p>
|
||||
<a class="small interactive vertical-center" ng-if="$ctrl.formValues.IsSimple" ng-click="$ctrl.showAdvancedMode()">
|
||||
<pr-icon icon="'list'" feather="true"></pr-icon> Advanced mode
|
||||
</a>
|
||||
<a class="small interactive vertical-center" ng-if="!$ctrl.formValues.IsSimple" ng-click="$ctrl.showSimpleMode()">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon> Simple mode
|
||||
</a>
|
||||
</p>
|
||||
<button type="button" class="btn btn-link btn-sm hover:no-underline !ml-0 p-0" ng-if="$ctrl.formValues.IsSimple" ng-click="$ctrl.showAdvancedMode()">
|
||||
<pr-icon icon="'list'" feather="true"></pr-icon> Advanced mode
|
||||
</button>
|
||||
<button type="button" class="btn btn-link btn-sm hover:no-underline !ml-0 p-0" ng-if="!$ctrl.formValues.IsSimple" ng-click="$ctrl.showSimpleMode()">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon> Simple mode
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted vertical-center" ng-if="$ctrl.formValues.IsSimple">
|
||||
<pr-icon icon="'info'" mode="'primary'" feather="true"></pr-icon>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { buildOption } from '@/portainer/components/BoxSelector';
|
||||
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
|
||||
import { isBE } from '@/portainer/feature-flags/feature-flags.service';
|
||||
import { editor, upload } from '@@/BoxSelector/common-options/build-methods';
|
||||
|
||||
class KubeCreateCustomTemplateViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService) {
|
||||
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService });
|
||||
|
||||
this.methodOptions = [
|
||||
buildOption('method_editor', 'svg-custom', 'Web editor', 'Use our Web editor', 'editor'),
|
||||
buildOption('method_upload', 'svg-upload', 'Upload', 'Upload from your computer', 'upload'),
|
||||
];
|
||||
this.methodOptions = [editor, upload];
|
||||
|
||||
this.templates = null;
|
||||
this.isTemplateVariablesEnabled = isBE;
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
</div>
|
||||
<div class="form-group" ng-show="kubernetesApplicationCreationForm.application_name.$invalid || ctrl.state.alreadyExists">
|
||||
<div class="small">
|
||||
<div class="col-sm-3 col-lg-2"></div>
|
||||
<div class="col-sm-3 col-lg-2"> </div>
|
||||
<div class="col-sm-8" ng-messages="kubernetesApplicationCreationForm.application_name.$error">
|
||||
<p class="text-muted vertical-center" ng-message="required"
|
||||
><pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> This field is required.</p
|
||||
@@ -156,10 +156,12 @@
|
||||
alphanumeric character (e.g. 'my-name', or 'abc-123').
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-muted vertical-center" ng-if="ctrl.state.alreadyExists">
|
||||
<pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
|
||||
An application with the same name already exists inside the selected namespace.
|
||||
</p>
|
||||
<div class="col-sm-8" ng-if="ctrl.state.alreadyExists">
|
||||
<p class="text-muted vertical-center">
|
||||
<pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
|
||||
An application with the same name already exists inside the selected namespace.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
@@ -709,83 +711,85 @@
|
||||
</div>
|
||||
|
||||
<!-- access policy options -->
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper !px-[15px]">
|
||||
<div
|
||||
ng-if="
|
||||
(!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) ||
|
||||
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED)
|
||||
"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id="data_access_isolated"
|
||||
ng-value="ctrl.ApplicationDataAccessPolicies.ISOLATED"
|
||||
ng-model="ctrl.formValues.DataAccessPolicy"
|
||||
ng-change="ctrl.resetDeploymentType()"
|
||||
/>
|
||||
<label for="data_access_isolated">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Isolated
|
||||
</div>
|
||||
<p>Application will be deployed as a StatefulSet with each instantiating their own data</p>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
style="color: #767676"
|
||||
ng-if="
|
||||
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED) || ctrl.state.persistedFoldersUseExistingVolumes
|
||||
"
|
||||
>
|
||||
<input type="radio" id="data_access_isolated" disabled />
|
||||
<label
|
||||
for="data_access_isolated"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="Changing the data access policy is not allowed"
|
||||
style="cursor: pointer; border-color: #767676"
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div
|
||||
ng-if="
|
||||
(!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) ||
|
||||
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED)
|
||||
"
|
||||
>
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Isolated
|
||||
</div>
|
||||
<p>Application will be deployed as a StatefulSet with each instantiating their own data</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!ctrl.state.isEdit || (ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED)">
|
||||
<input
|
||||
type="radio"
|
||||
id="data_access_shared"
|
||||
ng-value="ctrl.ApplicationDataAccessPolicies.SHARED"
|
||||
ng-model="ctrl.formValues.DataAccessPolicy"
|
||||
ng-change="ctrl.resetDeploymentType()"
|
||||
/>
|
||||
<label for="data_access_shared">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'box'" feather="true"></pr-icon>
|
||||
Shared
|
||||
</div>
|
||||
<p>Application will be deployed as a Deployment with a shared storage access</p>
|
||||
</label>
|
||||
</div>
|
||||
<div style="color: #767676" ng-if="ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED">
|
||||
<input type="radio" id="data_access_shared" disabled />
|
||||
<label
|
||||
for="data_access_shared"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="Changing the data access policy is not allowed"
|
||||
style="cursor: pointer; border-color: #767676"
|
||||
<input
|
||||
type="radio"
|
||||
id="data_access_isolated"
|
||||
ng-value="ctrl.ApplicationDataAccessPolicies.ISOLATED"
|
||||
ng-model="ctrl.formValues.DataAccessPolicy"
|
||||
ng-change="ctrl.resetDeploymentType()"
|
||||
/>
|
||||
<label for="data_access_isolated">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Isolated
|
||||
</div>
|
||||
<p>Application will be deployed as a StatefulSet with each instantiating their own data</p>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
style="color: #767676"
|
||||
ng-if="
|
||||
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED) || ctrl.state.persistedFoldersUseExistingVolumes
|
||||
"
|
||||
>
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'sliders'" feather="true"></pr-icon>
|
||||
Shared
|
||||
</div>
|
||||
<p>Application will be deployed as a Deployment with a shared storage access</p>
|
||||
</label>
|
||||
<input type="radio" id="data_access_isolated" disabled />
|
||||
<label
|
||||
for="data_access_isolated"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="Changing the data access policy is not allowed"
|
||||
style="cursor: pointer; border-color: #767676"
|
||||
>
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Isolated
|
||||
</div>
|
||||
<p>Application will be deployed as a StatefulSet with each instantiating their own data</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!ctrl.state.isEdit || (ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED)">
|
||||
<input
|
||||
type="radio"
|
||||
id="data_access_shared"
|
||||
ng-value="ctrl.ApplicationDataAccessPolicies.SHARED"
|
||||
ng-model="ctrl.formValues.DataAccessPolicy"
|
||||
ng-change="ctrl.resetDeploymentType()"
|
||||
/>
|
||||
<label for="data_access_shared">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'box'" feather="true"></pr-icon>
|
||||
Shared
|
||||
</div>
|
||||
<p>Application will be deployed as a Deployment with a shared storage access</p>
|
||||
</label>
|
||||
</div>
|
||||
<div style="color: #767676" ng-if="ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED">
|
||||
<input type="radio" id="data_access_shared" disabled />
|
||||
<label
|
||||
for="data_access_shared"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="Changing the data access policy is not allowed"
|
||||
style="cursor: pointer; border-color: #767676"
|
||||
>
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'sliders'" feather="true"></pr-icon>
|
||||
Shared
|
||||
</div>
|
||||
<p>Application will be deployed as a Deployment with a shared storage access</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -897,56 +901,58 @@
|
||||
</div>
|
||||
|
||||
<!-- deployment options -->
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper !px-[15px]">
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
id="deployment_replicated"
|
||||
ng-value="ctrl.ApplicationDeploymentTypes.REPLICATED"
|
||||
ng-model="ctrl.formValues.DeploymentType"
|
||||
data-cy="k8sAppCreate-replicatedDeploymentButton"
|
||||
/>
|
||||
<label for="deployment_replicated">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'sliders'" feather="true"></pr-icon>
|
||||
Replicated
|
||||
</div>
|
||||
<p>Run one or multiple instances of this container</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!ctrl.supportGlobalDeployment()">
|
||||
<input type="radio" id="deployment_global" disabled />
|
||||
<label
|
||||
for="deployment_global"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="The storage or access policy used for persisted folders cannot be used with this option"
|
||||
>
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Global
|
||||
</div>
|
||||
<p>Application will be deployed as a DaemonSet with an instance on each node of the cluster</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="ctrl.supportGlobalDeployment()">
|
||||
<input
|
||||
type="radio"
|
||||
id="deployment_global"
|
||||
ng-value="ctrl.ApplicationDeploymentTypes.GLOBAL"
|
||||
ng-model="ctrl.formValues.DeploymentType"
|
||||
ng-click="ctrl.unselectAutoScaler()"
|
||||
data-cy="k8sAppCreate-globalDeployButton"
|
||||
/>
|
||||
<label for="deployment_global">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Global
|
||||
</div>
|
||||
<p>Application will be deployed as a DaemonSet with an instance on each node of the sdfh</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
id="deployment_replicated"
|
||||
ng-value="ctrl.ApplicationDeploymentTypes.REPLICATED"
|
||||
ng-model="ctrl.formValues.DeploymentType"
|
||||
data-cy="k8sAppCreate-replicatedDeploymentButton"
|
||||
/>
|
||||
<label for="deployment_replicated">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'sliders'" feather="true"></pr-icon>
|
||||
Replicated
|
||||
</div>
|
||||
<p>Run one or multiple instances of this container</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!ctrl.supportGlobalDeployment()">
|
||||
<input type="radio" id="deployment_global" disabled />
|
||||
<label
|
||||
for="deployment_global"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="The storage or access policy used for persisted folders cannot be used with this option"
|
||||
>
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Global
|
||||
</div>
|
||||
<p>Application will be deployed as a DaemonSet with an instance on each node of the cluster</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="ctrl.supportGlobalDeployment()">
|
||||
<input
|
||||
type="radio"
|
||||
id="deployment_global"
|
||||
ng-value="ctrl.ApplicationDeploymentTypes.GLOBAL"
|
||||
ng-model="ctrl.formValues.DeploymentType"
|
||||
ng-click="ctrl.unselectAutoScaler()"
|
||||
data-cy="k8sAppCreate-globalDeployButton"
|
||||
/>
|
||||
<label for="deployment_global">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-cubes'"></pr-icon>
|
||||
Global
|
||||
</div>
|
||||
<p>Application will be deployed as a DaemonSet with an instance on each node of the sdfh</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1055,90 +1061,96 @@
|
||||
</div>
|
||||
|
||||
<div class="form-inline" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && ctrl.formValues.AutoScaler.IsUsed">
|
||||
<table class="table" style="margin-bottom: 0px">
|
||||
<tbody>
|
||||
<tr class="small">
|
||||
<td style="width: 33%; border: none; padding: 2px 0 2px 0">Minimum instances</td>
|
||||
<td style="width: 33%; border: none; padding: 2px 0 2px 0">Maximum instances</td>
|
||||
<td style="width: 33%; border: none; padding: 2px 0 2px 0">
|
||||
Target CPU usage (<b>%</b>)
|
||||
<portainer-tooltip message="'The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances.'">
|
||||
</portainer-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 5px 5px 0; border: none">
|
||||
<div class="input-group input-group-sm" style="width: 100%">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="auto_scaler_min"
|
||||
min="0"
|
||||
ng-max="ctrl.formValues.AutoScaler.MaxReplicas"
|
||||
ng-model="ctrl.formValues.AutoScaler.MinReplicas"
|
||||
data-cy="k8sAppCreate-autoScaleMin"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm" ng-show="kubernetesApplicationCreationForm['auto_scaler_min'].$invalid">
|
||||
<div class="small text-warning" style="margin-top: 5px">
|
||||
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_min'].$error">
|
||||
<p ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum instances is required.</p>
|
||||
<p ng-message="min"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum instances must be greater than 0.</p>
|
||||
<p ng-message="max"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum instances must be smaller than maximum instances.</p
|
||||
>
|
||||
</ng-messages>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding: 8px 5px 5px 0; border: none">
|
||||
<div class="input-group input-group-sm" style="width: 100%">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="auto_scaler_max"
|
||||
ng-min="ctrl.formValues.AutoScaler.MinReplicas"
|
||||
ng-model="ctrl.formValues.AutoScaler.MaxReplicas"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm" ng-show="kubernetesApplicationCreationForm['auto_scaler_max'].$invalid || ctrl.autoScalerOverflow()">
|
||||
<div class="small text-warning" style="margin-top: 5px">
|
||||
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_max'].$error">
|
||||
<p ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Maximum instances is required.</p>
|
||||
<p ng-message="min"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Maximum instances must be greater than minimum instances.</p
|
||||
>
|
||||
</ng-messages>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding: 8px 5px 5px 0; border: none">
|
||||
<div class="input-group input-group-sm" style="width: 100%">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="auto_scaler_cpu"
|
||||
ng-model="ctrl.formValues.AutoScaler.TargetCPUUtilization"
|
||||
min="1"
|
||||
max="100"
|
||||
required
|
||||
data-cy="k8sAppCreate-targetCPUInput"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm" ng-show="kubernetesApplicationCreationForm['auto_scaler_cpu'].$invalid">
|
||||
<div class="small text-warning" style="margin-top: 5px">
|
||||
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_cpu'].$error">
|
||||
<p ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Target CPU usage is required.</p>
|
||||
<p ng-message="min"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Target CPU usage must be greater than 0.</p>
|
||||
<p ng-message="max"><pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Target CPU usage must be smaller than 100.</p>
|
||||
</ng-messages>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
<div class="col-sm-4 pl-0">
|
||||
<label class="control-label text-left pb-2" for="auto_scaler_min">Minimum instances</label>
|
||||
<div class="input-group input-group-sm" style="width: 100%">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="auto_scaler_min"
|
||||
min="0"
|
||||
ng-max="ctrl.formValues.AutoScaler.MaxReplicas"
|
||||
ng-model="ctrl.formValues.AutoScaler.MinReplicas"
|
||||
data-cy="k8sAppCreate-autoScaleMin"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_min'].$invalid">
|
||||
<div class="small text-muted" style="margin-top: 5px">
|
||||
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_min'].$error">
|
||||
<p ng-message="required" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum instances is required.
|
||||
</p>
|
||||
<p ng-message="min" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum instances must be greater than 0.
|
||||
</p>
|
||||
<p ng-message="max" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Minimum instances must be smaller than maximum instances.
|
||||
</p>
|
||||
</ng-messages>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-4 pl-0">
|
||||
<label class="control-label text-left pb-2" for="auto_scaler_max">Maximum instances</label>
|
||||
<div class="input-group input-group-sm" style="width: 100%">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="auto_scaler_max"
|
||||
ng-min="ctrl.formValues.AutoScaler.MinReplicas"
|
||||
ng-model="ctrl.formValues.AutoScaler.MaxReplicas"
|
||||
/>
|
||||
</div>
|
||||
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_max'].$invalid || ctrl.autoScalerOverflow()">
|
||||
<div class="small text-muted" style="margin-top: 5px">
|
||||
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_max'].$error">
|
||||
<p ng-message="required" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Maximum instances is required.
|
||||
</p>
|
||||
<p ng-message="min" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Maximum instances must be greater than minimum instances.
|
||||
</p>
|
||||
</ng-messages>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-4 pl-0">
|
||||
<label class="control-label text-left pb-2" for="auto_scaler_cpu">
|
||||
Target CPU usage (<b>%</b>)
|
||||
<portainer-tooltip message="'The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances.'">
|
||||
</portainer-tooltip>
|
||||
</label>
|
||||
<div class="input-group input-group-sm" style="width: 100%">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="auto_scaler_cpu"
|
||||
ng-model="ctrl.formValues.AutoScaler.TargetCPUUtilization"
|
||||
min="1"
|
||||
max="100"
|
||||
required
|
||||
data-cy="k8sAppCreate-targetCPUInput"
|
||||
/>
|
||||
</div>
|
||||
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_cpu'].$invalid">
|
||||
<div class="small text-muted" style="margin-top: 5px">
|
||||
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_cpu'].$error">
|
||||
<p ng-message="required" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Target CPU usage is required.
|
||||
</p>
|
||||
<p ng-message="min" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Target CPU usage must be greater than 0.
|
||||
</p>
|
||||
<p ng-message="max" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon> Target CPU usage must be smaller than 100.
|
||||
</p>
|
||||
</ng-messages>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="ctrl.autoScalerOverflow()" style="margin-bottom: 10px">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
@@ -1234,39 +1246,41 @@
|
||||
</div>
|
||||
|
||||
<!-- placement policy options -->
|
||||
<div class="form-group" style="margin-bottom: 0" ng-if="ctrl.formValues.Placements.length">
|
||||
<div class="boxselector_wrapper !px-[15px]">
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
id="placement_hard"
|
||||
ng-value="ctrl.ApplicationPlacementTypes.MANDATORY"
|
||||
ng-model="ctrl.formValues.PlacementType"
|
||||
data-cy="k8sAppCreate-mandatoryPlacementButton"
|
||||
/>
|
||||
<label for="placement_hard">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'sliders'" feather="true"></pr-icon>
|
||||
Mandatory
|
||||
</div>
|
||||
<p>Schedule this application <b>ONLY</b> on nodes that match <b>ALL</b> Rules</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
id="placement_soft"
|
||||
ng-value="ctrl.ApplicationPlacementTypes.PREFERRED"
|
||||
ng-model="ctrl.formValues.PlacementType"
|
||||
data-cy="k8sAppCreate-prefferedPlacementButton"
|
||||
/>
|
||||
<label for="placement_soft">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'align-justify'" feather="true"></pr-icon>
|
||||
Preferred
|
||||
</div>
|
||||
<p>Schedule this application on nodes that match the rules if possible</p>
|
||||
</label>
|
||||
<div class="form-group" ng-if="ctrl.formValues.Placements.length">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
id="placement_hard"
|
||||
ng-value="ctrl.ApplicationPlacementTypes.MANDATORY"
|
||||
ng-model="ctrl.formValues.PlacementType"
|
||||
data-cy="k8sAppCreate-mandatoryPlacementButton"
|
||||
/>
|
||||
<label for="placement_hard">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'sliders'" feather="true"></pr-icon>
|
||||
Mandatory
|
||||
</div>
|
||||
<p>Schedule this application <b>ONLY</b> on nodes that match <b>ALL</b> Rules</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
id="placement_soft"
|
||||
ng-value="ctrl.ApplicationPlacementTypes.PREFERRED"
|
||||
ng-model="ctrl.formValues.PlacementType"
|
||||
data-cy="k8sAppCreate-prefferedPlacementButton"
|
||||
/>
|
||||
<label for="placement_soft">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'align-justify'" feather="true"></pr-icon>
|
||||
Preferred
|
||||
</div>
|
||||
<p>Schedule this application on nodes that match the rules if possible</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -87,27 +87,29 @@
|
||||
</div>
|
||||
|
||||
<!-- type options -->
|
||||
<div class="form-group px-[15px]" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" />
|
||||
<label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-filecode'"></pr-icon>
|
||||
ConfigMap
|
||||
</div>
|
||||
<p>This configuration holds non-sensitive information</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="type_secret" ng-value="ctrl.KubernetesConfigurationTypes.SECRET" ng-model="ctrl.formValues.Type" />
|
||||
<label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'lock'" feather="true"></pr-icon>
|
||||
Secret
|
||||
</div>
|
||||
<p>This configuration holds sensitive information</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" />
|
||||
<label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'svg-filecode'"></pr-icon>
|
||||
ConfigMap
|
||||
</div>
|
||||
<p>This configuration holds non-sensitive information</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="type_secret" ng-value="ctrl.KubernetesConfigurationTypes.SECRET" ng-model="ctrl.formValues.Type" />
|
||||
<label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'lock'" feather="true"></pr-icon>
|
||||
Secret
|
||||
</div>
|
||||
<p>This configuration holds sensitive information</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,23 +40,23 @@
|
||||
|
||||
<div class="dashboard-grid mx-4">
|
||||
<div ng-if="ctrl.pools" data-cy="k8sDashboard-namespaces">
|
||||
<a ui-sref="kubernetes.resourcePools">
|
||||
<a class="no-link" ui-sref="kubernetes.resourcePools">
|
||||
<dashboard-item feather-icon="true" icon="'layers'" type="'Namespace'" value="ctrl.pools.length"></dashboard-item>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.applications" data-cy="k8sDashboard-applications">
|
||||
<a ui-sref="kubernetes.applications">
|
||||
<a class="no-link" ui-sref="kubernetes.applications">
|
||||
<dashboard-item feather-icon="true" icon="'box'" type="'Application'" value="ctrl.applications.length"></dashboard-item>
|
||||
</a>
|
||||
</div>
|
||||
<div ng-if="ctrl.configurations" data-cy="k8sDashboard-configurations">
|
||||
<a ui-sref="kubernetes.configurations">
|
||||
<a class="no-link" ui-sref="kubernetes.configurations">
|
||||
<dashboard-item feather-icon="true" icon="'lock'" type="'ConfigMaps & Secret'" value="ctrl.configurations.length"></dashboard-item>
|
||||
</a>
|
||||
</div>
|
||||
<div ng-if="ctrl.volumes" data-cy="k8sDashboard-volumes">
|
||||
<a ui-sref="kubernetes.volumes">
|
||||
<a class="no-link" ui-sref="kubernetes.volumes">
|
||||
<dashboard-item feather-icon="true" icon="'database'" type="'Volume'" value="ctrl.volumes.length"></dashboard-item>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -5,9 +5,10 @@ import uuidv4 from 'uuid/v4';
|
||||
|
||||
import PortainerError from '@/portainer/error';
|
||||
import { KubernetesDeployManifestTypes, KubernetesDeployBuildMethods, KubernetesDeployRequestMethods, RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
||||
import { buildOption } from '@/portainer/components/BoxSelector';
|
||||
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
|
||||
import { isBE } from '@/portainer/feature-flags/feature-flags.service';
|
||||
import { compose, kubernetes } from '@@/BoxSelector/common-options/deployment-methods';
|
||||
import { editor, git, template, url } from '@@/BoxSelector/common-options/build-methods';
|
||||
|
||||
class KubernetesDeployController {
|
||||
/* @ngInject */
|
||||
@@ -27,15 +28,15 @@ class KubernetesDeployController {
|
||||
this.isTemplateVariablesEnabled = isBE;
|
||||
|
||||
this.deployOptions = [
|
||||
buildOption('method_kubernetes', 'svg-kubernetes', 'Kubernetes', 'Kubernetes manifest format', KubernetesDeployManifestTypes.KUBERNETES),
|
||||
buildOption('method_compose', 'svg-dockercompose', 'Compose', 'Docker compose format', KubernetesDeployManifestTypes.COMPOSE),
|
||||
{ ...kubernetes, value: KubernetesDeployManifestTypes.KUBERNETES },
|
||||
{ ...compose, value: KubernetesDeployManifestTypes.COMPOSE },
|
||||
];
|
||||
|
||||
this.methodOptions = [
|
||||
buildOption('method_repo', 'svg-git', 'Git Repository', 'Use a git repository', KubernetesDeployBuildMethods.GIT),
|
||||
buildOption('method_editor', 'svg-custom', 'Web editor', 'Use our Web editor', KubernetesDeployBuildMethods.WEB_EDITOR),
|
||||
buildOption('method_url', 'svg-url', 'URL', 'Specify a URL to a file', KubernetesDeployBuildMethods.URL),
|
||||
buildOption('method_template', 'svg-template', 'Custom Template', 'Use a custom template', KubernetesDeployBuildMethods.CUSTOM_TEMPLATE),
|
||||
{ ...git, value: KubernetesDeployBuildMethods.GIT },
|
||||
{ ...editor, value: KubernetesDeployBuildMethods.WEB_EDITOR },
|
||||
{ ...url, value: KubernetesDeployBuildMethods.URL },
|
||||
{ ...template, value: KubernetesDeployBuildMethods.CUSTOM_TEMPLATE },
|
||||
];
|
||||
|
||||
this.state = {
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
<p class="vertical-center" ng-if="$ctrl.state.hasPrefixKube"
|
||||
><pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> Prefix "kube-" is reserved for Kubernetes system namespaces.</p
|
||||
>
|
||||
<p class="vertical-center" ng-if="$ctrl.state.isAlreadyExist">
|
||||
<pr-icon icon="'alert-triangle'" feather="true" mode="'warning'"></pr-icon> A namespace with the same name already exists.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -47,16 +47,12 @@ export function EditDetails({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<BoxSelector
|
||||
radioName={withNamespace('ownership')}
|
||||
value={values.ownership}
|
||||
options={options}
|
||||
onChange={(ownership) => handleChangeOwnership(ownership)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<BoxSelector
|
||||
radioName={withNamespace('ownership')}
|
||||
value={values.ownership}
|
||||
options={options}
|
||||
onChange={(ownership) => handleChangeOwnership(ownership)}
|
||||
/>
|
||||
|
||||
{values.ownership === ResourceControlOwnership.RESTRICTED && (
|
||||
<div aria-label="extra-options">
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ownershipIcon } from '@/portainer/filters/filters';
|
||||
import { Team } from '@/portainer/teams/types';
|
||||
|
||||
import { BoxSelectorOption } from '@@/BoxSelector/types';
|
||||
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
|
||||
|
||||
import { ResourceControlOwnership } from '../types';
|
||||
|
||||
@@ -15,7 +16,7 @@ const publicOption: BoxSelectorOption<ResourceControlOwnership> = {
|
||||
id: 'access_public',
|
||||
description:
|
||||
'I want any user with access to this environment to be able to manage this resource',
|
||||
icon: ownershipIcon('public'),
|
||||
icon: <BadgeIcon icon={ownershipIcon('public')} />,
|
||||
};
|
||||
|
||||
export function useOptions(
|
||||
@@ -40,14 +41,14 @@ function adminOptions() {
|
||||
return [
|
||||
buildOption(
|
||||
'access_administrators',
|
||||
ownershipIcon('administrators'),
|
||||
<BadgeIcon icon={ownershipIcon('administrators')} />,
|
||||
'Administrators',
|
||||
'I want to restrict the management of this resource to administrators only',
|
||||
ResourceControlOwnership.ADMINISTRATORS
|
||||
),
|
||||
buildOption(
|
||||
'access_restricted',
|
||||
ownershipIcon('restricted'),
|
||||
<BadgeIcon icon={ownershipIcon('restricted')} />,
|
||||
'Restricted',
|
||||
'I want to restrict the management of this resource to a set of users and/or teams',
|
||||
ResourceControlOwnership.RESTRICTED
|
||||
@@ -58,7 +59,7 @@ function nonAdminOptions(teams?: Team[]) {
|
||||
return _.compact([
|
||||
buildOption(
|
||||
'access_private',
|
||||
ownershipIcon('private'),
|
||||
<BadgeIcon icon={ownershipIcon('private')} />,
|
||||
'Private',
|
||||
'I want to this resource to be manageable by myself only',
|
||||
ResourceControlOwnership.PRIVATE
|
||||
@@ -67,7 +68,7 @@ function nonAdminOptions(teams?: Team[]) {
|
||||
teams.length > 0 &&
|
||||
buildOption(
|
||||
'access_restricted',
|
||||
ownershipIcon('restricted'),
|
||||
<BadgeIcon icon={ownershipIcon('restricted')} />,
|
||||
'Restricted',
|
||||
teams.length === 1
|
||||
? `I want any member of my team (${teams[0].Name}) to be able to manage this resource`
|
||||
@@ -1,15 +1,16 @@
|
||||
import { FeatureId } from '@/portainer/feature-flags/enums';
|
||||
|
||||
import { BoxSelectorOption } from '@@/BoxSelector/types';
|
||||
import { IconProps } from '@@/Icon';
|
||||
|
||||
export function buildOption<T extends number | string>(
|
||||
id: string,
|
||||
icon: string,
|
||||
icon: IconProps['icon'],
|
||||
label: string,
|
||||
description: string,
|
||||
value: T,
|
||||
feature?: FeatureId,
|
||||
featherIcon?: boolean
|
||||
featherIcon?: IconProps['featherIcon']
|
||||
): BoxSelectorOption<T> {
|
||||
return { id, icon, label, description, value, feature, featherIcon };
|
||||
}
|
||||
|
||||
@@ -15,51 +15,54 @@
|
||||
</div>
|
||||
<!-- !access-control-switch -->
|
||||
<!-- restricted-access -->
|
||||
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper px-[15px]">
|
||||
<div ng-if="$ctrl.isAdmin">
|
||||
<input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" />
|
||||
<label for="access_administrators" data-cy="portainer-selectAdminAccess">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'eye-off'" feather="true"></pr-icon>
|
||||
Administrators
|
||||
</div>
|
||||
<p class="boxselector_content">I want to restrict the management of this resource to administrators only</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="$ctrl.isAdmin">
|
||||
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
|
||||
<label for="access_restricted" data-cy="portainer-selectRestrictedAccess">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'users'" feather="true"></pr-icon>
|
||||
Restricted
|
||||
</div>
|
||||
<p class="boxselector_content"> I want to restrict the management of this resource to a set of users and/or teams </p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!$ctrl.isAdmin">
|
||||
<input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private" />
|
||||
<label for="access_private">
|
||||
<div class="boxselector_header">
|
||||
<i ng-class="'private' | ownershipicon" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
Private
|
||||
</div>
|
||||
<p> I want to this resource to be manageable by myself only </p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 0">
|
||||
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
|
||||
<label for="access_restricted">
|
||||
<div class="boxselector_header">
|
||||
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
Restricted
|
||||
</div>
|
||||
<p ng-if="$ctrl.availableTeams.length === 1">
|
||||
I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b
|
||||
>) to be able to manage this resource
|
||||
</p>
|
||||
<p ng-if="$ctrl.availableTeams.length > 1"> I want to restrict the management of this resource to one or more of my teams </p>
|
||||
</label>
|
||||
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div ng-if="$ctrl.isAdmin">
|
||||
<input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" />
|
||||
<label for="access_administrators" data-cy="portainer-selectAdminAccess">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'eye-off'" feather="true"></pr-icon>
|
||||
Administrators
|
||||
</div>
|
||||
<p class="boxselector_content">I want to restrict the management of this resource to administrators only</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="$ctrl.isAdmin">
|
||||
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
|
||||
<label for="access_restricted" data-cy="portainer-selectRestrictedAccess">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'users'" feather="true"></pr-icon>
|
||||
Restricted
|
||||
</div>
|
||||
<p class="boxselector_content"> I want to restrict the management of this resource to a set of users and/or teams </p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!$ctrl.isAdmin">
|
||||
<input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private" />
|
||||
<label for="access_private">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'eye-off'" feather="true"></pr-icon>
|
||||
Private
|
||||
</div>
|
||||
<p> I want to this resource to be manageable by myself only </p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 0">
|
||||
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
|
||||
<label for="access_restricted">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'users'" feather="true"></pr-icon>
|
||||
|
||||
Restricted
|
||||
</div>
|
||||
<p ng-if="$ctrl.availableTeams.length === 1">
|
||||
I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b
|
||||
>) to be able to manage this resource
|
||||
</p>
|
||||
<p ng-if="$ctrl.availableTeams.length > 1"> I want to restrict the management of this resource to one or more of my teams </p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<option value="" label="Select a Custom template" disabled selected="selected"> </option>
|
||||
</select>
|
||||
<span class="small text-muted pt-[7px]" ng-if="!$ctrl.templates.length">
|
||||
No custom templates are available. Head over to the <a class="hyperlink" ui-state="$ctrl.newTemplatePath">custom template view</a> to create one.
|
||||
No custom templates are available. Head over to the <a ui-state="$ctrl.newTemplatePath">custom template view</a> to create one.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,19 +40,6 @@
|
||||
@apply text-blue-7;
|
||||
}
|
||||
|
||||
.datatable tr > td a:not(.btn) {
|
||||
color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.datatable tr > td a:not(.btn):hover,
|
||||
.datatable tr > td a:not(.btn):focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.datatable tr > td a.actions {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.toolBar .actionBar {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
@@ -77,10 +77,12 @@
|
||||
<input id="select_{{ $index }}" type="checkbox" disabled />
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<span>DockerHub (anonymous)</span>
|
||||
<span><default-registry-name></default-registry-name></span>
|
||||
</td>
|
||||
<td> <default-registry-domain></default-registry-domain> </td>
|
||||
<td>
|
||||
<default-registry-action ng-if="$ctrl.isAdmin && !$ctrl.endpointType"></default-registry-action>
|
||||
</td>
|
||||
<td> docker.io </td>
|
||||
<td> - </td>
|
||||
</tr>
|
||||
<tr
|
||||
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||
|
||||
@@ -24,47 +24,49 @@
|
||||
</div>
|
||||
<div class="form-group"></div>
|
||||
<!-- endpoint-tls-mode -->
|
||||
<div class="form-group" style="margin-bottom: 0" ng-if="$ctrl.formData.TLS">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" />
|
||||
<label for="tls_client_ca">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS with server and client verification
|
||||
</div>
|
||||
<p>Use client certificates and server verification</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca" />
|
||||
<label for="tls_client_noca">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS with client verification only
|
||||
</div>
|
||||
<p>Use client certificates without server verification</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca" />
|
||||
<label for="tls_ca">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS with server verification only
|
||||
</div>
|
||||
<p>Only verify the server certificate</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only" />
|
||||
<label for="tls_only">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS only
|
||||
</div>
|
||||
<p>No server/client verification</p>
|
||||
</label>
|
||||
<div class="form-group" ng-if="$ctrl.formData.TLS">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" />
|
||||
<label for="tls_client_ca">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS with server and client verification
|
||||
</div>
|
||||
<p>Use client certificates and server verification</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca" />
|
||||
<label for="tls_client_noca">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS with client verification only
|
||||
</div>
|
||||
<p>Use client certificates without server verification</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca" />
|
||||
<label for="tls_ca">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS with server verification only
|
||||
</div>
|
||||
<p>Only verify the server certificate</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only" />
|
||||
<label for="tls_only">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'shield'" feather="true"></pr-icon>
|
||||
TLS only
|
||||
</div>
|
||||
<p>No server/client verification</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
class WebEditorFormController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
constructor(BROWSER_OS_PLATFORM) {
|
||||
this.editorUpdate = this.editorUpdate.bind(this);
|
||||
this.BROWSER_OS_PLATFORM = BROWSER_OS_PLATFORM;
|
||||
}
|
||||
|
||||
editorUpdate(cm) {
|
||||
|
||||
@@ -1,7 +1,41 @@
|
||||
<ng-form name="$ctrl.webEditorForm">
|
||||
<div class="web-editor overflow-auto">
|
||||
<div ng-if="!$ctrl.hideTitle" class="col-sm-12 form-section-title">Web editor</div>
|
||||
<div class="trancluded-item form-group col-sm-12 col-lg-12 text-muted small" ng-transclude="description"></div>
|
||||
<div ng-if="!$ctrl.hideTitle" class="col-sm-12 form-section-title pr-0"
|
||||
>Web editor
|
||||
<div class="text-muted small vertical-center float-right mt-0">
|
||||
<span ng-if="$ctrl.BROWSER_OS_PLATFORM !== 'mac'" class="vertical-center">Ctrl+F for search</span>
|
||||
<span ng-if="$ctrl.BROWSER_OS_PLATFORM === 'mac'" class="vertical-center">Cmd+F for search</span>
|
||||
<portainer-tooltip
|
||||
ng-if="$ctrl.BROWSER_OS_PLATFORM !== 'mac'"
|
||||
message="'Ctrl+F - Start searching <br />
|
||||
Ctrl+G - Find next <br />
|
||||
Ctrl+Shift+G - Find previous <br />
|
||||
Ctrl+Shift+F - Replace <br />
|
||||
Ctrl+Shift+R - Replace all <br />
|
||||
Alt+G - Jump to line <br />
|
||||
Alt+F - Persistent search: <br />
|
||||
Enter - Find next <br />
|
||||
Shift+Enter - Find previous <br />'"
|
||||
class-name="'[&>span]:!text-left'"
|
||||
>
|
||||
</portainer-tooltip>
|
||||
<portainer-tooltip
|
||||
ng-if="$ctrl.BROWSER_OS_PLATFORM === 'mac'"
|
||||
message="'Cmd+F - Start searching <br />
|
||||
Cmd+G - Find next <br />
|
||||
Cmd+Shift+G - Find previous <br />
|
||||
Cmd+Option+F - Replace <br />
|
||||
Cmd+Option+R - Replace all <br />
|
||||
Option+G - Jump to line <br />
|
||||
Option+F - Persistent search: <br />
|
||||
Enter - Find next <br />
|
||||
Shift+Enter - Find previous <br />'"
|
||||
class-name="'[&>span]:!text-left'"
|
||||
>
|
||||
</portainer-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trancluded-item form-group col-sm-9 col-lg-10 text-muted small" ng-transclude="description"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 col-lg-12">
|
||||
<code-editor
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
</div>
|
||||
<div class="small vertical-center" ng-if="$ctrl.model.RepositoryAutomaticUpdates">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
|
||||
<span class="text-muted">Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.</span>
|
||||
<span class="text-muted"
|
||||
>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption. Do you wish to continue?</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>
|
||||
|
||||
@@ -48,14 +48,24 @@
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive vertical-center" ng-if="!$ctrl.state.overrideConfiguration" ng-click="$ctrl.state.overrideConfiguration = true;">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link btn-sm hover:no-underline !ml-0 p-0"
|
||||
ng-if="!$ctrl.state.overrideConfiguration"
|
||||
ng-click="$ctrl.state.overrideConfiguration = true;"
|
||||
>
|
||||
<pr-icon icon="'tool'" feather="true"></pr-icon>
|
||||
Override default configuration
|
||||
</a>
|
||||
<a class="small interactive vertical-center" ng-if="$ctrl.state.overrideConfiguration" ng-click="$ctrl.state.overrideConfiguration = false; $ctrl.resetDefaults()">
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link btn-sm hover:no-underline !ml-0 p-0"
|
||||
ng-if="$ctrl.state.overrideConfiguration"
|
||||
ng-click="$ctrl.state.overrideConfiguration = false; $ctrl.resetDefaults()"
|
||||
>
|
||||
<pr-icon icon="'settings'" feather="true"></pr-icon>
|
||||
Use default configuration
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -103,12 +103,12 @@ class StackRedeployGitFormController {
|
||||
|
||||
async submit() {
|
||||
const tplCrop =
|
||||
'<div>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption.</div>' +
|
||||
'<div"><div style="position: absolute; right: 110px; top: 68px; z-index: 999">' +
|
||||
'<div>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption. Do you wish to continue?</div>' +
|
||||
'<div"><div style="position: absolute; right: 5px; top: 84px; z-index: 999">' +
|
||||
'<be-feature-indicator feature="stackPullImageFeature"></be-feature-indicator></div></div>';
|
||||
const template = angular.element(tplCrop);
|
||||
const html = this.$compile(template)(this.$scope);
|
||||
this.ModalService.confirmStackUpdate(html, true, true, 'btn-warning', async (result) => {
|
||||
this.ModalService.confirmStackUpdate(html, true, false, 'btn-warning', async (result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="sliders" feather-icon="true" title-text="User theme"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="theme-panel">
|
||||
<!-- Theme Selector-->
|
||||
<form class="form-horizontal">
|
||||
<box-selector radio-name="'theme'" value="$ctrl.state.userTheme" options="$ctrl.state.availableThemes" on-change="($ctrl.setTheme)"></box-selector>
|
||||
<!-- !Theme -->
|
||||
</form>
|
||||
<p class="mt-2 vertical-center">
|
||||
<pr-icon icon="'alert-circle'" class-name="'icon-primary'" feather="true"></pr-icon>
|
||||
|
||||
@@ -105,11 +105,11 @@ export interface EnvironmentOptions {
|
||||
url?: string;
|
||||
publicUrl?: string;
|
||||
meta?: EnvironmentMetadata;
|
||||
checkinInterval?: number;
|
||||
azure?: AzureSettings;
|
||||
tls?: TLSSettings;
|
||||
isEdgeDevice?: boolean;
|
||||
gpus?: Gpu[];
|
||||
pollFrequency?: number;
|
||||
}
|
||||
|
||||
interface CreateRemoteEnvironment {
|
||||
@@ -130,7 +130,7 @@ export async function createRemoteEnvironment({
|
||||
}: CreateRemoteEnvironment) {
|
||||
return createEnvironment(name, creationType, {
|
||||
...options,
|
||||
url: `${url}`,
|
||||
url: `tcp://${url}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,6 +175,7 @@ export function createEdgeAgentEnvironment({
|
||||
meta = { tagIds: [] },
|
||||
gpus = [],
|
||||
isEdgeDevice,
|
||||
pollFrequency,
|
||||
}: CreateEdgeAgentEnvironment) {
|
||||
return createEnvironment(
|
||||
name,
|
||||
@@ -187,6 +188,7 @@ export function createEdgeAgentEnvironment({
|
||||
},
|
||||
gpus,
|
||||
isEdgeDevice,
|
||||
pollFrequency,
|
||||
...meta,
|
||||
}
|
||||
);
|
||||
@@ -211,7 +213,7 @@ async function createEnvironment(
|
||||
PublicURL: options.publicUrl,
|
||||
GroupID: groupId,
|
||||
TagIds: arrayToJson(tagIds),
|
||||
CheckinInterval: options.checkinInterval,
|
||||
CheckinInterval: options.pollFrequency,
|
||||
IsEdgeDevice: options.isEdgeDevice,
|
||||
Gpus: arrayToJson(options.gpus),
|
||||
};
|
||||
|
||||
@@ -30,4 +30,5 @@ export enum FeatureId {
|
||||
STACK_WEBHOOK = 'stack-webhook',
|
||||
CONTAINER_WEBHOOK = 'container-webhook',
|
||||
POD_SECURITY_POLICY_CONSTRAINT = 'pod-security-policy-constraint',
|
||||
HIDE_DOCKER_HUB_ANONYMOUS = 'hide-docker-hub-anonymous',
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export async function init(edition: Edition) {
|
||||
[FeatureId.STACK_WEBHOOK]: Edition.BE,
|
||||
[FeatureId.CONTAINER_WEBHOOK]: Edition.BE,
|
||||
[FeatureId.POD_SECURITY_POLICY_CONSTRAINT]: Edition.BE,
|
||||
[FeatureId.HIDE_DOCKER_HUB_ANONYMOUS]: Edition.BE,
|
||||
};
|
||||
|
||||
state.currentEdition = currentEdition;
|
||||
|
||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
||||
import _ from 'lodash-es';
|
||||
import filesize from 'filesize';
|
||||
|
||||
import { Eye, EyeOff, Users } from 'react-feather';
|
||||
import { ResourceControlOwnership as RCO } from '@/portainer/access-control/types';
|
||||
|
||||
export function truncateLeftRight(text, max, left, right) {
|
||||
@@ -106,13 +107,13 @@ export function environmentTypeIcon(type) {
|
||||
export function ownershipIcon(ownership) {
|
||||
switch (ownership) {
|
||||
case RCO.PRIVATE:
|
||||
return 'eye-off';
|
||||
return EyeOff;
|
||||
case RCO.ADMINISTRATORS:
|
||||
return 'eye-off';
|
||||
return EyeOff;
|
||||
case RCO.RESTRICTED:
|
||||
return 'users';
|
||||
return Users;
|
||||
default:
|
||||
return 'eye';
|
||||
return Eye;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,12 +46,11 @@ export function EnvironmentItem({ environment, onClick, groupName }: Props) {
|
||||
<div className={styles.root}>
|
||||
<button
|
||||
type="button"
|
||||
color="link"
|
||||
onClick={() => onClick(environment)}
|
||||
className={styles.wrapperButton}
|
||||
>
|
||||
<Link
|
||||
className={clsx('blocklist-item', styles.item)}
|
||||
className={clsx('blocklist-item no-link', styles.item)}
|
||||
to={route}
|
||||
params={{
|
||||
endpointId: environment.Id,
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
.refresh-environments-button {
|
||||
margin-left: 0 !important;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
@@ -73,7 +72,6 @@
|
||||
.filterSearchbar {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.filterSearchbar input[type='text'] {
|
||||
|
||||
@@ -130,7 +130,7 @@ export function EnvironmentList({ onClickItem, onRefresh }: Props) {
|
||||
status: statusFilter,
|
||||
tagIds: tagFilter?.length ? tagFilter : undefined,
|
||||
groupIds: groupFilter,
|
||||
edgeDevice: getEdgeDeviceFilter(connectionTypes.map((p) => p.value)),
|
||||
edgeDevice: false,
|
||||
tagsPartialMatch: true,
|
||||
agentVersions: agentVersions.map((a) => a.value),
|
||||
};
|
||||
@@ -192,8 +192,10 @@ export function EnvironmentList({ onClickItem, onRefresh }: Props) {
|
||||
<Button
|
||||
onClick={onRefresh}
|
||||
data-cy="home-refreshEndpointsButton"
|
||||
size="medium"
|
||||
color="secondary"
|
||||
className={clsx(
|
||||
'vertical-center',
|
||||
'vertical-center !ml-0',
|
||||
styles.refreshEnvironmentsButton
|
||||
)}
|
||||
>
|
||||
@@ -215,7 +217,7 @@ export function EnvironmentList({ onClickItem, onRefresh }: Props) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.filterSearchbar}>
|
||||
<div className={clsx(styles.filterSearchbar, 'ml-3')}>
|
||||
<FilterSearchBar
|
||||
value={searchBarValue}
|
||||
onChange={setSearchBarValue}
|
||||
@@ -331,19 +333,6 @@ export function EnvironmentList({ onClickItem, onRefresh }: Props) {
|
||||
</>
|
||||
);
|
||||
|
||||
function getEdgeDeviceFilter(connectionTypes: ConnectionType[]) {
|
||||
// show both types of edge agent if both are selected or if no connection type is selected
|
||||
if (
|
||||
connectionTypes.length === 0 ||
|
||||
(connectionTypes.includes(ConnectionType.EdgeAgent) &&
|
||||
connectionTypes.includes(ConnectionType.EdgeDevice))
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return connectionTypes.includes(ConnectionType.EdgeDevice);
|
||||
}
|
||||
|
||||
function getTypes(
|
||||
platformTypes: PlatformType[],
|
||||
connectionTypes: ConnectionType[]
|
||||
@@ -495,7 +484,6 @@ function getConnectionTypeOptions(platformTypes: Filter<PlatformType>[]) {
|
||||
{ value: ConnectionType.API, label: 'API' },
|
||||
{ value: ConnectionType.Agent, label: 'Agent' },
|
||||
{ value: ConnectionType.EdgeAgent, label: 'Edge Agent' },
|
||||
{ value: ConnectionType.EdgeDevice, label: 'Edge Device' },
|
||||
];
|
||||
|
||||
if (platformTypes.length === 0) {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.kubeconfig-button {
|
||||
padding: 8px 15px;
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { Query } from '@/portainer/environments/queries/useEnvironmentList';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
import styles from './KubeconfigButton.module.css';
|
||||
import { KubeconfigPrompt } from './KubeconfigPrompt';
|
||||
|
||||
import '@reach/dialog/styles.css';
|
||||
@@ -30,8 +29,8 @@ export function KubeconfigButton({ environments, envQueryParams }: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button className={styles.kubeconfigButton} onClick={handleClick}>
|
||||
<Download className="feather-icon-white" aria-hidden="true" />{' '}
|
||||
<Button onClick={handleClick} size="medium" className="!ml-3">
|
||||
<Download className="feather icon-white" aria-hidden="true" />{' '}
|
||||
Kubeconfig
|
||||
</Button>
|
||||
{prompt()}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { X } from 'react-feather';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
import { DialogOverlay } from '@reach/dialog';
|
||||
|
||||
@@ -59,7 +61,7 @@ export function KubeconfigPrompt({
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" onClick={onClose}>
|
||||
×
|
||||
<X />
|
||||
</button>
|
||||
<h5 className="modal-title">Download kubeconfig file</h5>
|
||||
</div>
|
||||
@@ -74,16 +76,24 @@ export function KubeconfigPrompt({
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<Checkbox
|
||||
id="settings-container-truncate-nae"
|
||||
label="Select all (in this page)"
|
||||
checked={isAllPageSelected}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
<div className="h-8 flex items-center">
|
||||
<Checkbox
|
||||
id="settings-container-truncate-name"
|
||||
label="Select all (in this page)"
|
||||
checked={isAllPageSelected}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
</div>
|
||||
<div className="datatable">
|
||||
<div className="bootbox-checkbox-list">
|
||||
{environments.map((env) => (
|
||||
<div className={styles.checkbox}>
|
||||
<div
|
||||
key={env.Id}
|
||||
className={clsx(
|
||||
styles.checkbox,
|
||||
'h-8 flex items-center pt-1'
|
||||
)}
|
||||
>
|
||||
<Checkbox
|
||||
id={`${env.Id}`}
|
||||
label={`${env.Name} (${env.URL})`}
|
||||
@@ -95,7 +105,7 @@ export function KubeconfigPrompt({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="footer">
|
||||
<div className="pt-3 flex justify-end w-full">
|
||||
<PaginationControls
|
||||
showAll={totalCount <= 100}
|
||||
page={page}
|
||||
@@ -111,7 +121,9 @@ export function KubeconfigPrompt({
|
||||
<Button onClick={onClose} color="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleDownload}>Download File</Button>
|
||||
<Button onClick={handleDownload} disabled={selectionSize < 1}>
|
||||
Download File
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,7 +155,7 @@ export function KubeconfigPrompt({
|
||||
}
|
||||
|
||||
export function expiryMessage(expiry: string) {
|
||||
const prefix = 'Kubeconfig file will';
|
||||
const prefix = 'The kubeconfig file will';
|
||||
switch (expiry) {
|
||||
case '24h':
|
||||
return `${prefix} expire in 1 day.`;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useLocalStorage } from '@/portainer/hooks/useLocalStorage';
|
||||
interface UIState {
|
||||
dismissedInfoPanels: Record<string, string>;
|
||||
dismissedInfoHash: string;
|
||||
dismissedUpdateVersion: string;
|
||||
}
|
||||
|
||||
type UIStateService = [UIState, (state: UIState) => void];
|
||||
|
||||
@@ -36,6 +36,7 @@ export function PublicSettingsViewModel(settings) {
|
||||
this.KubeconfigExpiry = settings.KubeconfigExpiry;
|
||||
this.Features = settings.Features;
|
||||
this.Edge = new EdgeSettingsViewModel(settings.Edge);
|
||||
this.DefaultRegistry = settings.DefaultRegistry;
|
||||
}
|
||||
|
||||
export function InternalAuthSettingsViewModel(data) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Edit } from 'react-feather';
|
||||
|
||||
import { FeatureId } from '@/portainer/feature-flags/enums';
|
||||
import Microsoft from '@/assets/ico/vendor/microsoft.svg?c';
|
||||
import Google from '@/assets/ico/vendor/google.svg?c';
|
||||
import Github from '@/assets/ico/vendor/github.svg?c';
|
||||
import Custom from '@/assets/ico/custom.svg?c';
|
||||
|
||||
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
|
||||
|
||||
export const options = [
|
||||
{
|
||||
@@ -32,7 +35,7 @@ export const options = [
|
||||
},
|
||||
{
|
||||
id: 'custom',
|
||||
icon: Custom,
|
||||
icon: <BadgeIcon icon={Edit} />,
|
||||
label: 'Custom',
|
||||
description: 'Custom OAuth provider',
|
||||
value: 'custom',
|
||||
|
||||
@@ -15,6 +15,7 @@ import { TableColumnHeaderAngular } from '@@/datatables/TableHeaderCell';
|
||||
import { DashboardItem } from '@@/DashboardItem';
|
||||
import { SearchBar } from '@@/datatables/SearchBar';
|
||||
import { FallbackImage } from '@@/FallbackImage';
|
||||
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
|
||||
|
||||
import { fileUploadField } from './file-upload-field';
|
||||
import { switchField } from './switch-field';
|
||||
@@ -28,7 +29,7 @@ export const componentsModule = angular
|
||||
)
|
||||
.component(
|
||||
'portainerTooltip',
|
||||
react2angular(Tooltip, ['message', 'position'])
|
||||
react2angular(Tooltip, ['message', 'position', 'className'])
|
||||
)
|
||||
.component('fileUploadField', fileUploadField)
|
||||
.component('porSwitchField', switchField)
|
||||
@@ -49,7 +50,14 @@ export const componentsModule = angular
|
||||
.component('viewLoading', r2a(ViewLoading, ['message']))
|
||||
.component(
|
||||
'pageHeader',
|
||||
r2a(PageHeader, ['title', 'breadcrumbs', 'loading', 'onReload', 'reload'])
|
||||
r2a(PageHeader, [
|
||||
'id',
|
||||
'title',
|
||||
'breadcrumbs',
|
||||
'loading',
|
||||
'onReload',
|
||||
'reload',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'fallbackImage',
|
||||
@@ -76,4 +84,8 @@ export const componentsModule = angular
|
||||
.component(
|
||||
'datatableSearchbar',
|
||||
r2a(SearchBar, ['data-cy', 'onChange', 'value', 'placeholder'])
|
||||
)
|
||||
.component(
|
||||
'boxSelectorBadgeIcon',
|
||||
react2angular(BadgeIcon, ['featherIcon', 'icon'])
|
||||
).name;
|
||||
|
||||
@@ -2,11 +2,19 @@ import angular from 'angular';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { CreateAccessToken } from '@/react/portainer/account/CreateAccessTokenView';
|
||||
import {
|
||||
DefaultRegistryAction,
|
||||
DefaultRegistryDomain,
|
||||
DefaultRegistryName,
|
||||
} from '@/react/portainer/registries/ListView/DefaultRegistry';
|
||||
|
||||
import { wizardModule } from './wizard';
|
||||
|
||||
export const viewsModule = angular
|
||||
.module('portainer.app.react.views', [wizardModule])
|
||||
.component('defaultRegistryName', r2a(DefaultRegistryName, []))
|
||||
.component('defaultRegistryAction', r2a(DefaultRegistryAction, []))
|
||||
.component('defaultRegistryDomain', r2a(DefaultRegistryDomain, []))
|
||||
.component(
|
||||
'createAccessToken',
|
||||
r2a(CreateAccessToken, ['onSubmit', 'onError'])
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'codemirror/addon/search/search.js';
|
||||
import 'codemirror/addon/search/searchcursor.js';
|
||||
import 'codemirror/addon/search/jump-to-line.js';
|
||||
import 'codemirror/addon/dialog/dialog.js';
|
||||
import 'codemirror/addon/dialog/dialog.css';
|
||||
import './codeMirrorDialog.css';
|
||||
|
||||
angular.module('portainer.app').factory('CodeMirrorService', function CodeMirrorService() {
|
||||
'use strict';
|
||||
|
||||
49
app/portainer/services/codeMirrorDialog.css
Normal file
49
app/portainer/services/codeMirrorDialog.css
Normal file
@@ -0,0 +1,49 @@
|
||||
/* styles from https://github.com/codemirror/codemirror5/blob/master/addon/dialog/dialog.css with the button styles updated */
|
||||
|
||||
.CodeMirror-dialog {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: inherit;
|
||||
z-index: 15;
|
||||
padding: 0.1em 0.8em;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-top {
|
||||
border-bottom: 1px solid #eee;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-bottom {
|
||||
border-top: 1px solid #eee;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
width: 20em;
|
||||
color: inherit;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog button {
|
||||
/* apply styles from btn-default */
|
||||
@apply bg-white border-gray-5 text-gray-9;
|
||||
@apply hover:bg-gray-3 hover:border-gray-5 hover:text-gray-10;
|
||||
/* dark mode */
|
||||
@apply th-dark:bg-gray-warm-10 th-dark:border-gray-warm-7 th-dark:text-gray-warm-4;
|
||||
@apply th-dark:hover:bg-gray-warm-9 th-dark:hover:border-gray-6 th-dark:hover:text-gray-warm-4;
|
||||
/* highcontrast mode */
|
||||
@apply th-highcontrast:bg-gray-warm-10 th-highcontrast:border-gray-warm-7 th-highcontrast:text-white;
|
||||
@apply th-highcontrast:hover:bg-gray-warm-9 th-highcontrast:hover:border-gray-6 th-highcontrast:hover:text-white;
|
||||
|
||||
@apply font-sans;
|
||||
@apply border-solid border;
|
||||
font-size: 85%;
|
||||
padding: 0px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -167,8 +167,8 @@ export function confirmStackUpdate(
|
||||
confirmButtonClassName: string | undefined,
|
||||
callback: PromptCallback
|
||||
) {
|
||||
const sanitizedMessage = sanitize(message);
|
||||
|
||||
const sanitizedMessage =
|
||||
typeof message === 'string' ? sanitize(message) : message;
|
||||
const box = prompt({
|
||||
title: buildTitle('Are you sure?'),
|
||||
inputType: 'checkbox',
|
||||
@@ -197,7 +197,8 @@ export function confirmStackUpdate(
|
||||
'position: relative; display: block; margin-top: 10px; margin-bottom: 10px;'
|
||||
);
|
||||
const checkboxLabel = box.find('.form-check-label');
|
||||
checkboxLabel.addClass('switch box-selector-item limited business');
|
||||
checkboxLabel.addClass('switch box-selector-item limited business mt-4');
|
||||
checkboxLabel.prop('style', 'width: 100%');
|
||||
const switchEle = checkboxLabel.find('i');
|
||||
switchEle.prop('style', 'margin-left:20px');
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ function StateManagerFactory(
|
||||
dismissedInfoPanels: {},
|
||||
dismissedInfoHash: '',
|
||||
timesPasswordChangeSkipped: {},
|
||||
dismissedUpdateVersion: '',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
</label>
|
||||
<label class="switch ml-7 my-0" ng-class="{ 'business limited': $ctrl.isLimitedFeatureSelfContained }">
|
||||
<input id="admin-auto-populate" ng-disabled="!$ctrl.enableAssignAdminGroup" name="admin-auto-populate" type="checkbox" ng-model="$ctrl.settings.AdminAutoPopulate" />
|
||||
<span class="slider round"></span>
|
||||
<span class="slider round before:content-['']"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Edit } from 'react-feather';
|
||||
|
||||
import { FeatureId } from '@/portainer/feature-flags/enums';
|
||||
import Openldap from '@/assets/ico/vendor/openldap.svg?c';
|
||||
import Custom from '@/assets/ico/custom.svg?c';
|
||||
|
||||
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
|
||||
|
||||
const SERVER_TYPES = {
|
||||
CUSTOM: 0,
|
||||
@@ -11,7 +14,7 @@ const SERVER_TYPES = {
|
||||
export const options = [
|
||||
{
|
||||
id: 'ldap_custom',
|
||||
icon: Custom,
|
||||
icon: <BadgeIcon icon={Edit} />,
|
||||
label: 'Custom',
|
||||
value: SERVER_TYPES.CUSTOM,
|
||||
},
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
getSettings,
|
||||
updateSettings,
|
||||
getPublicSettings,
|
||||
updateDefaultRegistry,
|
||||
} from './settings.service';
|
||||
import { Settings } from './types';
|
||||
import { DefaultRegistry, Settings } from './types';
|
||||
|
||||
export function usePublicSettings<T = PublicSettingsViewModel>({
|
||||
enabled,
|
||||
@@ -51,3 +52,15 @@ export function useUpdateSettingsMutation() {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function useUpdateDefaultRegistrySettingsMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
(payload: Partial<DefaultRegistry>) => updateDefaultRegistry(payload),
|
||||
mutationOptions(
|
||||
withInvalidate(queryClient, [['settings']]),
|
||||
withError('Unable to update default registry settings')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { PublicSettingsViewModel } from '@/portainer/models/settings';
|
||||
|
||||
import axios, { parseAxiosError } from '../services/axios';
|
||||
|
||||
import { PublicSettingsResponse, Settings } from './types';
|
||||
import { DefaultRegistry, PublicSettingsResponse, Settings } from './types';
|
||||
|
||||
export async function getPublicSettings() {
|
||||
try {
|
||||
@@ -38,6 +38,19 @@ export async function updateSettings(settings: Partial<Settings>) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateDefaultRegistry(
|
||||
defaultRegistry: Partial<DefaultRegistry>
|
||||
) {
|
||||
try {
|
||||
await axios.put(buildUrl('default_registry'), defaultRegistry);
|
||||
} catch (e) {
|
||||
throw parseAxiosError(
|
||||
e as Error,
|
||||
'Unable to update default registry settings'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function buildUrl(subResource?: string, action?: string) {
|
||||
let url = 'settings';
|
||||
if (subResource) {
|
||||
|
||||
@@ -89,6 +89,10 @@ enum AuthenticationMethod {
|
||||
|
||||
type Feature = string;
|
||||
|
||||
export interface DefaultRegistry {
|
||||
Hide: boolean;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
LogoURL: string;
|
||||
BlackListedLabels: Pair[];
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
|
||||
Activity Logs
|
||||
<be-feature-indicator feature="{{::$ctrl.feature}}"></be-feature-indicator>
|
||||
<be-feature-indicator feature="$ctrl.feature"></be-feature-indicator>
|
||||
</div>
|
||||
<div class="vertical-center">
|
||||
<datatable-searchbar on-change="($ctrl.onChangeKeyword)" value="$ctrl.keyword"></datatable-searchbar>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
|
||||
Authentication Events
|
||||
<be-feature-indicator feature="{{::$ctrl.feature}}"></be-feature-indicator>
|
||||
<be-feature-indicator feature="$ctrl.feature"></be-feature-indicator>
|
||||
</div>
|
||||
<div class="vertical-center">
|
||||
<datatable-searchbar on-change="($ctrl.onChangeKeyword)"></datatable-searchbar>
|
||||
|
||||
@@ -15,38 +15,39 @@
|
||||
<!-- build-method -->
|
||||
<div ng-if="!$ctrl.state.fromStack">
|
||||
<div class="col-sm-12 form-section-title"> Build method </div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="$ctrl.state.Method" value="editor" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="$ctrl.state.Method" value="upload" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_upload">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload from your computer</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_repository" ng-model="$ctrl.state.Method" value="repository" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_repository">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'git-pull-request'" feather="true"></pr-icon>
|
||||
Repository
|
||||
</div>
|
||||
<p>Use a git repository</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="$ctrl.state.Method" value="editor" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="$ctrl.state.Method" value="upload" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_upload">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload from your computer</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_repository" ng-model="$ctrl.state.Method" value="repository" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_repository">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'git-pull-request'" feather="true"></pr-icon>
|
||||
Repository
|
||||
</div>
|
||||
<p>Use a git repository</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,18 +16,19 @@
|
||||
<!-- !name-input -->
|
||||
<!-- build-method -->
|
||||
<div class="col-sm-12 form-section-title"> Profile configuration </div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="state.method" value="editor" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="state.method" value="editor" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,17 +17,19 @@
|
||||
<!-- build-method -->
|
||||
<div class="col-sm-12 form-section-title"> Profile configuration </div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="state.method" value="editor" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="state.method" value="editor" />
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'edit'" feather="true"></pr-icon>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -110,9 +110,11 @@
|
||||
<span ng-if="!state.agentEndpoint">Environment URL</span>
|
||||
<span ng-if="state.agentEndpoint">Environment address</span>
|
||||
<portainer-tooltip
|
||||
ng-if="!state.agentEndpoint"
|
||||
message="'URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it.'"
|
||||
>
|
||||
</portainer-tooltip>
|
||||
<portainer-tooltip ng-if="state.agentEndpoint" message="'The address for the Portainer agent in the format <HOST>:<PORT> or <IP>:<PORT>'"> </portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
|
||||
@@ -138,29 +138,33 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- !note -->
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="restore_file" checked="checked" />
|
||||
<label for="restore_file" data-cy="init-selectLocalFile">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload backup file
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="restore_file" checked="checked" />
|
||||
<label for="restore_file" data-cy="init-selectLocalFile">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'upload'" feather="true"></pr-icon>
|
||||
Upload backup file
|
||||
</div>
|
||||
<p></p>
|
||||
</label>
|
||||
</div>
|
||||
<p></p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="restore_s3" disabled />
|
||||
<label for="restore_s3" class="boxselector_disabled">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'download'" feather="true"></pr-icon>
|
||||
Retrieve from S3
|
||||
<div>
|
||||
<input type="radio" id="restore_s3" disabled />
|
||||
<label for="restore_s3" class="boxselector_disabled">
|
||||
<div class="boxselector_header">
|
||||
<pr-icon icon="'download'" feather="true"></pr-icon>
|
||||
Retrieve from S3
|
||||
</div>
|
||||
<p
|
||||
>This feature is available in
|
||||
<a class="hyperlink" href="https://www.portainer.io/business-upsell?from=restore-s3-form" target="_blank"> Portainer Business Edition</a></p
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<p
|
||||
>This feature is available in
|
||||
<a class="hyperlink" href="https://www.portainer.io/business-upsell?from=restore-s3-form" target="_blank"> Portainer Business Edition</a></p
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- note -->
|
||||
|
||||
@@ -21,17 +21,19 @@
|
||||
</div>
|
||||
<!-- !note -->
|
||||
<!-- environment-type -->
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div ng-repeat="type in ctrl.endpointSections">
|
||||
<input type="radio" id="{{ type.Id }}" ng-model="ctrl.formValues.ConnectionType" ng-value="type.Value" />
|
||||
<label for="{{ type.Id }}">
|
||||
<div class="boxselector_header">
|
||||
<i ng-class="type.Classes" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
{{ type.Title }}
|
||||
</div>
|
||||
<p>{{ type.Description }}</p>
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="boxselector_wrapper">
|
||||
<div ng-repeat="type in ctrl.endpointSections">
|
||||
<input type="radio" id="{{ type.Id }}" ng-model="ctrl.formValues.ConnectionType" ng-value="type.Value" />
|
||||
<label for="{{ type.Id }}">
|
||||
<div class="boxselector_header">
|
||||
<i ng-class="type.Classes" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
{{ type.Title }}
|
||||
</div>
|
||||
<p>{{ type.Description }}</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="col-sm-12 form-section-title"> Registry provider </div>
|
||||
|
||||
<div class="form-group"></div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<box-selector radio-name="'registry'" value="$ctrl.state.registryValue" options="$ctrl.state.availableRegistry" on-change="($ctrl.setRegistry)"></box-selector>
|
||||
</div>
|
||||
<box-selector radio-name="'availableRegistry'" value="$ctrl.state.registryValue" options="$ctrl.state.availableRegistry" on-change="($ctrl.setRegistry)"></box-selector>
|
||||
|
||||
<registry-form-quay
|
||||
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.QUAY"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Edit } from 'react-feather';
|
||||
|
||||
import Docker from '@/assets/ico/vendor/docker.svg?c';
|
||||
import Ecr from '@/assets/ico/vendor/ecr.svg?c';
|
||||
import Quay from '@/assets/ico/vendor/quay.svg?c';
|
||||
import Proget from '@/assets/ico/vendor/proget.svg?c';
|
||||
import Azure from '@/assets/ico/vendor/azure.svg?c';
|
||||
import Gitlab from '@/assets/ico/vendor/gitlab.svg?c';
|
||||
import Custom from '@/assets/ico/custom.svg?c';
|
||||
|
||||
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
|
||||
|
||||
export const options = [
|
||||
{
|
||||
@@ -51,7 +54,7 @@ export const options = [
|
||||
},
|
||||
{
|
||||
id: 'registry_custom',
|
||||
icon: Custom,
|
||||
icon: <BadgeIcon icon={Edit} />,
|
||||
label: 'Custom registry',
|
||||
description: 'Define your own registry',
|
||||
value: '3',
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { ArrowDownCircle } from 'react-feather';
|
||||
|
||||
import { FeatureId } from '@/portainer/feature-flags/enums';
|
||||
import Microsoft from '@/assets/ico/vendor/microsoft.svg?c';
|
||||
import Ldap from '@/assets/ico/ldap.svg?c';
|
||||
import Oauth from '@/assets/ico/oauth.svg?c';
|
||||
import OAuth from '@/assets/ico/oauth.svg?c';
|
||||
|
||||
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
|
||||
|
||||
export const options = [
|
||||
{
|
||||
id: 'auth_internal',
|
||||
icon: 'svg-internal',
|
||||
icon: <BadgeIcon icon={ArrowDownCircle} />,
|
||||
label: 'Internal',
|
||||
description: 'Internal authentication mechanism',
|
||||
value: 1,
|
||||
@@ -28,7 +32,7 @@ export const options = [
|
||||
},
|
||||
{
|
||||
id: 'auth_oauth',
|
||||
icon: Oauth,
|
||||
icon: OAuth,
|
||||
label: 'OAuth',
|
||||
description: 'OAuth authentication',
|
||||
value: 3,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user