mirror of https://github.com/portainer/portainer
fix(ingress): allow none controller type EE-4420 (#7883)
Co-authored-by: testA113 <alex.harris@portainer.io>pull/7917/head
parent
e48ceb15e9
commit
55211ef00e
|
@ -59,9 +59,6 @@ func (r K8sIngressInfo) Validate(request *http.Request) error {
|
||||||
if r.Namespace == "" {
|
if r.Namespace == "" {
|
||||||
return errors.New("missing ingress Namespace from the request payload")
|
return errors.New("missing ingress Namespace from the request payload")
|
||||||
}
|
}
|
||||||
if r.ClassName == "" {
|
|
||||||
return errors.New("missing ingress ClassName from the request payload")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -275,7 +275,7 @@ func migrateDBTestHelper(t *testing.T, srcPath, wantPath string) error {
|
||||||
|
|
||||||
// Compare the result we got with the one we wanted.
|
// Compare the result we got with the one we wanted.
|
||||||
if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
|
if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
|
||||||
gotPath := filepath.Join(t.TempDir(), "portainer-migrator-test-fail.json")
|
gotPath := filepath.Join(os.TempDir(), "portainer-migrator-test-fail.json")
|
||||||
os.WriteFile(
|
os.WriteFile(
|
||||||
gotPath,
|
gotPath,
|
||||||
gotJSON,
|
gotJSON,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
func (m *Migrator) migrateDBVersionToDB70() error {
|
func (m *Migrator) migrateDBVersionToDB70() error {
|
||||||
log.Info().Msg("- add IngressAvailabilityPerNamespace field")
|
log.Info().Msg("- add IngressAvailabilityPerNamespace field")
|
||||||
if err := m.addIngressAvailabilityPerNamespaceFieldDB70(); err != nil {
|
if err := m.updateIngressFieldsForEnvDB70(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ func (m *Migrator) migrateDBVersionToDB70() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Migrator) addIngressAvailabilityPerNamespaceFieldDB70() error {
|
func (m *Migrator) updateIngressFieldsForEnvDB70() error {
|
||||||
endpoints, err := m.endpointService.Endpoints()
|
endpoints, err := m.endpointService.Endpoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -59,6 +59,7 @@ func (m *Migrator) addIngressAvailabilityPerNamespaceFieldDB70() error {
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = true
|
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = true
|
||||||
|
endpoint.Kubernetes.Configuration.AllowNoneIngressClass = false
|
||||||
|
|
||||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
"IsEdgeDevice": false,
|
"IsEdgeDevice": false,
|
||||||
"Kubernetes": {
|
"Kubernetes": {
|
||||||
"Configuration": {
|
"Configuration": {
|
||||||
|
"AllowNoneIngressClass": false,
|
||||||
"EnableResourceOverCommit": false,
|
"EnableResourceOverCommit": false,
|
||||||
"IngressAvailabilityPerNamespace": true,
|
"IngressAvailabilityPerNamespace": true,
|
||||||
"IngressClasses": null,
|
"IngressClasses": null,
|
||||||
|
|
|
@ -57,11 +57,22 @@ func (handler *Handler) getKubernetesIngressControllers(w http.ResponseWriter, r
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add none controller if "AllowNone" is set for endpoint.
|
||||||
|
if endpoint.Kubernetes.Configuration.AllowNoneIngressClass {
|
||||||
|
controllers = append(controllers, models.K8sIngressController{
|
||||||
|
Name: "none",
|
||||||
|
ClassName: "none",
|
||||||
|
Type: "custom",
|
||||||
|
})
|
||||||
|
}
|
||||||
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
|
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
|
||||||
var updatedClasses []portainer.KubernetesIngressClassConfig
|
var updatedClasses []portainer.KubernetesIngressClassConfig
|
||||||
for i := range controllers {
|
for i := range controllers {
|
||||||
controllers[i].Availability = true
|
controllers[i].Availability = true
|
||||||
controllers[i].New = true
|
if controllers[i].ClassName != "none" {
|
||||||
|
controllers[i].New = true
|
||||||
|
}
|
||||||
|
|
||||||
var updatedClass portainer.KubernetesIngressClassConfig
|
var updatedClass portainer.KubernetesIngressClassConfig
|
||||||
updatedClass.Name = controllers[i].ClassName
|
updatedClass.Name = controllers[i].ClassName
|
||||||
|
@ -153,6 +164,14 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Add none controller if "AllowNone" is set for endpoint.
|
||||||
|
if endpoint.Kubernetes.Configuration.AllowNoneIngressClass {
|
||||||
|
currentControllers = append(currentControllers, models.K8sIngressController{
|
||||||
|
Name: "none",
|
||||||
|
ClassName: "none",
|
||||||
|
Type: "custom",
|
||||||
|
})
|
||||||
|
}
|
||||||
kubernetesConfig := endpoint.Kubernetes.Configuration
|
kubernetesConfig := endpoint.Kubernetes.Configuration
|
||||||
existingClasses := kubernetesConfig.IngressClasses
|
existingClasses := kubernetesConfig.IngressClasses
|
||||||
ingressAvailabilityPerNamespace := kubernetesConfig.IngressAvailabilityPerNamespace
|
ingressAvailabilityPerNamespace := kubernetesConfig.IngressAvailabilityPerNamespace
|
||||||
|
@ -161,7 +180,9 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon
|
||||||
for i := range currentControllers {
|
for i := range currentControllers {
|
||||||
var globallyblocked bool
|
var globallyblocked bool
|
||||||
currentControllers[i].Availability = true
|
currentControllers[i].Availability = true
|
||||||
currentControllers[i].New = true
|
if currentControllers[i].ClassName != "none" {
|
||||||
|
currentControllers[i].New = true
|
||||||
|
}
|
||||||
|
|
||||||
var updatedClass portainer.KubernetesIngressClassConfig
|
var updatedClass portainer.KubernetesIngressClassConfig
|
||||||
updatedClass.Name = currentControllers[i].ClassName
|
updatedClass.Name = currentControllers[i].ClassName
|
||||||
|
@ -258,6 +279,14 @@ func (handler *Handler) updateKubernetesIngressControllers(w http.ResponseWriter
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Add none controller if "AllowNone" is set for endpoint.
|
||||||
|
if endpoint.Kubernetes.Configuration.AllowNoneIngressClass {
|
||||||
|
controllers = append(controllers, models.K8sIngressController{
|
||||||
|
Name: "none",
|
||||||
|
ClassName: "none",
|
||||||
|
Type: "custom",
|
||||||
|
})
|
||||||
|
}
|
||||||
var updatedClasses []portainer.KubernetesIngressClassConfig
|
var updatedClasses []portainer.KubernetesIngressClassConfig
|
||||||
for i := range controllers {
|
for i := range controllers {
|
||||||
controllers[i].Availability = true
|
controllers[i].Availability = true
|
||||||
|
|
|
@ -90,11 +90,11 @@ func (kcl *KubeClient) GetIngresses(namespace string) ([]models.K8sIngressInfo,
|
||||||
|
|
||||||
var infos []models.K8sIngressInfo
|
var infos []models.K8sIngressInfo
|
||||||
for _, ingress := range ingressList.Items {
|
for _, ingress := range ingressList.Items {
|
||||||
ingressClass := ingress.Spec.IngressClassName
|
|
||||||
var info models.K8sIngressInfo
|
var info models.K8sIngressInfo
|
||||||
info.Name = ingress.Name
|
info.Name = ingress.Name
|
||||||
info.UID = string(ingress.UID)
|
info.UID = string(ingress.UID)
|
||||||
info.Namespace = namespace
|
info.Namespace = namespace
|
||||||
|
ingressClass := ingress.Spec.IngressClassName
|
||||||
info.ClassName = ""
|
info.ClassName = ""
|
||||||
if ingressClass != nil {
|
if ingressClass != nil {
|
||||||
info.ClassName = *ingressClass
|
info.ClassName = *ingressClass
|
||||||
|
@ -113,6 +113,10 @@ func (kcl *KubeClient) GetIngresses(namespace string) ([]models.K8sIngressInfo,
|
||||||
// Gather list of paths and hosts.
|
// Gather list of paths and hosts.
|
||||||
hosts := make(map[string]struct{})
|
hosts := make(map[string]struct{})
|
||||||
for _, r := range ingress.Spec.Rules {
|
for _, r := range ingress.Spec.Rules {
|
||||||
|
// We collect all exiting hosts in a map to avoid duplicates.
|
||||||
|
// Then, later convert it to a slice for the frontend.
|
||||||
|
hosts[r.Host] = struct{}{}
|
||||||
|
|
||||||
if r.HTTP == nil {
|
if r.HTTP == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -124,12 +128,10 @@ func (kcl *KubeClient) GetIngresses(namespace string) ([]models.K8sIngressInfo,
|
||||||
path.IngressName = info.Name
|
path.IngressName = info.Name
|
||||||
path.Host = r.Host
|
path.Host = r.Host
|
||||||
|
|
||||||
// We collect all exiting hosts in a map to avoid duplicates.
|
|
||||||
// Then, later convert it to a slice for the frontend.
|
|
||||||
hosts[r.Host] = struct{}{}
|
|
||||||
|
|
||||||
path.Path = p.Path
|
path.Path = p.Path
|
||||||
path.PathType = string(*p.PathType)
|
if p.PathType != nil {
|
||||||
|
path.PathType = string(*p.PathType)
|
||||||
|
}
|
||||||
path.ServiceName = p.Backend.Service.Name
|
path.ServiceName = p.Backend.Service.Name
|
||||||
path.Port = int(p.Backend.Service.Port.Number)
|
path.Port = int(p.Backend.Service.Port.Number)
|
||||||
info.Paths = append(info.Paths, path)
|
info.Paths = append(info.Paths, path)
|
||||||
|
@ -154,7 +156,9 @@ func (kcl *KubeClient) CreateIngress(namespace string, info models.K8sIngressInf
|
||||||
|
|
||||||
ingress.Name = info.Name
|
ingress.Name = info.Name
|
||||||
ingress.Namespace = info.Namespace
|
ingress.Namespace = info.Namespace
|
||||||
ingress.Spec.IngressClassName = &info.ClassName
|
if info.ClassName != "" {
|
||||||
|
ingress.Spec.IngressClassName = &info.ClassName
|
||||||
|
}
|
||||||
ingress.Annotations = info.Annotations
|
ingress.Annotations = info.Annotations
|
||||||
|
|
||||||
// Store TLS information.
|
// Store TLS information.
|
||||||
|
@ -224,7 +228,9 @@ func (kcl *KubeClient) UpdateIngress(namespace string, info models.K8sIngressInf
|
||||||
|
|
||||||
ingress.Name = info.Name
|
ingress.Name = info.Name
|
||||||
ingress.Namespace = info.Namespace
|
ingress.Namespace = info.Namespace
|
||||||
ingress.Spec.IngressClassName = &info.ClassName
|
if info.ClassName != "" {
|
||||||
|
ingress.Spec.IngressClassName = &info.ClassName
|
||||||
|
}
|
||||||
ingress.Annotations = info.Annotations
|
ingress.Annotations = info.Annotations
|
||||||
|
|
||||||
// Store TLS information.
|
// Store TLS information.
|
||||||
|
|
|
@ -555,6 +555,7 @@ type (
|
||||||
IngressClasses []KubernetesIngressClassConfig `json:"IngressClasses"`
|
IngressClasses []KubernetesIngressClassConfig `json:"IngressClasses"`
|
||||||
RestrictDefaultNamespace bool `json:"RestrictDefaultNamespace"`
|
RestrictDefaultNamespace bool `json:"RestrictDefaultNamespace"`
|
||||||
IngressAvailabilityPerNamespace bool `json:"IngressAvailabilityPerNamespace"`
|
IngressAvailabilityPerNamespace bool `json:"IngressAvailabilityPerNamespace"`
|
||||||
|
AllowNoneIngressClass bool `json:"AllowNoneIngressClass"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration
|
// KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration
|
||||||
|
|
|
@ -12,9 +12,10 @@ export const componentsModule = angular
|
||||||
.component(
|
.component(
|
||||||
'ingressClassDatatable',
|
'ingressClassDatatable',
|
||||||
r2a(IngressClassDatatable, [
|
r2a(IngressClassDatatable, [
|
||||||
'onChangeAvailability',
|
'onChangeControllers',
|
||||||
'description',
|
'description',
|
||||||
'ingressControllers',
|
'ingressControllers',
|
||||||
|
'allowNoneIngressClass',
|
||||||
'isLoading',
|
'isLoading',
|
||||||
'noIngressControllerLabel',
|
'noIngressControllerLabel',
|
||||||
'view',
|
'view',
|
||||||
|
|
|
@ -157,7 +157,9 @@ export function CreateIngressView() {
|
||||||
const existingIngressClass = useMemo(
|
const existingIngressClass = useMemo(
|
||||||
() =>
|
() =>
|
||||||
ingressControllersResults.data?.find(
|
ingressControllersResults.data?.find(
|
||||||
(i) => i.ClassName === ingressRule.IngressClassName
|
(i) =>
|
||||||
|
i.ClassName === ingressRule.IngressClassName ||
|
||||||
|
(i.Type === 'custom' && ingressRule.IngressClassName === '')
|
||||||
),
|
),
|
||||||
[ingressControllersResults.data, ingressRule.IngressClassName]
|
[ingressControllersResults.data, ingressRule.IngressClassName]
|
||||||
);
|
);
|
||||||
|
@ -177,10 +179,11 @@ export function CreateIngressView() {
|
||||||
ingressRule.IngressClassName &&
|
ingressRule.IngressClassName &&
|
||||||
!ingressControllersResults.isLoading
|
!ingressControllersResults.isLoading
|
||||||
) {
|
) {
|
||||||
|
const optionLabel = !ingressRule.IngressType
|
||||||
|
? `${ingressRule.IngressClassName} - NOT FOUND`
|
||||||
|
: `${ingressRule.IngressClassName} - DISALLOWED`;
|
||||||
ingressClassOptions.push({
|
ingressClassOptions.push({
|
||||||
label: !ingressRule.IngressType
|
label: optionLabel,
|
||||||
? `${ingressRule.IngressClassName} - NOT FOUND`
|
|
||||||
: `${ingressRule.IngressClassName} - DISALLOWED`,
|
|
||||||
value: ingressRule.IngressClassName,
|
value: ingressRule.IngressClassName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -206,6 +209,7 @@ export function CreateIngressView() {
|
||||||
!!params.name &&
|
!!params.name &&
|
||||||
ingressesResults.data &&
|
ingressesResults.data &&
|
||||||
!ingressRule.IngressName &&
|
!ingressRule.IngressName &&
|
||||||
|
!ingressControllersResults.isLoading &&
|
||||||
!ingressControllersResults.isLoading
|
!ingressControllersResults.isLoading
|
||||||
) {
|
) {
|
||||||
// if it is an edit screen, prepare the rule from the ingress
|
// if it is an edit screen, prepare the rule from the ingress
|
||||||
|
@ -214,9 +218,11 @@ export function CreateIngressView() {
|
||||||
);
|
);
|
||||||
if (ing) {
|
if (ing) {
|
||||||
const type = ingressControllersResults.data?.find(
|
const type = ingressControllersResults.data?.find(
|
||||||
(c) => c.ClassName === ing.ClassName
|
(c) =>
|
||||||
|
c.ClassName === ing.ClassName ||
|
||||||
|
(c.Type === 'custom' && !ing.ClassName)
|
||||||
)?.Type;
|
)?.Type;
|
||||||
const r = prepareRuleFromIngress(ing);
|
const r = prepareRuleFromIngress(ing, type);
|
||||||
r.IngressType = type || r.IngressType;
|
r.IngressType = type || r.IngressType;
|
||||||
setIngressRule(r);
|
setIngressRule(r);
|
||||||
}
|
}
|
||||||
|
@ -636,7 +642,7 @@ export function CreateIngressView() {
|
||||||
setIngressRule(rule);
|
setIngressRule(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewAnnotation(type?: 'rewrite' | 'regex') {
|
function addNewAnnotation(type?: 'rewrite' | 'regex' | 'ingressClass') {
|
||||||
const rule = { ...ingressRule };
|
const rule = { ...ingressRule };
|
||||||
|
|
||||||
const annotation: Annotation = {
|
const annotation: Annotation = {
|
||||||
|
@ -644,13 +650,21 @@ export function CreateIngressView() {
|
||||||
Value: '',
|
Value: '',
|
||||||
ID: uuidv4(),
|
ID: uuidv4(),
|
||||||
};
|
};
|
||||||
if (type === 'rewrite') {
|
switch (type) {
|
||||||
annotation.Key = 'nginx.ingress.kubernetes.io/rewrite-target';
|
case 'rewrite':
|
||||||
annotation.Value = '/$1';
|
annotation.Key = 'nginx.ingress.kubernetes.io/rewrite-target';
|
||||||
}
|
annotation.Value = '/$1';
|
||||||
if (type === 'regex') {
|
break;
|
||||||
annotation.Key = 'nginx.ingress.kubernetes.io/use-regex';
|
case 'regex':
|
||||||
annotation.Value = 'true';
|
annotation.Key = 'nginx.ingress.kubernetes.io/use-regex';
|
||||||
|
annotation.Value = 'true';
|
||||||
|
break;
|
||||||
|
case 'ingressClass':
|
||||||
|
annotation.Key = 'kubernetes.io/ingress.class';
|
||||||
|
annotation.Value = '';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
rule.Annotations = rule.Annotations || [];
|
rule.Annotations = rule.Annotations || [];
|
||||||
rule.Annotations?.push(annotation);
|
rule.Annotations?.push(annotation);
|
||||||
|
@ -690,10 +704,13 @@ export function CreateIngressView() {
|
||||||
function handleCreateIngressRules() {
|
function handleCreateIngressRules() {
|
||||||
const rule = { ...ingressRule };
|
const rule = { ...ingressRule };
|
||||||
|
|
||||||
|
const classNameToSend =
|
||||||
|
rule.IngressClassName === 'none' ? '' : rule.IngressClassName;
|
||||||
|
|
||||||
const ingress: Ingress = {
|
const ingress: Ingress = {
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Name: rule.IngressName,
|
Name: rule.IngressName,
|
||||||
ClassName: rule.IngressClassName,
|
ClassName: classNameToSend,
|
||||||
Hosts: rule.Hosts.map((host) => host.Host),
|
Hosts: rule.Hosts.map((host) => host.Host),
|
||||||
Paths: preparePaths(rule.IngressName, rule.Hosts),
|
Paths: preparePaths(rule.IngressName, rule.Hosts),
|
||||||
TLS: prepareTLS(rule.Hosts),
|
TLS: prepareTLS(rule.Hosts),
|
||||||
|
|
|
@ -47,7 +47,7 @@ interface Props {
|
||||||
|
|
||||||
addNewIngressHost: (noHost?: boolean) => void;
|
addNewIngressHost: (noHost?: boolean) => void;
|
||||||
addNewIngressRoute: (hostIndex: number) => void;
|
addNewIngressRoute: (hostIndex: number) => void;
|
||||||
addNewAnnotation: (type?: 'rewrite' | 'regex') => void;
|
addNewAnnotation: (type?: 'rewrite' | 'regex' | 'ingressClass') => void;
|
||||||
|
|
||||||
handleNamespaceChange: (val: string) => void;
|
handleNamespaceChange: (val: string) => void;
|
||||||
handleHostChange: (hostIndex: number, val: string) => void;
|
handleHostChange: (hostIndex: number, val: string) => void;
|
||||||
|
@ -249,9 +249,10 @@ export function IngressForm({
|
||||||
onClick={() => addNewAnnotation('rewrite')}
|
onClick={() => addNewAnnotation('rewrite')}
|
||||||
icon={Plus}
|
icon={Plus}
|
||||||
title="When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
|
title="When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
|
||||||
|
data-cy="add-rewrite-annotation"
|
||||||
>
|
>
|
||||||
{' '}
|
{' '}
|
||||||
add rewrite annotation
|
Add rewrite annotation
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -259,11 +260,23 @@ export function IngressForm({
|
||||||
onClick={() => addNewAnnotation('regex')}
|
onClick={() => addNewAnnotation('regex')}
|
||||||
icon={Plus}
|
icon={Plus}
|
||||||
title="When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
|
title="When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
|
||||||
|
data-cy="add-regex-annotation"
|
||||||
>
|
>
|
||||||
add regular expression annotation
|
Add regular expression annotation
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{rule.IngressType === 'custom' && (
|
||||||
|
<Button
|
||||||
|
className="btn btn-sm btn-light mb-2 ml-2"
|
||||||
|
onClick={() => addNewAnnotation('ingressClass')}
|
||||||
|
icon={Plus}
|
||||||
|
data-cy="add-ingress-class-annotation"
|
||||||
|
>
|
||||||
|
Add kubernetes.io/ingress.class annotation
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-12 px-0 text-muted">Rules</div>
|
<div className="col-sm-12 px-0 text-muted">Rules</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { Annotation } from '@/kubernetes/react/views/networks/ingresses/components/annotations/types';
|
import { Annotation } from '@/kubernetes/react/views/networks/ingresses/components/annotations/types';
|
||||||
|
import { SupportedIngControllerTypes } from '@/react/kubernetes/cluster/ingressClass/types';
|
||||||
|
|
||||||
import { TLS, Ingress } from '../types';
|
import { TLS, Ingress } from '../types';
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ export function prepareRuleHostsFromIngress(ing: Ingress) {
|
||||||
h.Host = host;
|
h.Host = host;
|
||||||
h.Secret = getSecretByHost(host, ing.TLS);
|
h.Secret = getSecretByHost(host, ing.TLS);
|
||||||
h.Paths = [];
|
h.Paths = [];
|
||||||
ing.Paths.forEach((path) => {
|
ing.Paths?.forEach((path) => {
|
||||||
if (path.Host === host) {
|
if (path.Host === host) {
|
||||||
h.Paths.push({
|
h.Paths.push({
|
||||||
Route: path.Path,
|
Route: path.Path,
|
||||||
|
@ -99,12 +100,15 @@ export function getAnnotationsForEdit(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareRuleFromIngress(ing: Ingress): Rule {
|
export function prepareRuleFromIngress(
|
||||||
|
ing: Ingress,
|
||||||
|
type?: SupportedIngControllerTypes
|
||||||
|
): Rule {
|
||||||
return {
|
return {
|
||||||
Key: uuidv4(),
|
Key: uuidv4(),
|
||||||
IngressName: ing.Name,
|
IngressName: ing.Name,
|
||||||
Namespace: ing.Namespace,
|
Namespace: ing.Namespace,
|
||||||
IngressClassName: ing.ClassName,
|
IngressClassName: type === 'custom' ? 'none' : ing.ClassName,
|
||||||
Hosts: prepareRuleHostsFromIngress(ing) || [],
|
Hosts: prepareRuleHostsFromIngress(ing) || [],
|
||||||
Annotations: ing.Annotations ? getAnnotationsForEdit(ing.Annotations) : [],
|
Annotations: ing.Annotations ? getAnnotationsForEdit(ing.Annotations) : [],
|
||||||
IngressType: ing.Type,
|
IngressType: ing.Type,
|
||||||
|
|
|
@ -96,8 +96,11 @@ export function useIngresses(
|
||||||
const serviceNamesInNamespace = servicesInNamespace?.map(
|
const serviceNamesInNamespace = servicesInNamespace?.map(
|
||||||
(service) => service.Name
|
(service) => service.Name
|
||||||
);
|
);
|
||||||
ing.Paths.forEach((path, pIndex) => {
|
ing.Paths?.forEach((path, pIndex) => {
|
||||||
if (!serviceNamesInNamespace?.includes(path.ServiceName)) {
|
if (
|
||||||
|
!serviceNamesInNamespace?.includes(path.ServiceName) &&
|
||||||
|
filteredIngresses[iIndex].Paths
|
||||||
|
) {
|
||||||
filteredIngresses[iIndex].Paths[pIndex].HasService = false;
|
filteredIngresses[iIndex].Paths[pIndex].HasService = false;
|
||||||
} else {
|
} else {
|
||||||
filteredIngresses[iIndex].Paths[pIndex].HasService = true;
|
filteredIngresses[iIndex].Paths[pIndex].HasService = true;
|
||||||
|
@ -186,6 +189,7 @@ export function useIngressControllers(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!namespace,
|
enabled: !!namespace,
|
||||||
|
cacheTime: 0,
|
||||||
...withError('Unable to get ingress controllers'),
|
...withError('Unable to get ingress controllers'),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
PaginationTableSettings,
|
PaginationTableSettings,
|
||||||
SortableTableSettings,
|
SortableTableSettings,
|
||||||
} from '@/react/components/datatables/types';
|
} from '@/react/components/datatables/types';
|
||||||
|
import { SupportedIngControllerTypes } from '@/react/kubernetes/cluster/ingressClass/types';
|
||||||
|
|
||||||
export interface TableSettings
|
export interface TableSettings
|
||||||
extends SortableTableSettings,
|
extends SortableTableSettings,
|
||||||
|
@ -42,6 +43,6 @@ export interface IngressController {
|
||||||
Name: string;
|
Name: string;
|
||||||
ClassName: string;
|
ClassName: string;
|
||||||
Availability: string;
|
Availability: string;
|
||||||
Type: string;
|
Type: SupportedIngControllerTypes;
|
||||||
New: boolean;
|
New: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ingress-class-datatable
|
<ingress-class-datatable
|
||||||
on-change-availability="(ctrl.onChangeAvailability)"
|
on-change-controllers="(ctrl.onChangeControllers)"
|
||||||
|
allow-none-ingress-class="ctrl.formValues.AllowNoneIngressClass"
|
||||||
ingress-controllers="ctrl.originalIngressControllers"
|
ingress-controllers="ctrl.originalIngressControllers"
|
||||||
is-loading="ctrl.isIngressControllersLoading"
|
is-loading="ctrl.isIngressControllersLoading"
|
||||||
description="'Enabling ingress controllers in your cluster allows them to be available in the Portainer UI for users to publish applications over HTTP/HTTPS. A controller must have a class name for it to be included here.'"
|
description="'Enabling ingress controllers in your cluster allows them to be available in the Portainer UI for users to publish applications over HTTP/HTTPS. A controller must have a class name for it to be included here.'"
|
||||||
|
@ -50,18 +51,46 @@
|
||||||
view="'cluster'"
|
view="'cluster'"
|
||||||
></ingress-class-datatable>
|
></ingress-class-datatable>
|
||||||
|
|
||||||
<div class="form-group">
|
<label htmlFor="foldingButtonIngControllerSettings" class="col-sm-12 form-section-title cursor-pointer flex items-center">
|
||||||
<div class="col-sm-12">
|
<button
|
||||||
<por-switch-field
|
id="foldingButtonIngControllerSettings"
|
||||||
checked="ctrl.formValues.IngressAvailabilityPerNamespace"
|
type="button"
|
||||||
name="'ingressAvailabilityPerNamespace'"
|
class="border-0 mx-2 bg-transparent inline-flex justify-center items-center w-2 !ml-0"
|
||||||
label="'Configure ingress controller availability per namespace'"
|
ng-click="ctrl.toggleAdvancedIngSettings()"
|
||||||
tooltip="'This allows an administrator to configure, in each namespace, which ingress controllers will be available for users to select when setting up ingresses for applications.'"
|
>
|
||||||
on-change="(ctrl.onToggleIngressAvailabilityPerNamespace)"
|
<pr-icon ng-if="!ctrl.state.isIngToggleSectionExpanded" feather="true" icon="'chevron-right'"></pr-icon>
|
||||||
label-class="'col-sm-5 col-lg-4 px-0 !m-0'"
|
<pr-icon ng-if="ctrl.state.isIngToggleSectionExpanded" feather="true" icon="'chevron-down'"></pr-icon>
|
||||||
switch-class="'col-sm-8'"
|
</button>
|
||||||
>
|
More settings
|
||||||
</por-switch-field>
|
</label>
|
||||||
|
<div ng-if="ctrl.state.isIngToggleSectionExpanded" class="ml-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<por-switch-field
|
||||||
|
checked="ctrl.formValues.AllowNoneIngressClass"
|
||||||
|
name="'allowNoIngressClass'"
|
||||||
|
label="'Allow ingress class to be set to "none"'"
|
||||||
|
tooltip="'This allows users setting up ingresses to select "none" as the ingress class.'"
|
||||||
|
on-change="(ctrl.onToggleAllowNoneIngressClass)"
|
||||||
|
label-class="'col-sm-5 col-lg-4 px-0 !m-0'"
|
||||||
|
switch-class="'col-sm-8'"
|
||||||
|
>
|
||||||
|
</por-switch-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<por-switch-field
|
||||||
|
checked="ctrl.formValues.IngressAvailabilityPerNamespace"
|
||||||
|
name="'ingressAvailabilityPerNamespace'"
|
||||||
|
label="'Configure ingress controller availability per namespace'"
|
||||||
|
tooltip="'This allows an administrator to configure, in each namespace, which ingress controllers will be available for users to select when setting up ingresses for applications.'"
|
||||||
|
on-change="(ctrl.onToggleIngressAvailabilityPerNamespace)"
|
||||||
|
label-class="'col-sm-5 col-lg-4 px-0 !m-0'"
|
||||||
|
switch-class="'col-sm-8'"
|
||||||
|
>
|
||||||
|
</por-switch-field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,10 @@ class KubernetesConfigureController {
|
||||||
this.limitedFeature = FeatureId.K8S_SETUP_DEFAULT;
|
this.limitedFeature = FeatureId.K8S_SETUP_DEFAULT;
|
||||||
this.limitedFeatureAutoWindow = FeatureId.HIDE_AUTO_UPDATE_WINDOW;
|
this.limitedFeatureAutoWindow = FeatureId.HIDE_AUTO_UPDATE_WINDOW;
|
||||||
this.onToggleAutoUpdate = this.onToggleAutoUpdate.bind(this);
|
this.onToggleAutoUpdate = this.onToggleAutoUpdate.bind(this);
|
||||||
this.onChangeAvailability = this.onChangeAvailability.bind(this);
|
this.onChangeControllers = this.onChangeControllers.bind(this);
|
||||||
this.onChangeEnableResourceOverCommit = this.onChangeEnableResourceOverCommit.bind(this);
|
this.onChangeEnableResourceOverCommit = this.onChangeEnableResourceOverCommit.bind(this);
|
||||||
this.onToggleIngressAvailabilityPerNamespace = this.onToggleIngressAvailabilityPerNamespace.bind(this);
|
this.onToggleIngressAvailabilityPerNamespace = this.onToggleIngressAvailabilityPerNamespace.bind(this);
|
||||||
|
this.onToggleAllowNoneIngressClass = this.onToggleAllowNoneIngressClass.bind(this);
|
||||||
this.onChangeStorageClassAccessMode = this.onChangeStorageClassAccessMode.bind(this);
|
this.onChangeStorageClassAccessMode = this.onChangeStorageClassAccessMode.bind(this);
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
@ -71,7 +72,7 @@ class KubernetesConfigureController {
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region INGRESS CLASSES UI MANAGEMENT */
|
/* #region INGRESS CLASSES UI MANAGEMENT */
|
||||||
onChangeAvailability(controllerClassMap) {
|
onChangeControllers(controllerClassMap) {
|
||||||
this.ingressControllers = controllerClassMap;
|
this.ingressControllers = controllerClassMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +80,18 @@ class KubernetesConfigureController {
|
||||||
return _.find(this.formValues.IngressClasses, { Type: this.IngressClassTypes.TRAEFIK });
|
return _.find(this.formValues.IngressClasses, { Type: this.IngressClassTypes.TRAEFIK });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleAdvancedIngSettings() {
|
||||||
|
this.$scope.$evalAsync(() => {
|
||||||
|
this.state.isIngToggleSectionExpanded = !this.state.isIngToggleSectionExpanded;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleAllowNoneIngressClass() {
|
||||||
|
this.$scope.$evalAsync(() => {
|
||||||
|
this.formValues.AllowNoneIngressClass = !this.formValues.AllowNoneIngressClass;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onToggleIngressAvailabilityPerNamespace() {
|
onToggleIngressAvailabilityPerNamespace() {
|
||||||
this.$scope.$evalAsync(() => {
|
this.$scope.$evalAsync(() => {
|
||||||
this.formValues.IngressAvailabilityPerNamespace = !this.formValues.IngressAvailabilityPerNamespace;
|
this.formValues.IngressAvailabilityPerNamespace = !this.formValues.IngressAvailabilityPerNamespace;
|
||||||
|
@ -109,6 +122,7 @@ class KubernetesConfigureController {
|
||||||
endpoint.Kubernetes.Configuration.IngressClasses = ingressClasses;
|
endpoint.Kubernetes.Configuration.IngressClasses = ingressClasses;
|
||||||
endpoint.Kubernetes.Configuration.RestrictDefaultNamespace = this.formValues.RestrictDefaultNamespace;
|
endpoint.Kubernetes.Configuration.RestrictDefaultNamespace = this.formValues.RestrictDefaultNamespace;
|
||||||
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = this.formValues.IngressAvailabilityPerNamespace;
|
endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace = this.formValues.IngressAvailabilityPerNamespace;
|
||||||
|
endpoint.Kubernetes.Configuration.AllowNoneIngressClass = this.formValues.AllowNoneIngressClass;
|
||||||
endpoint.ChangeWindow = this.state.autoUpdateSettings;
|
endpoint.ChangeWindow = this.state.autoUpdateSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +270,7 @@ class KubernetesConfigureController {
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
displayConfigureClassPanel: {},
|
displayConfigureClassPanel: {},
|
||||||
viewReady: false,
|
viewReady: false,
|
||||||
|
isIngToggleSectionExpanded: false,
|
||||||
endpointId: this.$state.params.endpointId,
|
endpointId: this.$state.params.endpointId,
|
||||||
duplicates: {
|
duplicates: {
|
||||||
ingressClasses: new KubernetesFormValidationReferences(),
|
ingressClasses: new KubernetesFormValidationReferences(),
|
||||||
|
@ -315,6 +330,7 @@ class KubernetesConfigureController {
|
||||||
return ic;
|
return ic;
|
||||||
});
|
});
|
||||||
this.formValues.IngressAvailabilityPerNamespace = this.endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace;
|
this.formValues.IngressAvailabilityPerNamespace = this.endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace;
|
||||||
|
this.formValues.AllowNoneIngressClass = this.endpoint.Kubernetes.Configuration.AllowNoneIngressClass;
|
||||||
|
|
||||||
this.oldFormValues = Object.assign({}, this.formValues);
|
this.oldFormValues = Object.assign({}, this.formValues);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -185,7 +185,7 @@
|
||||||
<div class="col-sm-12 form-section-title"> Networking </div>
|
<div class="col-sm-12 form-section-title"> Networking </div>
|
||||||
<ingress-class-datatable
|
<ingress-class-datatable
|
||||||
ng-if="$ctrl.state.ingressAvailabilityPerNamespace"
|
ng-if="$ctrl.state.ingressAvailabilityPerNamespace"
|
||||||
on-change-availability="($ctrl.onChangeIngressControllerAvailability)"
|
on-change-controllers="($ctrl.onChangeIngressControllerAvailability)"
|
||||||
ingress-controllers="$ctrl.ingressControllers"
|
ingress-controllers="$ctrl.ingressControllers"
|
||||||
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
|
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
|
||||||
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"
|
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
<div class="col-sm-12 form-section-title"> Networking </div>
|
<div class="col-sm-12 form-section-title"> Networking </div>
|
||||||
<ingress-class-datatable
|
<ingress-class-datatable
|
||||||
ng-if="ctrl.state.ingressAvailabilityPerNamespace"
|
ng-if="ctrl.state.ingressAvailabilityPerNamespace"
|
||||||
on-change-availability="(ctrl.onChangeIngressControllerAvailability)"
|
on-change-controllers="(ctrl.onChangeIngressControllerAvailability)"
|
||||||
ingress-controllers="ctrl.ingressControllers"
|
ingress-controllers="ctrl.ingressControllers"
|
||||||
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
|
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
|
||||||
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"
|
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"
|
||||||
|
|
|
@ -75,6 +75,7 @@ export function createMockEnvironment(): Environment {
|
||||||
Configuration: {
|
Configuration: {
|
||||||
IngressClasses: [],
|
IngressClasses: [],
|
||||||
IngressAvailabilityPerNamespace: false,
|
IngressAvailabilityPerNamespace: false,
|
||||||
|
AllowNoneIngressClass: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
EdgeKey: '',
|
EdgeKey: '',
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { PropsWithChildren, useState } from 'react';
|
import { PropsWithChildren, useState } from 'react';
|
||||||
|
|
||||||
|
import { Icon } from '@@/Icon';
|
||||||
|
|
||||||
import { FormSectionTitle } from '../FormSectionTitle';
|
import { FormSectionTitle } from '../FormSectionTitle';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -22,11 +24,12 @@ export function FormSection({
|
||||||
id={`foldingButton${title}`}
|
id={`foldingButton${title}`}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
className="border-0 mx-2 bg-transparent inline-flex justify-center items-center w-2"
|
className="border-0 mx-2 !ml-0 bg-transparent inline-flex justify-center items-center w-2"
|
||||||
>
|
>
|
||||||
<i
|
<Icon
|
||||||
className={`fa fa-caret-${isExpanded ? 'down' : 'right'}`}
|
icon={isExpanded ? 'chevron-down' : 'chevron-right'}
|
||||||
aria-hidden="true"
|
className="shrink-0"
|
||||||
|
feather
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function FormSectionTitle({
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
htmlFor={htmlFor}
|
htmlFor={htmlFor}
|
||||||
className="col-sm-12 form-section-title cursor-pointer"
|
className="col-sm-12 form-section-title cursor-pointer flex items-center"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { confirmWarn } from '@/portainer/services/modal.service/confirm';
|
import { confirmWarn } from '@/portainer/services/modal.service/confirm';
|
||||||
|
|
||||||
|
@ -14,29 +14,64 @@ import { createStore } from './datatable-store';
|
||||||
const useStore = createStore('ingressClasses');
|
const useStore = createStore('ingressClasses');
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onChangeAvailability: (
|
onChangeControllers: (
|
||||||
controllerClassMap: IngressControllerClassMap[]
|
controllerClassMap: IngressControllerClassMap[]
|
||||||
) => void; // angular function to save the ingress class list
|
) => void; // angular function to save the ingress class list
|
||||||
description: string;
|
description: string;
|
||||||
ingressControllers: IngressControllerClassMap[] | undefined;
|
ingressControllers: IngressControllerClassMap[] | undefined;
|
||||||
|
allowNoneIngressClass: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
noIngressControllerLabel: string;
|
noIngressControllerLabel: string;
|
||||||
view: string;
|
view: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IngressClassDatatable({
|
export function IngressClassDatatable({
|
||||||
onChangeAvailability,
|
onChangeControllers,
|
||||||
description,
|
description,
|
||||||
ingressControllers,
|
ingressControllers,
|
||||||
|
allowNoneIngressClass,
|
||||||
isLoading,
|
isLoading,
|
||||||
noIngressControllerLabel,
|
noIngressControllerLabel,
|
||||||
view,
|
view,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [ingControllerFormValues, setIngControllerFormValues] =
|
const [ingControllerFormValues, setIngControllerFormValues] = useState(
|
||||||
useState(ingressControllers);
|
ingressControllers || []
|
||||||
|
);
|
||||||
const settings = useStore();
|
const settings = useStore();
|
||||||
const columns = useColumns();
|
const columns = useColumns();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (allowNoneIngressClass === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newIngFormValues: IngressControllerClassMap[];
|
||||||
|
const isCustomTypeExist = ingControllerFormValues.some(
|
||||||
|
(ic) => ic.Type === 'custom'
|
||||||
|
);
|
||||||
|
if (allowNoneIngressClass) {
|
||||||
|
newIngFormValues = [...ingControllerFormValues];
|
||||||
|
// add the ingress controller type 'custom' with a 'none' ingress class name
|
||||||
|
if (!isCustomTypeExist) {
|
||||||
|
newIngFormValues.push({
|
||||||
|
Name: 'none',
|
||||||
|
ClassName: 'none',
|
||||||
|
Type: 'custom',
|
||||||
|
Availability: true,
|
||||||
|
New: false,
|
||||||
|
Used: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newIngFormValues = ingControllerFormValues.filter(
|
||||||
|
(ingController) => ingController.ClassName !== 'none'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setIngControllerFormValues(newIngFormValues);
|
||||||
|
onChangeControllers(newIngFormValues);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [allowNoneIngressClass, onChangeControllers]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="-mx-[15px]">
|
<div className="-mx-[15px]">
|
||||||
<Datatable
|
<Datatable
|
||||||
|
@ -134,7 +169,7 @@ export function IngressClassDatatable({
|
||||||
);
|
);
|
||||||
if (view === 'namespace') {
|
if (view === 'namespace') {
|
||||||
setIngControllerFormValues(updatedIngressControllers);
|
setIngControllerFormValues(updatedIngressControllers);
|
||||||
onChangeAvailability(updatedIngressControllers);
|
onChangeControllers(updatedIngressControllers);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,14 +215,14 @@ export function IngressClassDatatable({
|
||||||
callback: (confirmed) => {
|
callback: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
setIngControllerFormValues(updatedIngressControllers);
|
setIngControllerFormValues(updatedIngressControllers);
|
||||||
onChangeAvailability(updatedIngressControllers);
|
onChangeControllers(updatedIngressControllers);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIngControllerFormValues(updatedIngressControllers);
|
setIngControllerFormValues(updatedIngressControllers);
|
||||||
onChangeAvailability(updatedIngressControllers);
|
onChangeControllers(updatedIngressControllers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,10 +231,13 @@ function isUnsavedChanges(
|
||||||
oldIngressControllers: IngressControllerClassMap[],
|
oldIngressControllers: IngressControllerClassMap[],
|
||||||
newIngressControllers: IngressControllerClassMap[]
|
newIngressControllers: IngressControllerClassMap[]
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < oldIngressControllers.length; i += 1) {
|
if (oldIngressControllers.length !== newIngressControllers.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < newIngressControllers.length; i += 1) {
|
||||||
if (
|
if (
|
||||||
oldIngressControllers[i].Availability !==
|
oldIngressControllers[i]?.Availability !==
|
||||||
newIngressControllers[i].Availability
|
newIngressControllers[i]?.Availability
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
type SupportedIngControllerNames = 'nginx' | 'traefik' | 'unknown';
|
export type SupportedIngControllerTypes =
|
||||||
|
| 'nginx'
|
||||||
|
| 'traefik'
|
||||||
|
| 'other'
|
||||||
|
| 'custom';
|
||||||
|
|
||||||
export interface IngressControllerClassMap extends Record<string, unknown> {
|
export interface IngressControllerClassMap extends Record<string, unknown> {
|
||||||
Name: string;
|
Name: string;
|
||||||
ClassName: string;
|
ClassName: string;
|
||||||
Type: SupportedIngControllerNames;
|
Type: SupportedIngControllerTypes;
|
||||||
Availability: boolean;
|
Availability: boolean;
|
||||||
New: boolean;
|
New: boolean;
|
||||||
Used: boolean; // if the controller is used by any ingress in the cluster
|
Used: boolean; // if the controller is used by any ingress in the cluster
|
||||||
|
|
|
@ -70,6 +70,7 @@ export interface KubernetesConfiguration {
|
||||||
RestrictDefaultNamespace?: boolean;
|
RestrictDefaultNamespace?: boolean;
|
||||||
IngressClasses: IngressClass[];
|
IngressClasses: IngressClass[];
|
||||||
IngressAvailabilityPerNamespace: boolean;
|
IngressAvailabilityPerNamespace: boolean;
|
||||||
|
AllowNoneIngressClass: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KubernetesSettings {
|
export interface KubernetesSettings {
|
||||||
|
|
Loading…
Reference in New Issue