mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
chore(data-cy): require data-cy attributes [EE-6880] (#11453)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
This commit is contained in:
parent
3cad13388c
commit
d38085a560
538 changed files with 2571 additions and 595 deletions
|
@ -39,7 +39,11 @@ export function StackName({
|
|||
<br />
|
||||
You can leave the stack name empty, or even turn off Kubernetes Stacks
|
||||
functionality entirely via{' '}
|
||||
<Link to="portainer.settings" target="_blank">
|
||||
<Link
|
||||
to="portainer.settings"
|
||||
target="_blank"
|
||||
data-cy="k8s-deploy-stack-input-settings-link"
|
||||
>
|
||||
Kubernetes Settings
|
||||
</Link>
|
||||
.
|
||||
|
@ -71,6 +75,7 @@ export function StackName({
|
|||
onChange={setStackName}
|
||||
placeholder="e.g. myStack"
|
||||
inputId="stack_name"
|
||||
data-cy="k8s-deploy-stack-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,11 @@ export function StackNameLabelInsight() {
|
|||
<>
|
||||
<br />
|
||||
Kubernetes Stacks functionality can be turned off entirely via{' '}
|
||||
<Link to="portainer.settings" target="_blank">
|
||||
<Link
|
||||
to="portainer.settings"
|
||||
target="_blank"
|
||||
data-cy="k8s-deploy-settings-link"
|
||||
>
|
||||
Kubernetes Settings
|
||||
</Link>
|
||||
.
|
||||
|
|
|
@ -40,6 +40,7 @@ export function AnnotationsBeTeaser() {
|
|||
message="Allows specifying of annotations on this resource."
|
||||
featureId={FeatureId.K8S_ANNOTATIONS}
|
||||
buttonClassName="!ml-0"
|
||||
data-cy="annotations-be-teaser"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -46,10 +46,11 @@ export function AnnotationsForm({
|
|||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleAnnotationChange(i, 'Key', e.target.value)
|
||||
}
|
||||
data-cy={`annotation-key-${i}`}
|
||||
/>
|
||||
</div>
|
||||
{annotationErrors?.[i]?.Key && (
|
||||
<FormError className="mt-1 !mb-0">
|
||||
<FormError className="!mb-0 mt-1">
|
||||
{annotationErrors[i]?.Key}
|
||||
</FormError>
|
||||
)}
|
||||
|
@ -66,10 +67,11 @@ export function AnnotationsForm({
|
|||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleAnnotationChange(i, 'Value', e.target.value)
|
||||
}
|
||||
data-cy={`annotation-value-${i}`}
|
||||
/>
|
||||
</div>
|
||||
{annotationErrors?.[i]?.Value && (
|
||||
<FormError className="mt-1 !mb-0">
|
||||
<FormError className="!mb-0 mt-1">
|
||||
{annotationErrors[i]?.Value}
|
||||
</FormError>
|
||||
)}
|
||||
|
@ -77,6 +79,7 @@ export function AnnotationsForm({
|
|||
<div className="col-sm-3 !m-0 !pl-0">
|
||||
<Button
|
||||
size="small"
|
||||
data-cy={`remove-annotation-${i}`}
|
||||
color="dangerlight"
|
||||
className="btn-only-icon !ml-0"
|
||||
type="button"
|
||||
|
|
|
@ -11,6 +11,7 @@ import { PageHeader } from '@@/PageHeader';
|
|||
import { Widget, WidgetBody } from '@@/Widget';
|
||||
import { Icon } from '@@/Icon';
|
||||
import { Button } from '@@/buttons';
|
||||
import { Input } from '@@/form-components/Input';
|
||||
|
||||
interface StringDictionary {
|
||||
[index: string]: string;
|
||||
|
@ -129,7 +130,7 @@ export function ConsoleView() {
|
|||
<span className="input-group-addon">
|
||||
<Icon icon={TerminalIcon} className="mr-1" />
|
||||
</span>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="/bin/bash"
|
||||
|
@ -141,12 +142,14 @@ export function ConsoleView() {
|
|||
// https://portainer.atlassian.net/browse/EE-5752
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
data-cy="console-command-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row mt-4">
|
||||
<Button
|
||||
className="btn btn-primary !ml-0"
|
||||
data-cy="connect-console-button"
|
||||
onClick={
|
||||
connectionStatus === 'closed'
|
||||
? connectConsole
|
||||
|
|
|
@ -35,13 +35,18 @@ function UpdateIngressPrompt({
|
|||
|
||||
<SwitchField
|
||||
name="noMatch"
|
||||
data-cy="kube-update-ingress-prompt-switch"
|
||||
label={inputLabel}
|
||||
checked={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => onSubmit({ noMatch: value })} color="primary">
|
||||
<Button
|
||||
onClick={() => onSubmit({ noMatch: value })}
|
||||
color="primary"
|
||||
data-cy="update-ingress-confirm-button"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
|
|
|
@ -50,6 +50,7 @@ export function ClusterIpServiceForm({
|
|||
<div className="text-muted vertical-center">ClusterIP</div>
|
||||
<Button
|
||||
icon={Trash2}
|
||||
data-cy={`remove-service_${serviceIndex}`}
|
||||
color="dangerlight"
|
||||
className="!ml-0"
|
||||
onClick={() => {
|
||||
|
@ -174,6 +175,7 @@ export function ClusterIpServiceForm({
|
|||
<div className="flex">
|
||||
<Button
|
||||
icon={Plus}
|
||||
data-cy={`add-port_${serviceIndex}`}
|
||||
color="default"
|
||||
className="!ml-0"
|
||||
onClick={() => {
|
||||
|
|
|
@ -27,6 +27,7 @@ export function ServiceTabs({
|
|||
>
|
||||
<input
|
||||
type="radio"
|
||||
data-cy="service-type-radio"
|
||||
name="widget-tabs"
|
||||
className="hidden"
|
||||
value={serviceTypeOptions[index].value}
|
||||
|
|
|
@ -121,12 +121,14 @@ export function AppIngressPathForm({
|
|||
};
|
||||
onChangeIngressPath(newIngressPath);
|
||||
}}
|
||||
data-cy="k8sAppCreate-ingressPathHostSelect"
|
||||
/>
|
||||
<InputGroup.ButtonWrapper>
|
||||
<Button
|
||||
icon={RefreshCw}
|
||||
color="default"
|
||||
onClick={() => ingressesQuery.refetch()}
|
||||
data-cy="k8sAppCreate-ingressPathHostRefreshButton"
|
||||
/>
|
||||
</InputGroup.ButtonWrapper>
|
||||
</InputGroup>
|
||||
|
@ -138,6 +140,7 @@ export function AppIngressPathForm({
|
|||
to="kubernetes.ingresses.create"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-cy="k8sAppCreate-ingressPathCreateIngressLink"
|
||||
>
|
||||
create an ingress
|
||||
</Link>
|
||||
|
@ -153,6 +156,7 @@ export function AppIngressPathForm({
|
|||
<InputGroup.Addon required>Path</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={ingressPath?.Path ?? ''}
|
||||
data-cy={`k8sAppCreate-ingressPathPathInput-${ingressPath?.Host}`}
|
||||
placeholder="/example"
|
||||
onChange={(e) => {
|
||||
const newIngressPath = {
|
||||
|
@ -174,6 +178,7 @@ export function AppIngressPathForm({
|
|||
size="medium"
|
||||
className="!ml-0"
|
||||
onClick={() => onRemoveIngressPath()}
|
||||
data-cy="k8sAppCreate-removeIngressPathButton"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -115,6 +115,7 @@ export function AppIngressPathsForm({
|
|||
to="kubernetes.ingresses"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-cy="applicationCreate-ingressesLink"
|
||||
>
|
||||
Ingresses screen
|
||||
</Link>{' '}
|
||||
|
@ -153,6 +154,7 @@ export function AppIngressPathsForm({
|
|||
<div className="flex w-full flex-wrap gap-2">
|
||||
<Button
|
||||
icon={Plus}
|
||||
data-cy={`applicationCreate-addIngressPath-${serviceIndex}-${portIndex}`}
|
||||
className="!ml-0"
|
||||
size="small"
|
||||
color="default"
|
||||
|
|
|
@ -55,6 +55,7 @@ export function LoadBalancerServiceForm({
|
|||
<div className="text-muted vertical-center">LoadBalancer</div>
|
||||
<Button
|
||||
icon={Trash2}
|
||||
data-cy={`remove-service-${serviceIndex}`}
|
||||
color="dangerlight"
|
||||
className="!ml-0"
|
||||
onClick={() => {
|
||||
|
@ -213,6 +214,7 @@ export function LoadBalancerServiceForm({
|
|||
<div className="flex">
|
||||
<Button
|
||||
icon={Plus}
|
||||
data-cy={`k8sAppCreate-addPortButton_${serviceIndex}`}
|
||||
color="default"
|
||||
className="!ml-0"
|
||||
onClick={() => {
|
||||
|
|
|
@ -76,6 +76,7 @@ export function LoadBalancerServicesForm({
|
|||
to="kubernetes.cluster.setup"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-cy="k8sAppCreate-clusterSetupLink"
|
||||
>
|
||||
Cluster Setup
|
||||
</Link>{' '}
|
||||
|
@ -88,6 +89,7 @@ export function LoadBalancerServicesForm({
|
|||
<div className="flex">
|
||||
<Button
|
||||
icon={RefreshCw}
|
||||
data-cy="k8sAppCreate-refreshLoadBalancerButton"
|
||||
color="default"
|
||||
className="!ml-0"
|
||||
onClick={() => loadBalancerEnabledQuery.refetch()}
|
||||
|
|
|
@ -56,6 +56,7 @@ export function NodePortServiceForm({
|
|||
<div className="text-muted vertical-center">NodePort</div>
|
||||
<Button
|
||||
icon={Trash2}
|
||||
data-cy={`remove-service-${serviceIndex}`}
|
||||
color="dangerlight"
|
||||
className="!ml-0"
|
||||
onClick={() => {
|
||||
|
@ -214,6 +215,7 @@ export function NodePortServiceForm({
|
|||
<div className="flex">
|
||||
<Button
|
||||
icon={Plus}
|
||||
data-cy={`k8sAppCreate-addNodePortButton_${serviceIndex}`}
|
||||
color="default"
|
||||
className="!ml-0"
|
||||
onClick={() => {
|
||||
|
|
|
@ -35,6 +35,7 @@ export function ApplicationYAMLEditor() {
|
|||
identifier="application-yaml"
|
||||
data={fullApplicationYaml}
|
||||
hideMessage
|
||||
data-cy="application-yaml"
|
||||
/>
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
|
|
|
@ -60,6 +60,7 @@ export function ApplicationContainersDatatable() {
|
|||
titleIcon={Server}
|
||||
getRowId={(row) => row.podName} // use pod name because it's unique (name is not unique)
|
||||
disableSelect
|
||||
data-cy="k8s-application-containers-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export function getActions(isServerMetricsEnabled: boolean) {
|
|||
className="flex items-center gap-1"
|
||||
to="kubernetes.applications.application.stats"
|
||||
params={{ pod: container.podName, container: container.name }}
|
||||
data-cy={`application-container-stats-${container.name}`}
|
||||
>
|
||||
<Icon icon={BarChart} />
|
||||
Stats
|
||||
|
@ -27,6 +28,7 @@ export function getActions(isServerMetricsEnabled: boolean) {
|
|||
className="flex items-center gap-1"
|
||||
to="kubernetes.applications.application.logs"
|
||||
params={{ pod: container.podName, container: container.name }}
|
||||
data-cy={`application-container-logs-${container.name}`}
|
||||
>
|
||||
<Icon icon={FileText} />
|
||||
Logs
|
||||
|
@ -37,6 +39,7 @@ export function getActions(isServerMetricsEnabled: boolean) {
|
|||
className="flex items-center gap-1"
|
||||
to="kubernetes.applications.application.console"
|
||||
params={{ pod: container.podName, container: container.name }}
|
||||
data-cy={`application-container-console-${container.name}`}
|
||||
>
|
||||
<Icon icon={Terminal} />
|
||||
Console
|
||||
|
|
|
@ -14,7 +14,11 @@ export const node = columnHelper.accessor('nodeName', {
|
|||
childrenUnauthorized={nodeName}
|
||||
adminOnlyCE
|
||||
>
|
||||
<Link to="kubernetes.cluster.node" params={{ nodeName }}>
|
||||
<Link
|
||||
to="kubernetes.cluster.node"
|
||||
params={{ nodeName }}
|
||||
data-cy={`application-container-node-${nodeName}`}
|
||||
>
|
||||
<div className="max-w-xs truncate" title={nodeName}>
|
||||
{nodeName}
|
||||
</div>
|
||||
|
|
|
@ -67,7 +67,10 @@ export function ApplicationDetailsWidget() {
|
|||
{!isSystemNamespace && (
|
||||
<div className="mb-4 flex flex-wrap gap-2">
|
||||
<Authorized authorizations="K8sApplicationDetailsW">
|
||||
<Link to="kubernetes.applications.application.edit">
|
||||
<Link
|
||||
to="kubernetes.applications.application.edit"
|
||||
data-cy="k8sAppDetail-editAppLink"
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
color="light"
|
||||
|
|
|
@ -98,6 +98,7 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
|
|||
namespace,
|
||||
}}
|
||||
className="flex items-center"
|
||||
data-cy={`configmap-link-${envVar.resourseName}`}
|
||||
>
|
||||
<Icon
|
||||
icon={envVar.type === 'configMap' ? FileCode : Lock}
|
||||
|
|
|
@ -48,6 +48,7 @@ export function ApplicationIngressesTable({
|
|||
<Link
|
||||
to="kubernetes.ingresses.edit"
|
||||
params={{ name: ingressPath.ingressName, namespace }}
|
||||
data-cy={`ingress-link-${ingressPath.ingressName}`}
|
||||
>
|
||||
{ingressPath.ingressName}
|
||||
</Link>
|
||||
|
|
|
@ -109,6 +109,7 @@ export function ApplicationPersistentDataTable({
|
|||
name: `${persistedFolder.volume.persistentVolumeClaim.claimName}-${persistedFolder.volumeMount?.pod?.metadata?.name}`,
|
||||
namespace,
|
||||
}}
|
||||
data-cy={`k8sAppDetail-volMountPath-${index}`}
|
||||
>
|
||||
<Icon icon={Database} className="!mr-1 shrink-0" />
|
||||
{`${persistedFolder.volume.persistentVolumeClaim.claimName}-${persistedFolder.volumeMount?.pod?.metadata?.name}`}
|
||||
|
@ -146,6 +147,7 @@ export function ApplicationPersistentDataTable({
|
|||
.claimName,
|
||||
namespace,
|
||||
}}
|
||||
data-cy={`k8sAppDetail-volMountPath-${index}`}
|
||||
>
|
||||
<Icon icon={Database} className="!mr-1 shrink-0" />
|
||||
{
|
||||
|
|
|
@ -81,6 +81,7 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
|
|||
className="flex items-center"
|
||||
to="kubernetes.secrets.secret"
|
||||
params={{ name: volumeConfigName, namespace }}
|
||||
data-cy={`secret-link-${volumeConfigName}`}
|
||||
>
|
||||
<Icon icon={Plus} className="!mr-1" />
|
||||
{volumeConfigName}
|
||||
|
@ -90,6 +91,7 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
|
|||
className="flex items-center"
|
||||
to="kubernetes.configmaps.configmap"
|
||||
params={{ name: volumeConfigName, namespace }}
|
||||
data-cy={`config-link-${volumeConfigName}`}
|
||||
>
|
||||
<Icon icon={Plus} className="!mr-1" />
|
||||
{volumeConfigName}
|
||||
|
|
|
@ -100,7 +100,7 @@ export function ApplicationSummaryWidget() {
|
|||
<>
|
||||
{failedCreateCondition && (
|
||||
<div
|
||||
className="flex gap-1 items-start alert alert-danger mb-2"
|
||||
className="alert alert-danger mb-2 flex items-start gap-1"
|
||||
data-cy="k8sAppDetail-failedCreateMessage"
|
||||
>
|
||||
<div className="mt-0.5">
|
||||
|
@ -118,7 +118,7 @@ export function ApplicationSummaryWidget() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DetailsTable>
|
||||
<DetailsTable dataCy="k8sAppDetail-table">
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
|
@ -153,6 +153,7 @@ export function ApplicationSummaryWidget() {
|
|||
>
|
||||
<Link
|
||||
to="kubernetes.resourcePools.resourcePool"
|
||||
data-cy="k8sAppDetail-namespaceLink"
|
||||
params={{ id: namespace }}
|
||||
>
|
||||
{namespace}
|
||||
|
|
|
@ -68,6 +68,7 @@ export function PlacementsDatatable({
|
|||
renderSubRow={(row) => (
|
||||
<SubRow node={row.original} cellCount={row.getVisibleCells().length} />
|
||||
)}
|
||||
data-cy="kubernetes-application-placements-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export function ApplicationsStacksDatatable({
|
|||
emptyContentLabel="No stack available."
|
||||
description={
|
||||
<div className="w-full">
|
||||
<div className="min-w-[140px] float-right mr-2">
|
||||
<div className="float-right mr-2 min-w-[140px]">
|
||||
<NamespaceFilter
|
||||
namespaces={namespaces}
|
||||
value={namespace}
|
||||
|
@ -96,6 +96,7 @@ export function ApplicationsStacksDatatable({
|
|||
/>
|
||||
)}
|
||||
getRowId={(row) => `${row.Name}-${row.ResourcePool}`}
|
||||
data-cy="applications-stacks-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ export function NamespaceFilter({
|
|||
</InputGroup.Addon>
|
||||
<Select
|
||||
className="!h-[30px] py-1"
|
||||
data-cy="app-stacks-namespace-filter"
|
||||
value={value || ''}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
options={[
|
||||
|
|
|
@ -31,6 +31,7 @@ export function SubRows({
|
|||
<Link
|
||||
to="kubernetes.applications.application"
|
||||
params={{ name: app.Name, namespace: app.ResourcePool }}
|
||||
data-cy={`app-stack-application-link-${app.Name}`}
|
||||
>
|
||||
{app.Name}
|
||||
</Link>
|
||||
|
|
|
@ -21,13 +21,14 @@ export const columns = [
|
|||
columnHelper.accessor('ResourcePool', {
|
||||
id: 'namespace',
|
||||
header: 'Namespace',
|
||||
cell: ({ getValue }) => {
|
||||
cell: ({ getValue, row }) => {
|
||||
const value = getValue();
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Link
|
||||
to="kubernetes.resourcePools.resourcePool"
|
||||
params={{ id: value }}
|
||||
data-cy={`app-stack-namespace-link-${row.original.Name}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
|
@ -52,6 +53,7 @@ export const columns = [
|
|||
to="kubernetes.stacks.stack.logs"
|
||||
params={{ namespace: item.ResourcePool, name: item.Name }}
|
||||
className="flex items-center gap-1"
|
||||
data-cy={`app-stack-logs-link-${item.Name}`}
|
||||
>
|
||||
<Icon icon={FileText} />
|
||||
Logs
|
||||
|
|
|
@ -29,6 +29,7 @@ export function AutoScalingFormSection({
|
|||
{!isMetricsEnabled && <NoMetricsServerWarning />}
|
||||
<SwitchField
|
||||
disabled={!isMetricsEnabled}
|
||||
data-cy="k8sAppCreate-autoScaleSwitch"
|
||||
label="Enable auto scaling for this application"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
checked={values.isUsed}
|
||||
|
@ -50,9 +51,9 @@ export function AutoScalingFormSection({
|
|||
}}
|
||||
/>
|
||||
{values.isUsed && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 w-full gap-x-4 gap-y-2 my-3">
|
||||
<div className="flex flex-col min-w-fit">
|
||||
<label htmlFor="min-instances" className="font-normal text-xs">
|
||||
<div className="my-3 grid w-full grid-cols-1 gap-x-4 gap-y-2 md:grid-cols-3">
|
||||
<div className="flex min-w-fit flex-col">
|
||||
<label htmlFor="min-instances" className="text-xs font-normal">
|
||||
Minimum instances
|
||||
</label>
|
||||
<Input
|
||||
|
@ -71,8 +72,8 @@ export function AutoScalingFormSection({
|
|||
/>
|
||||
{errors?.minReplicas && <FormError>{errors.minReplicas}</FormError>}
|
||||
</div>
|
||||
<div className="flex flex-col min-w-fit">
|
||||
<label htmlFor="max-instances" className="font-normal text-xs">
|
||||
<div className="flex min-w-fit flex-col">
|
||||
<label htmlFor="max-instances" className="text-xs font-normal">
|
||||
Maximum instances
|
||||
</label>
|
||||
<Input
|
||||
|
@ -90,10 +91,10 @@ export function AutoScalingFormSection({
|
|||
/>
|
||||
{errors?.maxReplicas && <FormError>{errors.maxReplicas}</FormError>}
|
||||
</div>
|
||||
<div className="flex flex-col min-w-fit">
|
||||
<div className="flex min-w-fit flex-col">
|
||||
<label
|
||||
htmlFor="cpu-threshold"
|
||||
className="font-normal text-xs flex items-center"
|
||||
className="flex items-center text-xs font-normal"
|
||||
>
|
||||
Target CPU usage (<b>%</b>)
|
||||
<Tooltip message="The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances." />
|
||||
|
@ -135,7 +136,10 @@ function NoMetricsServerWarning() {
|
|||
{isAdmin && (
|
||||
<>
|
||||
Server metrics features must be enabled in the{' '}
|
||||
<Link to="kubernetes.cluster.setup">
|
||||
<Link
|
||||
to="kubernetes.cluster.setup"
|
||||
data-cy="environment-configuration-view"
|
||||
>
|
||||
environment configuration view
|
||||
</Link>
|
||||
.
|
||||
|
|
|
@ -46,8 +46,7 @@ export function ConfigMapsFormSection({
|
|||
onChange={onChange}
|
||||
errors={errors}
|
||||
isDeleteButtonHidden
|
||||
deleteButtonDataCy="k8sAppCreate-configRemoveButton"
|
||||
addButtonDataCy="k8sAppCreate-configAddButton"
|
||||
data-cy="k8sAppCreate-config"
|
||||
disabled={configMaps.length === 0}
|
||||
addButtonError={
|
||||
configMaps.length === 0
|
||||
|
|
|
@ -74,6 +74,7 @@ export function ConfigurationItem({
|
|||
className={clsx('!ml-0', { active: !item.overriden })}
|
||||
onClick={() => onToggleOverride(false)}
|
||||
icon={RotateCw}
|
||||
data-cy={`k8sAppCreate-add${configurationType}AutoButton_${index}`}
|
||||
>
|
||||
Auto
|
||||
</Button>
|
||||
|
@ -83,6 +84,7 @@ export function ConfigurationItem({
|
|||
className={clsx('!ml-0 mr-1', { active: item.overriden })}
|
||||
onClick={() => onToggleOverride(true)}
|
||||
icon={List}
|
||||
data-cy={`k8sAppCreate-add${configurationType}OverrideButton_${index}`}
|
||||
>
|
||||
Override
|
||||
</Button>
|
||||
|
@ -92,8 +94,9 @@ export function ConfigurationItem({
|
|||
color="dangerlight"
|
||||
size="medium"
|
||||
onClick={onRemoveItem}
|
||||
className="!ml-0 vertical-center btn-only-icon"
|
||||
className="vertical-center btn-only-icon !ml-0"
|
||||
icon={Trash2}
|
||||
data-cy={`k8sAppCreate-remove${configurationType}Button_${index}`}
|
||||
/>
|
||||
</div>
|
||||
{!item.overriden && (
|
||||
|
|
|
@ -36,10 +36,15 @@ export function ConfigurationData({
|
|||
? overrideKeysErrors[keyIndex]
|
||||
: undefined;
|
||||
return (
|
||||
<div className="flex items-start gap-x-2 gap-y-2 flex-wrap">
|
||||
<div className="flex flex-wrap items-start gap-x-2 gap-y-2">
|
||||
<InputGroup size="small" className="min-w-[250px]">
|
||||
<InputGroup.Addon>Key</InputGroup.Addon>
|
||||
<InputGroup.Input type="text" value={value.key} disabled />
|
||||
<InputGroup.Input
|
||||
type="text"
|
||||
value={value.key}
|
||||
disabled
|
||||
data-cy={`k8sAppCreate-${dataCyType}KeyInput_${configurationIndex}_${keyIndex}`}
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup size="small">
|
||||
<InputGroup.ButtonWrapper>
|
||||
|
|
|
@ -46,8 +46,7 @@ export function SecretsFormSection({
|
|||
onChange={onChange}
|
||||
errors={errors}
|
||||
isDeleteButtonHidden
|
||||
deleteButtonDataCy="k8sAppCreate-secretRemoveButton"
|
||||
addButtonDataCy="k8sAppCreate-secretAddButton"
|
||||
data-cy="k8sAppCreate-secret"
|
||||
disabled={secrets.length === 0}
|
||||
addButtonError={
|
||||
secrets.length === 0
|
||||
|
|
|
@ -29,6 +29,7 @@ export function EditYamlFormSection({
|
|||
return (
|
||||
<div>
|
||||
<WebEditorForm
|
||||
data-cy="k8s-yaml-editor"
|
||||
value={values}
|
||||
readonly={!isAllowedToEdit}
|
||||
titleContent={<TitleContent isComposeFormat={isComposeFormat} />}
|
||||
|
|
|
@ -44,7 +44,7 @@ export function PersistedFolderItem({
|
|||
const formikError = isErrorType(error) ? error : undefined;
|
||||
|
||||
return (
|
||||
<div className="flex items-start flex-wrap gap-x-2 gap-y-2">
|
||||
<div className="flex flex-wrap items-start gap-x-2 gap-y-2">
|
||||
<div>
|
||||
<InputGroup
|
||||
size="small"
|
||||
|
@ -99,7 +99,7 @@ export function PersistedFolderItem({
|
|||
<InputGroup
|
||||
size="small"
|
||||
className={clsx(
|
||||
'min-w-fit flex',
|
||||
'flex min-w-fit',
|
||||
item.needsDeletion && 'striked'
|
||||
)}
|
||||
>
|
||||
|
@ -107,7 +107,7 @@ export function PersistedFolderItem({
|
|||
Requested size
|
||||
</InputGroup.Addon>
|
||||
<Input
|
||||
className="!rounded-none -mr-[1px] !w-20"
|
||||
className="-mr-[1px] !w-20 !rounded-none"
|
||||
type="number"
|
||||
placeholder="e.g. 20"
|
||||
min="0"
|
||||
|
|
|
@ -61,8 +61,7 @@ export function PersistedFoldersFormSection({
|
|||
isEdit && applicationValues.ApplicationType === 'StatefulSet'
|
||||
}
|
||||
canUndoDelete={isEdit}
|
||||
deleteButtonDataCy="k8sAppCreate-persistentFolderRemoveButton"
|
||||
addButtonDataCy="k8sAppCreate-persistentFolderAddButton"
|
||||
data-cy="k8sAppCreate-persistentFolder"
|
||||
disabled={storageClasses.length === 0}
|
||||
addButtonError={getAddButtonError(storageClasses)}
|
||||
isAddButtonHidden={!isAddPersistentFolderButtonShown}
|
||||
|
|
|
@ -42,7 +42,7 @@ export function PlacementFormSection({ values, onChange, errors }: Props) {
|
|||
following placement rules. Placement rules are based on node labels.
|
||||
</TextTip>
|
||||
)}
|
||||
<InputList
|
||||
<InputList<Placement>
|
||||
value={values.placements}
|
||||
onChange={(placements) => onChange({ ...values, placements })}
|
||||
renderItem={(item, onChange, index, error) => (
|
||||
|
@ -63,7 +63,7 @@ export function PlacementFormSection({ values, onChange, errors }: Props) {
|
|||
errors={errors?.placements}
|
||||
addLabel="Add rule"
|
||||
canUndoDelete
|
||||
deleteButtonDataCy="k8sAppCreate-deletePlacementButton"
|
||||
data-cy="k8sAppCreate-placement"
|
||||
disabled={Object.keys(availableNodeLabels).length === 0}
|
||||
addButtonError={
|
||||
Object.keys(availableNodeLabels).length === 0
|
||||
|
|
|
@ -32,7 +32,7 @@ export function PlacementItem({
|
|||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex w-full gap-2">
|
||||
<div className="basis-1/2 grow">
|
||||
<div className="grow basis-1/2">
|
||||
<Select
|
||||
options={labelOptions}
|
||||
value={{ label: item.label, value: item.label }}
|
||||
|
@ -54,7 +54,7 @@ export function PlacementItem({
|
|||
<FormError>{placementError.label}</FormError>
|
||||
)}
|
||||
</div>
|
||||
<div className="basis-1/2 grow">
|
||||
<div className="grow basis-1/2">
|
||||
<Select
|
||||
options={valueOptions}
|
||||
value={valueOptions?.find((option) => option.value === item.value)}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
diff a/app/react/kubernetes/cluster/ConfigureView/ConfigureForm/EnableMetricsInput.tsx b/app/react/kubernetes/cluster/ConfigureView/ConfigureForm/EnableMetricsInput.tsx (rejected hunks)
|
||||
@@ -103,7 +103,10 @@ export function EnableMetricsInput({ value, error, environmentId }: Props) {
|
||||
<TextTip color="red" icon={XCircle}>
|
||||
Unable to reach metrics API. You can enable the metrics-server
|
||||
addon in the{' '}
|
||||
- <Link to="kubernetes.cluster">Cluster Details view</Link>.
|
||||
+ <Link to="kubernetes.cluster" data-cy="cluster-details-view-link">
|
||||
+ Cluster Details view
|
||||
+ </Link>
|
||||
+ .
|
||||
</TextTip>
|
||||
)}
|
||||
{metricsFound === true && (
|
|
@ -37,7 +37,7 @@ export function StorageClassDatatable({ storageClassValues }: Props) {
|
|||
checked
|
||||
)
|
||||
}
|
||||
className="mr-2 mb-0"
|
||||
className="mb-0 mr-2"
|
||||
id={`kubeSetup-storageToggle${storageClassValue.Name}`}
|
||||
name={`kubeSetup-storageToggle${storageClassValue.Name}`}
|
||||
data-cy={`kubeSetup-storageToggle${storageClassValue.Name}`}
|
||||
|
@ -68,7 +68,7 @@ export function StorageClassDatatable({ storageClassValues }: Props) {
|
|||
checked
|
||||
)
|
||||
}
|
||||
className="mr-2 mb-0"
|
||||
className="mb-0 mr-2"
|
||||
data-cy={`kubeSetup-storageExpansionToggle${storageClassValue.Name}`}
|
||||
id={`kubeSetup-storageExpansionToggle${storageClassValue.Name}`}
|
||||
name={`kubeSetup-storageExpansionToggle${storageClassValue.Name}`}
|
||||
|
|
|
@ -65,6 +65,7 @@ export function NodesDatatable() {
|
|||
/>
|
||||
</TableSettingsMenu>
|
||||
)}
|
||||
data-cy="k8s-nodes-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ function ActionsCell({
|
|||
to="kubernetes.cluster.node.stats"
|
||||
params={{ nodeName }}
|
||||
className="flex items-center p-1"
|
||||
data-cy="nodeStatsButton"
|
||||
>
|
||||
<Icon icon={BarChart} />
|
||||
</Link>
|
||||
|
|
|
@ -25,7 +25,11 @@ function NameCell({
|
|||
childrenUnauthorized={nodeName}
|
||||
adminOnlyCE
|
||||
>
|
||||
<Link to="kubernetes.cluster.node" params={{ nodeName }}>
|
||||
<Link
|
||||
to="kubernetes.cluster.node"
|
||||
params={{ nodeName }}
|
||||
data-cy={`node-name-link-${nodeName}`}
|
||||
>
|
||||
{nodeName}
|
||||
</Link>
|
||||
</Authorized>
|
||||
|
|
|
@ -51,6 +51,7 @@ export function NodeApplicationsDatatable({
|
|||
/>
|
||||
</TableSettingsMenu>
|
||||
)}
|
||||
data-cy="node-applications-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ function Cell({
|
|||
<Link
|
||||
to="kubernetes.applications.application"
|
||||
params={{ name: item.Name, namespace: item.ResourcePool }}
|
||||
data-cy={`application-link-${item.Name}`}
|
||||
>
|
||||
{item.Name}
|
||||
</Link>
|
||||
|
|
|
@ -26,6 +26,7 @@ export function useColumns(areStacksVisible: boolean) {
|
|||
<Link
|
||||
to="kubernetes.resourcePools.resourcePool"
|
||||
params={{ id: namespace }}
|
||||
data-cy={`namespace-link-${namespace}`}
|
||||
>
|
||||
{namespace}
|
||||
</Link>
|
||||
|
|
|
@ -51,8 +51,10 @@ export function AccessTable({
|
|||
</>
|
||||
}
|
||||
onConfirmed={() => onRemove(selectedItems)}
|
||||
data-cy="remove-registry-access-button"
|
||||
/>
|
||||
)}
|
||||
data-cy="registry-access-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
value: string[];
|
||||
onChange(value: string[]): void;
|
||||
namespaces: Namespace[];
|
||||
dataCy?: string;
|
||||
dataCy: string;
|
||||
inputId?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ export function IngressClassDatatable({
|
|||
getRowId={(row) => `${row.Name}-${row.ClassName}-${row.Type}`}
|
||||
renderTableActions={(selectedRows) => renderTableActions(selectedRows)}
|
||||
description={renderIngressClassDescription()}
|
||||
data-cy="ingress-class-datatable"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -59,6 +60,7 @@ export function IngressClassDatatable({
|
|||
<div className="flex items-start">
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
data-cy="disallow-ingress-controllers-button"
|
||||
disabled={
|
||||
selectedRows.filter((row) => row.Availability === true).length ===
|
||||
0
|
||||
|
@ -72,6 +74,7 @@ export function IngressClassDatatable({
|
|||
Disallow selected
|
||||
</Button>
|
||||
<Button
|
||||
data-cy="allow-ingress-controllers-button"
|
||||
disabled={
|
||||
selectedRows.filter((row) => row.Availability === false)
|
||||
.length === 0
|
||||
|
|
|
@ -105,6 +105,7 @@ export function IngressClassDatatableAngular({
|
|||
getRowId={(row) => `${row.Name}-${row.ClassName}-${row.Type}`}
|
||||
renderTableActions={(selectedRows) => renderTableActions(selectedRows)}
|
||||
description={renderIngressClassDescription()}
|
||||
data-cy="k8s-ingress-classes-datatable"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -127,6 +128,7 @@ export function IngressClassDatatableAngular({
|
|||
false
|
||||
)
|
||||
}
|
||||
data-cy="k8s-disallow-selected-ingress-controllers-button"
|
||||
>
|
||||
Disallow selected
|
||||
</Button>
|
||||
|
@ -144,6 +146,7 @@ export function IngressClassDatatableAngular({
|
|||
true
|
||||
)
|
||||
}
|
||||
data-cy="k8s-allow-selected-ingress-controllers-button"
|
||||
>
|
||||
Allow selected
|
||||
</Button>
|
||||
|
|
|
@ -21,12 +21,14 @@ export function IntegratedAppsDatatable({
|
|||
isLoading,
|
||||
tableKey,
|
||||
tableTitle,
|
||||
dataCy,
|
||||
}: {
|
||||
dataset: Array<IntegratedApp>;
|
||||
onRefresh: () => void;
|
||||
isLoading: boolean;
|
||||
tableKey: string;
|
||||
tableTitle: string;
|
||||
dataCy: string;
|
||||
}) {
|
||||
const tableState = useTableStateWithStorage<TableSettings>(
|
||||
tableKey,
|
||||
|
@ -54,6 +56,7 @@ export function IntegratedAppsDatatable({
|
|||
/>
|
||||
</TableSettingsMenu>
|
||||
)}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ function Cell({ row: { original: item } }: CellContext<IntegratedApp, string>) {
|
|||
<Link
|
||||
to="kubernetes.applications.application"
|
||||
params={{ name: item.Name, namespace: item.ResourcePool }}
|
||||
data-cy={`application-link-${item.Name}`}
|
||||
>
|
||||
{item.Name}
|
||||
</Link>
|
||||
|
|
|
@ -3,6 +3,7 @@ import YAML from 'yaml';
|
|||
import { Minus, Plus } from 'lucide-react';
|
||||
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
import { WebEditorForm } from '@@/WebEditorForm';
|
||||
import { Button } from '@@/buttons';
|
||||
|
@ -12,15 +13,21 @@ type Props = {
|
|||
identifier: string;
|
||||
data: string;
|
||||
hideMessage?: boolean;
|
||||
};
|
||||
} & AutomationTestingProps;
|
||||
|
||||
export function YAMLInspector({ identifier, data, hideMessage }: Props) {
|
||||
export function YAMLInspector({
|
||||
identifier,
|
||||
data,
|
||||
hideMessage,
|
||||
'data-cy': dataCy,
|
||||
}: Props) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const yaml = useMemo(() => cleanYamlUnwantedFields(data), [data]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<WebEditorForm
|
||||
data-cy={dataCy}
|
||||
value={yaml}
|
||||
placeholder={
|
||||
hideMessage
|
||||
|
@ -37,6 +44,7 @@ export function YAMLInspector({ identifier, data, hideMessage }: Props) {
|
|||
<div className="flex items-center justify-between py-5">
|
||||
<Button
|
||||
icon={expanded ? Minus : Plus}
|
||||
data-cy={`expand-collapse-yaml-${identifier}`}
|
||||
color="default"
|
||||
className="!ml-0"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
|
@ -48,6 +56,7 @@ export function YAMLInspector({ identifier, data, hideMessage }: Props) {
|
|||
heading="Apply YAML changes"
|
||||
message="Applies any changes that you make in the YAML editor by calling the Kubernetes API to patch the relevant resources. Any resource removals or unexpected resource additions that you make in the YAML will be ignored. Note that editing is disabled for resources in namespaces marked as system."
|
||||
buttonText="Apply changes"
|
||||
data-cy="yaml-inspector-apply-changes-teaser-button"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -103,6 +103,7 @@ export function ConfigMapsDatatable() {
|
|||
showSystemResources={tableState.showSystemResources}
|
||||
/>
|
||||
}
|
||||
data-cy="k8s-configmaps-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ function Cell({ row }: CellContext<ConfigMapRowData, string>) {
|
|||
}}
|
||||
title={name}
|
||||
className="w-fit max-w-xs truncate xl:max-w-sm 2xl:max-w-md"
|
||||
data-cy={`configmap-name-link-${name}`}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
|
|
@ -23,6 +23,7 @@ export const namespace = columnHelper.accessor(
|
|||
id: namespace,
|
||||
}}
|
||||
title={namespace}
|
||||
data-cy={`configmap-namespace-link-${namespace}`}
|
||||
>
|
||||
{namespace}
|
||||
</Link>
|
||||
|
|
|
@ -103,6 +103,7 @@ export function SecretsDatatable() {
|
|||
showSystemResources={tableState.showSystemResources}
|
||||
/>
|
||||
}
|
||||
data-cy="k8s-secrets-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ function Cell({ row }: CellContext<SecretRowData, string>) {
|
|||
}}
|
||||
title={name}
|
||||
className="w-fit max-w-xs truncate xl:max-w-sm 2xl:max-w-md"
|
||||
data-cy={`secret-name-link-${name}`}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
|
|
@ -23,6 +23,7 @@ export const namespace = columnHelper.accessor(
|
|||
id: namespace,
|
||||
}}
|
||||
title={namespace}
|
||||
data-cy={`secret-namespace-link-${namespace}`}
|
||||
>
|
||||
{namespace}
|
||||
</Link>
|
||||
|
|
|
@ -14,6 +14,7 @@ export function SystemResourcesSettings({
|
|||
<Authorized authorizations="K8sAccessSystemNamespaces" adminOnlyCE>
|
||||
<Checkbox
|
||||
id="show-system-resources"
|
||||
data-cy="show-system-resources"
|
||||
label="Show system resources"
|
||||
checked={value}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
|
|
|
@ -488,6 +488,7 @@ export function CreateIngressView() {
|
|||
params={{ id: environmentId }}
|
||||
className="text-primary"
|
||||
target="_blank"
|
||||
data-cy="ingresses-create-application-link"
|
||||
>
|
||||
Applications
|
||||
</Link>
|
||||
|
@ -613,6 +614,7 @@ export function CreateIngressView() {
|
|||
<div className="col-sm-12">
|
||||
<Button
|
||||
onClick={() => handleCreateIngressRules()}
|
||||
data-cy="ingresses-create-button"
|
||||
disabled={Object.keys(errors).length > 0}
|
||||
>
|
||||
{isEdit ? 'Update' : 'Create'}
|
||||
|
|
|
@ -15,6 +15,7 @@ import { InlineLoader } from '@@/InlineLoader';
|
|||
import { Select } from '@@/form-components/ReactSelect';
|
||||
import { Card } from '@@/Card';
|
||||
import { InputGroup } from '@@/form-components/InputGroup';
|
||||
import { Input } from '@@/form-components/Input';
|
||||
|
||||
import { AnnotationsForm } from '../../annotations/AnnotationsForm';
|
||||
|
||||
|
@ -182,6 +183,7 @@ export function IngressForm({
|
|||
: 'No namespaces available'
|
||||
}
|
||||
noOptionsMessage={() => 'No namespaces available'}
|
||||
data-cy="k8sAppCreate-namespaceSelect"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -204,7 +206,7 @@ export function IngressForm({
|
|||
{isEdit ? (
|
||||
rule.IngressName
|
||||
) : (
|
||||
<input
|
||||
<Input
|
||||
name="ingress_name"
|
||||
type="text"
|
||||
className="form-control"
|
||||
|
@ -214,6 +216,7 @@ export function IngressForm({
|
|||
handleIngressChange('IngressName', e.target.value)
|
||||
}
|
||||
disabled={isEdit}
|
||||
data-cy="k8sAppCreate-ingressNameInput"
|
||||
/>
|
||||
)}
|
||||
{errors.ingressName && !isEdit && (
|
||||
|
@ -262,6 +265,7 @@ export function IngressForm({
|
|||
)
|
||||
}
|
||||
noOptionsMessage={() => 'No ingress classes available'}
|
||||
data-cy="k8sAppCreate-ingressClassSelect"
|
||||
/>
|
||||
{errors.className && (
|
||||
<FormError className="error-inline mt-1">
|
||||
|
@ -318,6 +322,7 @@ export function IngressForm({
|
|||
<span>
|
||||
<Button
|
||||
className="btn btn-sm btn-light !ml-0 mb-2"
|
||||
data-cy="add-annotation-button"
|
||||
onClick={() => addNewAnnotation()}
|
||||
icon={Plus}
|
||||
>
|
||||
|
@ -408,6 +413,7 @@ export function IngressForm({
|
|||
</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
name={`ingress_host_${hostIndex}`}
|
||||
data-cy={`ingress-host_${hostIndex}`}
|
||||
id={`ingress_host_${hostIndex}`}
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
|
@ -457,6 +463,7 @@ export function IngressForm({
|
|||
}
|
||||
noOptionsMessage={() => 'No TLS secrets available'}
|
||||
size="sm"
|
||||
data-cy={`k8sAppCreate-tlsSelect_${hostIndex}`}
|
||||
/>
|
||||
{!host.NoHost && (
|
||||
<div className="input-group-btn">
|
||||
|
@ -464,6 +471,7 @@ export function IngressForm({
|
|||
className="btn btn-light btn-sm !ml-0 !rounded-l-none"
|
||||
onClick={() => reloadTLSCerts()}
|
||||
icon={RefreshCw}
|
||||
data-cy={`k8sAppCreate-tlsRefreshButton_${hostIndex}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -478,6 +486,7 @@ export function IngressForm({
|
|||
params={{ id: environmentID }}
|
||||
className="text-primary"
|
||||
target="_blank"
|
||||
data-cy={`k8sAppCreate-createSecretLink_${hostIndex}`}
|
||||
>
|
||||
Create secret
|
||||
</Link>{' '}
|
||||
|
@ -553,6 +562,7 @@ export function IngressForm({
|
|||
}
|
||||
noOptionsMessage={() => 'No services available'}
|
||||
size="sm"
|
||||
data-cy={`k8sAppCreate-serviceSelect_${hostIndex}_${pathIndex}`}
|
||||
/>
|
||||
</InputGroup>
|
||||
{errors[
|
||||
|
@ -614,6 +624,7 @@ export function IngressForm({
|
|||
}
|
||||
noOptionsMessage={() => 'No ports available'}
|
||||
size="sm"
|
||||
data-cy={`k8sAppCreate-servicePortSelect_${hostIndex}_${pathIndex}`}
|
||||
/>
|
||||
</InputGroup>
|
||||
{errors[
|
||||
|
@ -672,6 +683,7 @@ export function IngressForm({
|
|||
}
|
||||
noOptionsMessage={() => 'No path types available'}
|
||||
size="sm"
|
||||
data-cy={`k8sAppCreate-pathTypeSelect_${hostIndex}_${pathIndex}`}
|
||||
/>
|
||||
</InputGroup>
|
||||
{errors[
|
||||
|
@ -748,8 +760,7 @@ export function IngressForm({
|
|||
type="button"
|
||||
onClick={() => addNewIngressRoute(hostIndex)}
|
||||
icon={Plus}
|
||||
size="small"
|
||||
color="default"
|
||||
data-cy={`k8sAppCreate-addPathButton_${hostIndex}`}
|
||||
>
|
||||
Add path
|
||||
</Button>
|
||||
|
@ -766,8 +777,7 @@ export function IngressForm({
|
|||
type="button"
|
||||
onClick={() => addNewIngressHost()}
|
||||
icon={Plus}
|
||||
color="default"
|
||||
size="small"
|
||||
data-cy="k8sAppCreate-addHostButton"
|
||||
>
|
||||
Add new host
|
||||
</Button>
|
||||
|
@ -778,8 +788,7 @@ export function IngressForm({
|
|||
onClick={() => addNewIngressHost(true)}
|
||||
disabled={hasNoHostRule}
|
||||
icon={Plus}
|
||||
color="default"
|
||||
size="small"
|
||||
data-cy="k8sAppCreate-addFallbackButton"
|
||||
>
|
||||
Add fallback rule
|
||||
</Button>
|
||||
|
|
|
@ -90,6 +90,7 @@ export function IngressDatatable() {
|
|||
/>
|
||||
}
|
||||
disableSelect={useCheckboxes()}
|
||||
data-cy="k8s-ingresses-datatable"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -114,15 +115,15 @@ export function IngressDatatable() {
|
|||
<DeleteButton
|
||||
disabled={selectedFlatRows.length === 0}
|
||||
onConfirmed={() => handleRemoveClick(selectedFlatRows)}
|
||||
data-cy="k8sSecret-removeSecretButton"
|
||||
confirmMessage="Are you sure you want to delete the selected ingresses?"
|
||||
data-cy="remove-ingresses-button"
|
||||
/>
|
||||
|
||||
<AddButton to=".create" color="secondary">
|
||||
<AddButton to=".create" color="secondary" data-cy="add-ingress-button">
|
||||
Add with form
|
||||
</AddButton>
|
||||
|
||||
<CreateFromManifestButton />
|
||||
<CreateFromManifestButton data-cy="k8s-ingress-deploy-button" />
|
||||
</Authorized>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ function Cell({ row, getValue }: CellContext<Ingress, string>) {
|
|||
name,
|
||||
}}
|
||||
title={name}
|
||||
data-cy={`ingress-name-link-${name}`}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
|
|
@ -24,7 +24,7 @@ export const namespace = columnHelper.accessor('Namespace', {
|
|||
enableColumnFilter: true,
|
||||
});
|
||||
|
||||
function Cell({ getValue }: CellContext<Ingress, string>) {
|
||||
function Cell({ getValue, row }: CellContext<Ingress, string>) {
|
||||
const namespace = getValue();
|
||||
return (
|
||||
<Link
|
||||
|
@ -33,6 +33,7 @@ function Cell({ getValue }: CellContext<Ingress, string>) {
|
|||
id: namespace,
|
||||
}}
|
||||
title={namespace}
|
||||
data-cy={`ingress-namespace-link-${row.original.Name}`}
|
||||
>
|
||||
{namespace}
|
||||
</Link>
|
||||
|
|
|
@ -11,7 +11,7 @@ interface Props {
|
|||
value: Option[];
|
||||
onChange(value: readonly Option[]): void;
|
||||
options: Option[];
|
||||
dataCy?: string;
|
||||
dataCy: string;
|
||||
inputId?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ export function NamespaceAppsDatatable({
|
|||
/>
|
||||
</TableSettingsMenu>
|
||||
)}
|
||||
data-cy="namespace-apps-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ export const columns = [
|
|||
<Link
|
||||
to="kubernetes.applications.application"
|
||||
params={{ name: item.Name, namespace: item.ResourcePool }}
|
||||
data-cy={`application-link-${item.Name}`}
|
||||
>
|
||||
{item.Name}
|
||||
</Link>
|
||||
|
|
|
@ -70,11 +70,14 @@ export function NamespacesDatatable({
|
|||
<DeleteButton
|
||||
onClick={() => onRemove(selectedItems)}
|
||||
disabled={selectedItems.length === 0}
|
||||
data-cy="delete-namespace-button"
|
||||
/>
|
||||
|
||||
<AddButton color="secondary">Add with form</AddButton>
|
||||
<AddButton color="secondary" data-cy="add-namespace-form-button">
|
||||
Add with form
|
||||
</AddButton>
|
||||
|
||||
<CreateFromManifestButton />
|
||||
<CreateFromManifestButton data-cy="k8s-namespaces-deploy-button" />
|
||||
</Authorized>
|
||||
)}
|
||||
renderTableSettings={() => (
|
||||
|
|
|
@ -37,8 +37,10 @@ function Cell({
|
|||
props={{
|
||||
to: 'kubernetes.resourcePools.resourcePool.access',
|
||||
params: { id: item.Namespace.Name },
|
||||
'data-cy': `manage-access-link-${item.Namespace.Name}`,
|
||||
}}
|
||||
icon={Users}
|
||||
data-cy={`manage-access-button-${item.Namespace.Name}`}
|
||||
>
|
||||
Manage access
|
||||
</Button>
|
||||
|
|
|
@ -34,6 +34,7 @@ export function useColumns() {
|
|||
params={{
|
||||
id: name,
|
||||
}}
|
||||
data-cy={`namespace-link-${name}`}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
|
|
@ -24,11 +24,15 @@ export function RegistriesSelector({
|
|||
return (
|
||||
<>
|
||||
{options.length === 0 && (
|
||||
<p className="text-muted text-xs mb-1 mt-2">
|
||||
<p className="text-muted mb-1 mt-2 text-xs">
|
||||
{isPureAdmin ? (
|
||||
<span>
|
||||
No registries available. Head over to the{' '}
|
||||
<Link to="portainer.registries" target="_blank">
|
||||
<Link
|
||||
to="portainer.registries"
|
||||
target="_blank"
|
||||
data-cy="namespace-permissions-registries-selector"
|
||||
>
|
||||
registry view
|
||||
</Link>{' '}
|
||||
to define a container registry.
|
||||
|
|
|
@ -15,7 +15,7 @@ export function StorageQuotaItem() {
|
|||
<span>standard</span>
|
||||
</div>
|
||||
</FormSectionTitle>
|
||||
<hr className="mt-2 mb-0 w-full" />
|
||||
<hr className="mb-0 mt-2 w-full" />
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
|
|
|
@ -90,6 +90,7 @@ export function ServicesDatatable() {
|
|||
/>
|
||||
}
|
||||
renderRow={servicesRenderRow}
|
||||
data-cy="k8s-services-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -156,9 +157,10 @@ function TableActions({ selectedItems }: TableActionsProps) {
|
|||
</ul>
|
||||
</>
|
||||
}
|
||||
data-cy="k8s-remove-services-button"
|
||||
/>
|
||||
|
||||
<CreateFromManifestButton />
|
||||
<CreateFromManifestButton data-cy="k8s-create-service-button" />
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ function Cell({ row, getValue }: CellContext<Service, string>) {
|
|||
name: appName,
|
||||
}}
|
||||
title={appName}
|
||||
data-cy={`service-application-link-${appName}`}
|
||||
>
|
||||
{appName}
|
||||
</Link>
|
||||
|
|
|
@ -11,7 +11,7 @@ import { columnHelper } from './helper';
|
|||
export const namespace = columnHelper.accessor('Namespace', {
|
||||
header: 'Namespace',
|
||||
id: 'namespace',
|
||||
cell: ({ getValue }) => {
|
||||
cell: ({ getValue, row }) => {
|
||||
const namespace = getValue();
|
||||
|
||||
return (
|
||||
|
@ -21,6 +21,7 @@ export const namespace = columnHelper.accessor('Namespace', {
|
|||
id: namespace,
|
||||
}}
|
||||
title={namespace}
|
||||
data-cy={`service-namespace-link-${row.original.Name}`}
|
||||
>
|
||||
{namespace}
|
||||
</Link>
|
||||
|
|
|
@ -66,6 +66,7 @@ export function StorageDatatable({
|
|||
)}
|
||||
getRowCanExpand={(row) => row.original.Volumes.length > 0}
|
||||
renderSubRow={(row) => <SubRow item={row.original} />}
|
||||
data-cy="k8s-storage-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -83,6 +84,7 @@ function SubRow({ item }: { item: StorageClassViewModel }) {
|
|||
name: vol.PersistentVolumeClaim.Name,
|
||||
namespace: vol.PersistentVolumeClaim.Namespace,
|
||||
}}
|
||||
data-cy={`volume-link-${vol.PersistentVolumeClaim.Name}`}
|
||||
>
|
||||
{vol.PersistentVolumeClaim.Name}
|
||||
</Link>
|
||||
|
|
|
@ -70,8 +70,9 @@ export function VolumesDatatable({
|
|||
confirmMessage="Do you want to remove the selected volume(s)?"
|
||||
onConfirmed={() => onRemove(selectedItems)}
|
||||
disabled={selectedItems.length === 0}
|
||||
data-cy="k8s-volumes-delete-button"
|
||||
/>
|
||||
<CreateFromManifestButton />
|
||||
<CreateFromManifestButton data-cy="k8s-volumes-deploy-button" />
|
||||
</>
|
||||
)}
|
||||
renderTableSettings={() => (
|
||||
|
|
|
@ -33,6 +33,7 @@ export function NameCell({
|
|||
namespace: item.ResourcePool.Namespace.Name,
|
||||
name: item.PersistentVolumeClaim.Name,
|
||||
}}
|
||||
data-cy={`volume-link-${item.PersistentVolumeClaim.Name}`}
|
||||
>
|
||||
{item.PersistentVolumeClaim.Name}
|
||||
</Link>
|
||||
|
|
|
@ -16,6 +16,7 @@ export const columns = [
|
|||
<Link
|
||||
to="kubernetes.resourcePools.resourcePool"
|
||||
params={{ id: namespace }}
|
||||
data-cy={`volume-namespace-link-${namespace}`}
|
||||
>
|
||||
{namespace}
|
||||
</Link>
|
||||
|
@ -37,6 +38,7 @@ export const columns = [
|
|||
name: item.Applications[0].Name,
|
||||
namespace: item.ResourcePool.Namespace.Name,
|
||||
}}
|
||||
data-cy={`volume-application-link-${item.Applications[0].Name}`}
|
||||
>
|
||||
{item.Applications[0].Name}
|
||||
</Link>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue