mirror of https://github.com/portainer/portainer
fix(namespace): create ns qa feedback [EE-2226] (#10474)
parent
bcb3f918d1
commit
07ec2ffe5e
|
@ -11,6 +11,7 @@ type K8sNamespaceDetails struct {
|
|||
Name string `json:"Name"`
|
||||
Annotations map[string]string `json:"Annotations"`
|
||||
ResourceQuota *K8sResourceQuota `json:"ResourceQuota"`
|
||||
Owner string `json:"Owner"`
|
||||
}
|
||||
|
||||
type K8sResourceQuota struct {
|
||||
|
|
|
@ -63,15 +63,21 @@ func (kcl *KubeClient) GetNamespace(name string) (portainer.K8sNamespaceInfo, er
|
|||
|
||||
// CreateNamespace creates a new ingress in a given namespace in a k8s endpoint.
|
||||
func (kcl *KubeClient) CreateNamespace(info models.K8sNamespaceDetails) error {
|
||||
portainerLabels := map[string]string{
|
||||
"io.portainer.kubernetes.resourcepool.name": info.Name,
|
||||
"io.portainer.kubernetes.resourcepool.owner": info.Owner,
|
||||
}
|
||||
|
||||
var ns v1.Namespace
|
||||
ns.Name = info.Name
|
||||
ns.Annotations = info.Annotations
|
||||
ns.Labels = portainerLabels
|
||||
|
||||
resourceQuota := &v1.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "portainer-rq-" + info.Name,
|
||||
Namespace: info.Name,
|
||||
Labels: portainerLabels,
|
||||
},
|
||||
Spec: v1.ResourceQuotaSpec{
|
||||
Hard: v1.ResourceList{},
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
class="searchInput"
|
||||
ng-model="$ctrl.state.textFilter"
|
||||
ng-change="$ctrl.onTextFilterChange()"
|
||||
placeholder="Search for a namespace..."
|
||||
placeholder="Search..."
|
||||
auto-focus
|
||||
ng-model-options="{ debounce: 300 }"
|
||||
data-cy="k8sNamespace-namespaceSearchInput"
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
|
||||
import { useEnvironmentRegistries } from '@/react/portainer/environments/queries/useEnvironmentRegistries';
|
||||
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||
|
||||
import { Widget, WidgetBody } from '@@/Widget';
|
||||
|
||||
|
@ -30,6 +31,7 @@ export function CreateNamespaceForm() {
|
|||
const { data: registries } = useEnvironmentRegistries(environmentId, {
|
||||
hideDefault: true,
|
||||
});
|
||||
const { user } = useCurrentUser();
|
||||
// for namespace create, show ingress classes that are allowed in the current environment.
|
||||
// the ingressClasses show the none option, so we don't need to add it here.
|
||||
const { data: ingressClasses } = useIngressControllerClassMapQuery({
|
||||
|
@ -65,7 +67,7 @@ export function CreateNamespaceForm() {
|
|||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
onSubmit={(values) => handleSubmit(values, user.Username)}
|
||||
validateOnMount
|
||||
validationSchema={getNamespaceValidationSchema(
|
||||
memoryLimit,
|
||||
|
@ -78,9 +80,9 @@ export function CreateNamespaceForm() {
|
|||
</Widget>
|
||||
);
|
||||
|
||||
function handleSubmit(values: CreateNamespaceFormValues) {
|
||||
function handleSubmit(values: CreateNamespaceFormValues, userName: string) {
|
||||
const createNamespacePayload: CreateNamespacePayload =
|
||||
transformFormValuesToNamespacePayload(values);
|
||||
transformFormValuesToNamespacePayload(values, userName);
|
||||
const updateRegistriesPayload: UpdateRegistryPayload[] =
|
||||
values.registries.flatMap((registryFormValues) => {
|
||||
// find the matching registry from the cluster registries
|
||||
|
|
|
@ -15,6 +15,7 @@ export type CreateNamespaceFormValues = {
|
|||
|
||||
export type CreateNamespacePayload = {
|
||||
Name: string;
|
||||
Owner: string;
|
||||
ResourceQuota: ResourceQuotaPayload;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { CreateNamespaceFormValues, CreateNamespacePayload } from './types';
|
||||
|
||||
export function transformFormValuesToNamespacePayload(
|
||||
createNamespaceFormValues: CreateNamespaceFormValues
|
||||
createNamespaceFormValues: CreateNamespaceFormValues,
|
||||
owner: string
|
||||
): CreateNamespacePayload {
|
||||
const memoryInBytes =
|
||||
Number(createNamespaceFormValues.resourceQuota.memory) * 10 ** 6;
|
||||
return {
|
||||
Name: createNamespaceFormValues.name,
|
||||
Owner: owner,
|
||||
ResourceQuota: {
|
||||
enabled: createNamespaceFormValues.resourceQuota.enabled,
|
||||
cpu: createNamespaceFormValues.resourceQuota.cpu,
|
||||
|
|
|
@ -97,9 +97,7 @@ export function NamespaceInnerForm({
|
|||
}
|
||||
errors={errors.registries}
|
||||
/>
|
||||
{storageClasses.length > 0 && (
|
||||
<StorageQuotaFormSection storageClasses={storageClasses} />
|
||||
)}
|
||||
{storageClasses.length > 0 && <StorageQuotaFormSection />}
|
||||
<NamespaceSummary
|
||||
initialValues={initialValues}
|
||||
values={values}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|||
import { InlineLoader } from '@@/InlineLoader';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { FormSection } from '@@/form-components/FormSection';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
import { RegistriesSelector } from './RegistriesSelector';
|
||||
|
||||
|
@ -24,10 +25,13 @@ export function RegistriesFormSection({ values, onChange, errors }: Props) {
|
|||
});
|
||||
return (
|
||||
<FormSection title="Registries">
|
||||
<TextTip color="blue" className="mb-2">
|
||||
Define which registries can be used by users who have access to this
|
||||
namespace.
|
||||
</TextTip>
|
||||
<FormControl
|
||||
inputId="registries"
|
||||
label="Select registries"
|
||||
required
|
||||
errors={errors}
|
||||
>
|
||||
{registriesQuery.isLoading && (
|
||||
|
|
|
@ -35,22 +35,12 @@ export function ResourceQuotaFormSection({
|
|||
|
||||
return (
|
||||
<FormSection title="Resource Quota">
|
||||
{values.enabled ? (
|
||||
<TextTip color="blue">
|
||||
A namespace is a logical abstraction of a Kubernetes cluster, to
|
||||
provide for more flexible management of resources. Best practice is to
|
||||
set a quota assignment as this ensures greatest security/stability;
|
||||
alternatively, you can disable assigning a quota for unrestricted
|
||||
access (not recommended).
|
||||
</TextTip>
|
||||
) : (
|
||||
<TextTip color="blue">
|
||||
A namespace is a logical abstraction of a Kubernetes cluster, to
|
||||
provide for more flexible management of resources. Resource
|
||||
over-commit is disabled, please assign a capped limit of resources to
|
||||
this namespace.
|
||||
</TextTip>
|
||||
)}
|
||||
<TextTip color="blue">
|
||||
A resource quota sets boundaries on the compute resources a namespace
|
||||
can use. It's good practice to set a quota for a namespace to
|
||||
manage resources effectively. Alternatively, you can disable assigning a
|
||||
quota for unrestricted access (not recommended).
|
||||
</TextTip>
|
||||
|
||||
<SwitchField
|
||||
data-cy="k8sNamespaceCreate-resourceAssignmentToggle"
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
import { StorageClass } from '@/react/portainer/environments/types';
|
||||
|
||||
import { FormSection } from '@@/form-components/FormSection';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
import { StorageQuotaItem } from './StorageQuotaItem';
|
||||
|
||||
interface Props {
|
||||
storageClasses: StorageClass[];
|
||||
}
|
||||
|
||||
export function StorageQuotaFormSection({ storageClasses }: Props) {
|
||||
export function StorageQuotaFormSection() {
|
||||
return (
|
||||
<FormSection title="Storage">
|
||||
<TextTip color="blue">
|
||||
|
@ -19,9 +13,7 @@ export function StorageQuotaFormSection({ storageClasses }: Props) {
|
|||
this namespace.
|
||||
</TextTip>
|
||||
|
||||
{storageClasses.map((storageClass) => (
|
||||
<StorageQuotaItem key={storageClass.Name} storageClass={storageClass} />
|
||||
))}
|
||||
<StorageQuotaItem />
|
||||
</FormSection>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
import { Database } from 'lucide-react';
|
||||
|
||||
import { StorageClass } from '@/react/portainer/environments/types';
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
import { FormSectionTitle } from '@@/form-components/FormSectionTitle';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
|
||||
type Props = {
|
||||
storageClass: StorageClass;
|
||||
};
|
||||
|
||||
export function StorageQuotaItem({ storageClass }: Props) {
|
||||
export function StorageQuotaItem() {
|
||||
return (
|
||||
<div key={storageClass.Name}>
|
||||
<div>
|
||||
<FormSectionTitle>
|
||||
<div className="vertical-center text-muted inline-flex gap-1 align-top">
|
||||
<Icon icon={Database} className="!mt-0.5 flex-none" />
|
||||
<span>{storageClass.Name}</span>
|
||||
<span>standard</span>
|
||||
</div>
|
||||
</FormSectionTitle>
|
||||
<hr className="mt-2 mb-0 w-full" />
|
||||
|
|
Loading…
Reference in New Issue