mirror of
https://github.com/portainer/portainer.git
synced 2025-08-08 23:35:31 +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
|
@ -20,8 +20,10 @@ export function BackupFailedPanel() {
|
|||
<TextTip>
|
||||
The latest automated backup has failed at {isoDate(status.TimestampUTC)}
|
||||
. For details please see the log files and have a look at the{' '}
|
||||
<Link to="portainer.settings">settings</Link> to verify the backup
|
||||
configuration.
|
||||
<Link to="portainer.settings" data-cy="backup-failed-settings-link">
|
||||
settings
|
||||
</Link>{' '}
|
||||
to verify the backup configuration.
|
||||
</TextTip>
|
||||
</InformationPanel>
|
||||
);
|
||||
|
|
|
@ -35,7 +35,12 @@ export function AMTButton({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={openDialog} icon={Link} color="light">
|
||||
<Button
|
||||
onClick={openDialog}
|
||||
icon={Link}
|
||||
color="light"
|
||||
data-cy="associate-amt-button"
|
||||
>
|
||||
Associate with OpenAMT
|
||||
</Button>
|
||||
{isOpenDialog && (
|
||||
|
|
|
@ -53,6 +53,7 @@ export function AssociateAMTDialog({
|
|||
<div className="flex h-8 items-center">
|
||||
<Checkbox
|
||||
id="settings-container-truncate-name"
|
||||
data-cy="select-all-checkbox"
|
||||
label="Select all (in this page)"
|
||||
checked={isAllPageSelected}
|
||||
onChange={handleSelectAll}
|
||||
|
@ -67,6 +68,7 @@ export function AssociateAMTDialog({
|
|||
>
|
||||
<Checkbox
|
||||
id={`${env.Id}`}
|
||||
data-cy={`environment-checkbox-${env.Name}`}
|
||||
label={`${env.Name} (${env.URL})`}
|
||||
checked={selection.includes(env.Id)}
|
||||
onChange={() =>
|
||||
|
@ -89,11 +91,16 @@ export function AssociateAMTDialog({
|
|||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={onClose} color="default">
|
||||
<Button
|
||||
onClick={onClose}
|
||||
color="default"
|
||||
data-cy="associate-amt-dialog-cancel-button"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<LoadingButton
|
||||
onClick={handleSubmit}
|
||||
data-cy="associate-amt-dialog-associate-button"
|
||||
disabled={selection.length === 0}
|
||||
loadingText="Associating..."
|
||||
isLoading={activateDeviceMutation.isLoading}
|
||||
|
|
|
@ -39,6 +39,7 @@ export function EditButtons({ environment }: { environment: Environment }) {
|
|||
size="medium"
|
||||
className={buttonsClasses}
|
||||
title="Edit"
|
||||
data-cy={`edit-environment-link-${environment.Name}`}
|
||||
/>
|
||||
|
||||
<LinkButton
|
||||
|
@ -50,6 +51,7 @@ export function EditButtons({ environment }: { environment: Environment }) {
|
|||
size="medium"
|
||||
className={buttonsClasses}
|
||||
title="Configuration"
|
||||
data-cy={`configure-environment-link-${environment.Name}`}
|
||||
/>
|
||||
</ButtonsGrid>
|
||||
);
|
||||
|
|
|
@ -48,12 +48,14 @@ export function EnvironmentBrowseButtons({
|
|||
? 'Browse snapshot is only available for async environments'
|
||||
: ''
|
||||
}
|
||||
data-cy={`browse-snapshot-link-${environment.Name}`}
|
||||
>
|
||||
Browse snapshot
|
||||
</LinkButton>
|
||||
) : (
|
||||
<Button
|
||||
icon={X}
|
||||
data-cy={`close-snapshot-link-${environment.Name}`}
|
||||
onClick={onClickDisconnect}
|
||||
className="!m-0 w-full !py-0 opacity-60"
|
||||
size="medium"
|
||||
|
@ -78,11 +80,13 @@ export function EnvironmentBrowseButtons({
|
|||
onClick={onClickBrowse}
|
||||
color="primary"
|
||||
className="!m-0 w-full !py-0"
|
||||
data-cy={`live-connect-link-${environment.Name}`}
|
||||
>
|
||||
Live connect
|
||||
</LinkButton>
|
||||
) : (
|
||||
<Button
|
||||
data-cy={`disconnect-link-${environment.Name}`}
|
||||
icon={WifiOff}
|
||||
onClick={onClickDisconnect}
|
||||
className="!m-0 w-full !py-0 opacity-60"
|
||||
|
|
|
@ -23,7 +23,12 @@ function Option<TValue = number>(props: OptionProps<OptionType<TValue>, true>) {
|
|||
{...props}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="checkbox" checked={isSelected} onChange={() => null} />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => null}
|
||||
data-cy={`homepage-filter-option-${label}`}
|
||||
/>
|
||||
<label className="whitespace-nowrap">{label}</label>
|
||||
</div>
|
||||
</components.Option>
|
||||
|
@ -46,6 +51,7 @@ export function HomepageFilter<TValue = number>({
|
|||
components={{ Option }}
|
||||
onChange={(option) => onChange([...option])}
|
||||
bindToBody
|
||||
data-cy="homepage-filter"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ export function KubeconfigButton({ environments, envQueryParams }: Props) {
|
|||
<>
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
data-cy="download-kubeconfig-button"
|
||||
size="medium"
|
||||
className="!m-0"
|
||||
icon={Download}
|
||||
|
|
|
@ -76,6 +76,7 @@ export function KubeconfigPrompt({
|
|||
<div className="mt-2 flex h-8 items-center">
|
||||
<Checkbox
|
||||
id="settings-container-truncate-name"
|
||||
data-cy="select-all-checkbox"
|
||||
label="Select all (in this page)"
|
||||
checked={isAllPageSelected}
|
||||
onChange={handleSelectAll}
|
||||
|
@ -95,6 +96,7 @@ export function KubeconfigPrompt({
|
|||
>
|
||||
<Checkbox
|
||||
id={`${env.Id}`}
|
||||
data-cy={`select-environment-checkbox-${env.Name}`}
|
||||
label={`${env.Name} (${env.URL})`}
|
||||
checked={selection.includes(env.Id)}
|
||||
onChange={() =>
|
||||
|
@ -118,10 +120,18 @@ export function KubeconfigPrompt({
|
|||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button onClick={onClose} color="default">
|
||||
<Button
|
||||
onClick={onClose}
|
||||
color="default"
|
||||
data-cy="cancel-kubeconfig-download-button"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleDownload} disabled={selection.length === 0}>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
disabled={selection.length === 0}
|
||||
data-cy="download-kubeconfig-confirbutton"
|
||||
>
|
||||
Download File
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
|
|
|
@ -9,8 +9,13 @@ export function NoEnvironmentsInfoPanel({ isAdmin }: { isAdmin: boolean }) {
|
|||
{isAdmin ? (
|
||||
<span>
|
||||
No environment available for management. Please head over the{' '}
|
||||
<Link to="portainer.wizard.endpoints">environment wizard</Link> to
|
||||
add an environment.
|
||||
<Link
|
||||
to="portainer.wizard.endpoints"
|
||||
data-cy="wizard-add-environments-link"
|
||||
>
|
||||
environment wizard
|
||||
</Link>{' '}
|
||||
to add an environment.
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
|
|
|
@ -45,6 +45,7 @@ export function SortbySelector({
|
|||
onChange={(option: ListSortType) => onChange(option)}
|
||||
isClearable
|
||||
value={value}
|
||||
data-cy="home-view-sortby-selector"
|
||||
/>
|
||||
|
||||
<button
|
||||
|
|
|
@ -28,6 +28,7 @@ export function UpdateBadge() {
|
|||
'th-dark:bg-blue-3 th-dark:text-blue-8 th-dark:hover:bg-blue-5 th-dark:hover:text-blue-8',
|
||||
'th-highcontrast:border-white th-highcontrast:bg-transparent th-highcontrast:text-white th-highcontrast:hover:bg-gray-warm-7 th-highcontrast:hover:text-white'
|
||||
)}
|
||||
data-cy="home-schedule-update-link"
|
||||
>
|
||||
Schedule Update
|
||||
</Link>
|
||||
|
|
|
@ -70,7 +70,11 @@ export function AccessControlPanel({
|
|||
{!isEditDisabled && !isEditMode && (
|
||||
<div className="row">
|
||||
<div>
|
||||
<Button color="link" onClick={toggleEditMode}>
|
||||
<Button
|
||||
color="link"
|
||||
onClick={toggleEditMode}
|
||||
data-cy="change-ownership-button"
|
||||
>
|
||||
<Icon icon={Edit} className="space-right" />
|
||||
Change ownership
|
||||
</Button>
|
||||
|
|
|
@ -134,7 +134,11 @@ function getInheritanceMessage(
|
|||
return (
|
||||
<InheritanceMessage tooltip="Access control applied on a service is also applied on each container of that service.">
|
||||
Access control on this resource is inherited from the following service:
|
||||
<Link to="docker.services.service" params={{ id: resourceId }}>
|
||||
<Link
|
||||
to="docker.services.service"
|
||||
params={{ id: resourceId }}
|
||||
data-cy="docker-access-inherited-service"
|
||||
>
|
||||
{truncate(resourceId)}
|
||||
</Link>
|
||||
</InheritanceMessage>
|
||||
|
@ -149,7 +153,11 @@ function getInheritanceMessage(
|
|||
<InheritanceMessage tooltip="Access control applied on a container created using a template is also applied on each volume associated to the container.">
|
||||
Access control on this resource is inherited from the following
|
||||
container:
|
||||
<Link to="docker.containers.container" params={{ id: resourceId }}>
|
||||
<Link
|
||||
to="docker.containers.container"
|
||||
params={{ id: resourceId }}
|
||||
data-cy="docker-access-inherited-container"
|
||||
>
|
||||
{truncate(resourceId)}
|
||||
</Link>
|
||||
</InheritanceMessage>
|
||||
|
|
|
@ -102,11 +102,17 @@ export function AccessControlPanelForm({
|
|||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<Button size="small" color="default" onClick={onCancelClick}>
|
||||
<Button
|
||||
size="small"
|
||||
color="default"
|
||||
onClick={onCancelClick}
|
||||
data-cy="cancel-access-control-update-button"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<LoadingButton
|
||||
size="small"
|
||||
data-cy="update-access-control-button"
|
||||
color="primary"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
|
|
|
@ -40,11 +40,15 @@ export function TeamsField({
|
|||
onChange={onChange}
|
||||
value={value}
|
||||
inputId="teams-selector"
|
||||
dataCy="teams-selector"
|
||||
/>
|
||||
) : (
|
||||
<span className="small text-muted">
|
||||
You have not yet created any teams. Head over to the{' '}
|
||||
<Link to="portainer.teams">Teams view</Link> to manage teams.
|
||||
<Link to="portainer.teams" data-cy="teams-view-link">
|
||||
Teams view
|
||||
</Link>{' '}
|
||||
to manage teams.
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
|
|
@ -31,11 +31,15 @@ export function UsersField({ name, users, value, onChange, errors }: Props) {
|
|||
onChange={onChange}
|
||||
value={value}
|
||||
inputId="users-selector"
|
||||
dataCy="users-selector"
|
||||
/>
|
||||
) : (
|
||||
<span className="small text-muted">
|
||||
You have not yet created any users. Head over to the{' '}
|
||||
<Link to="portainer.users">Users view</Link> to manage users.
|
||||
<Link to="portainer.users" data-cy="access-control-users-link">
|
||||
Users view
|
||||
</Link>{' '}
|
||||
to manage users.
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
|
|
@ -27,6 +27,7 @@ export function AccessTokensDatatable({ canExit }: { canExit?: boolean }) {
|
|||
renderTableActions={(selectedItems) => (
|
||||
<TableActions selectedItems={selectedItems} canExit={canExit} />
|
||||
)}
|
||||
data-cy="access-tokens-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,9 +22,14 @@ export function TableActions({
|
|||
disabled={selectedItems.length === 0}
|
||||
confirmMessage="Do you want to remove the selected access token(s)? Any script or application using these tokens will no longer be able to invoke the Portainer API."
|
||||
onConfirmed={handleRemove}
|
||||
data-cy="access-tokens-delete-button"
|
||||
/>
|
||||
|
||||
<AddButton to=".new-access-token" disabled={!canExit}>
|
||||
<AddButton
|
||||
to=".new-access-token"
|
||||
disabled={!canExit}
|
||||
data-cy="access-tokens-add-button"
|
||||
>
|
||||
Add access token
|
||||
</AddButton>
|
||||
</>
|
||||
|
|
|
@ -40,6 +40,7 @@ export function ApplicationSettingsForm() {
|
|||
</TextTip>
|
||||
<SwitchField
|
||||
label="Enable front-end data caching for Kubernetes environments"
|
||||
data-cy="account-applicationSettingsUseCacheSwitch"
|
||||
checked={values.useCache}
|
||||
onChange={(value) => setFieldValue('useCache', value)}
|
||||
labelClass="col-lg-2 col-sm-3" // match the label width of the other fields in the page
|
||||
|
|
|
@ -73,6 +73,7 @@ export function HelmRepositoryDatatable() {
|
|||
emptyContentLabel="No Helm repository found"
|
||||
isLoading={helmReposQuery.isLoading}
|
||||
isRowSelectable={(row) => !row.original.Global}
|
||||
data-cy="helm-repositories-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -84,7 +85,11 @@ function HelmDatatableDescription({ isAdmin }: { isAdmin: boolean }) {
|
|||
account's Portainer UI. Helm charts are pulled down from these repos
|
||||
(plus the{' '}
|
||||
{isAdmin ? (
|
||||
<Link to="portainer.settings" params={{ '#': 'kubernetes-settings' }}>
|
||||
<Link
|
||||
to="portainer.settings"
|
||||
params={{ '#': 'kubernetes-settings' }}
|
||||
data-cy="k8s-globally-select-repo-link"
|
||||
>
|
||||
<span>globally-set Helm repo</span>
|
||||
</Link>
|
||||
) : (
|
||||
|
|
|
@ -52,6 +52,7 @@ export function CreateUserAccessTokenInnerForm({ showAuthentication }: Props) {
|
|||
</FormControl>
|
||||
<LoadingButton
|
||||
disabled={!isValid || !dirty}
|
||||
data-cy="create-access-token-button"
|
||||
isLoading={false}
|
||||
loadingText="Adding access token..."
|
||||
>
|
||||
|
|
|
@ -17,13 +17,18 @@ export function DisplayUserAccessToken({ apikey }: { apikey: string }) {
|
|||
<div className="inline-flex">
|
||||
<div className="">{apikey}</div>
|
||||
<div>
|
||||
<CopyButton copyText={apikey} color="link" />
|
||||
<CopyButton
|
||||
copyText={apikey}
|
||||
color="link"
|
||||
data-cy="create-access-token-copy-button"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
data-cy="create-access-token-done-button"
|
||||
onClick={() => router.stateService.go('portainer.account')}
|
||||
>
|
||||
Done
|
||||
|
|
|
@ -57,6 +57,7 @@ export function HelmRepositoryForm({
|
|||
<div className="col-sm-12 mt-3">
|
||||
<LoadingButton
|
||||
disabled={!isValid || !dirty}
|
||||
data-cy="helm-repository-save-button"
|
||||
isLoading={isLoading}
|
||||
loadingText="Saving Helm repository..."
|
||||
>
|
||||
|
@ -65,6 +66,7 @@ export function HelmRepositoryForm({
|
|||
{isEditing && (
|
||||
<Button
|
||||
color="default"
|
||||
data-cy="helm-repository-cancel-button"
|
||||
onClick={() => router.stateService.go('portainer.account')}
|
||||
>
|
||||
Cancel
|
||||
|
|
|
@ -32,6 +32,7 @@ export function CommonFields({
|
|||
>
|
||||
<Input
|
||||
name="title"
|
||||
data-cy="custom-templates-title-input"
|
||||
placeholder="e.g. mytemplate"
|
||||
id="template-title"
|
||||
required
|
||||
|
@ -50,6 +51,7 @@ export function CommonFields({
|
|||
>
|
||||
<Input
|
||||
name="description"
|
||||
data-cy="custom-templates-description-input"
|
||||
id="template-description"
|
||||
required
|
||||
value={values.Description}
|
||||
|
@ -62,6 +64,7 @@ export function CommonFields({
|
|||
<FormControl label="Note" inputId="template-note" errors={errors?.Note}>
|
||||
<Input
|
||||
name="note"
|
||||
data-cy="custom-templates-note-input"
|
||||
id="template-note"
|
||||
value={values.Note}
|
||||
onChange={(e) => {
|
||||
|
@ -73,6 +76,7 @@ export function CommonFields({
|
|||
<FormControl label="Logo" inputId="template-logo" errors={errors?.Logo}>
|
||||
<Input
|
||||
name="logo"
|
||||
data-cy="custom-templates-logo-input"
|
||||
id="template-logo"
|
||||
value={values.Logo}
|
||||
onChange={(e) => {
|
||||
|
|
|
@ -50,6 +50,7 @@ export function CustomTemplatesVariablesDefinitionField({
|
|||
errors={errors}
|
||||
textTip="List should map the mustache variables in the template file, if default value is empty, the variable will be required."
|
||||
isAddButtonHidden={isVariablesNamesFromParent}
|
||||
data-cy="custom-templates-variables-field"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -58,7 +59,13 @@ interface DefinitionItemProps extends ItemProps<VariableDefinition> {
|
|||
isNameReadonly?: boolean;
|
||||
}
|
||||
|
||||
function Item({ item, onChange, error, isNameReadonly }: DefinitionItemProps) {
|
||||
function Item({
|
||||
item,
|
||||
onChange,
|
||||
error,
|
||||
isNameReadonly,
|
||||
index,
|
||||
}: DefinitionItemProps) {
|
||||
const errorObj = typeof error === 'object' ? error : {};
|
||||
|
||||
return (
|
||||
|
@ -70,6 +77,7 @@ function Item({ item, onChange, error, isNameReadonly }: DefinitionItemProps) {
|
|||
onChange={handleChange}
|
||||
placeholder="Name (e.g var_name)"
|
||||
readOnly={isNameReadonly}
|
||||
data-cy={`custom-templates-item-name-field_${index}`}
|
||||
/>
|
||||
{errorObj?.name && <FormError>{errorObj.name}</FormError>}
|
||||
</div>
|
||||
|
@ -79,6 +87,7 @@ function Item({ item, onChange, error, isNameReadonly }: DefinitionItemProps) {
|
|||
onChange={handleChange}
|
||||
placeholder="Label"
|
||||
name="label"
|
||||
data-cy={`custom-templates-item-label-field_${index}`}
|
||||
/>
|
||||
{errorObj?.label && <FormError>{errorObj.label}</FormError>}
|
||||
</div>
|
||||
|
@ -88,6 +97,7 @@ function Item({ item, onChange, error, isNameReadonly }: DefinitionItemProps) {
|
|||
value={item.description}
|
||||
onChange={handleChange}
|
||||
placeholder="Description"
|
||||
data-cy={`custom-templates-item-description-field_${index}`}
|
||||
/>
|
||||
{errorObj?.description && <FormError>{errorObj.description}</FormError>}
|
||||
</div>
|
||||
|
@ -97,6 +107,7 @@ function Item({ item, onChange, error, isNameReadonly }: DefinitionItemProps) {
|
|||
onChange={handleChange}
|
||||
placeholder="Default Value"
|
||||
name="defaultValue"
|
||||
data-cy={`custom-templates-item-default-value-field_${index}`}
|
||||
/>
|
||||
{errorObj?.defaultValue && (
|
||||
<FormError>{errorObj.defaultValue}</FormError>
|
||||
|
|
|
@ -28,6 +28,7 @@ export function VariableFieldItem({
|
|||
>
|
||||
<Input
|
||||
name={`variables.${definition.name}`}
|
||||
data-cy={`custom-template-variables-${definition.name}`}
|
||||
id={inputId}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
|
|
|
@ -19,6 +19,7 @@ export function PlatformField({
|
|||
<FormControl label="Platform" required inputId="template-platform">
|
||||
<Select
|
||||
name="platform"
|
||||
data-cy="custom-tempalte-platform-select"
|
||||
id="template-platform"
|
||||
required
|
||||
options={platformOptions}
|
||||
|
|
|
@ -19,6 +19,7 @@ export function TemplateTypeSelector({
|
|||
<FormControl label="Type" required inputId="template-type">
|
||||
<Select
|
||||
name="type"
|
||||
data-cy="custom-template-template-type"
|
||||
id="template-type"
|
||||
required
|
||||
options={typeOptions}
|
||||
|
|
|
@ -75,8 +75,13 @@ export function AutomaticEdgeEnvCreation() {
|
|||
{!edgeComputeConfigurationOK ? (
|
||||
<TextTip color="orange">
|
||||
In order to use this feature, please turn on Edge Compute features{' '}
|
||||
<Link to="portainer.settings.edgeCompute">here</Link> and have
|
||||
Portainer API server URL and tunnel server address properly
|
||||
<Link
|
||||
to="portainer.settings.edgeCompute"
|
||||
data-cy="edge-disabled-portainer-edge-settings-link"
|
||||
>
|
||||
here
|
||||
</Link>{' '}
|
||||
and have Portainer API server URL and tunnel server address properly
|
||||
configured.
|
||||
</TextTip>
|
||||
) : (
|
||||
|
@ -147,7 +152,12 @@ function EdgeKeyInfo({
|
|||
<code>{edgeKey}</code>
|
||||
</div>
|
||||
|
||||
<CopyButton copyText={edgeKey}>Copy token</CopyButton>
|
||||
<CopyButton
|
||||
copyText={edgeKey}
|
||||
data-cy="edge-auto-create-copy-token-button"
|
||||
>
|
||||
Copy token
|
||||
</CopyButton>
|
||||
</FormSection>
|
||||
|
||||
<hr />
|
||||
|
@ -159,19 +169,28 @@ function EdgeKeyInfo({
|
|||
showMetaFields
|
||||
>
|
||||
<FormControl label="Portainer API server URL">
|
||||
<Input value={url} readOnly />
|
||||
<Input value={url} readOnly data-cy="edge-auto-create-url-input" />
|
||||
</FormControl>
|
||||
|
||||
{!asyncMode && (
|
||||
<FormControl label="Portainer tunnel server address">
|
||||
<Input value={tunnelUrl} readOnly />
|
||||
<Input
|
||||
value={tunnelUrl}
|
||||
readOnly
|
||||
data-cy="edge-auto-create-tunnel-address-input"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
<TextTip color="blue">
|
||||
Portainer Server URL{' '}
|
||||
{!asyncMode ? 'and tunnel server address are' : 'is'} set{' '}
|
||||
<Link to="portainer.settings.edgeCompute">here</Link>
|
||||
<Link
|
||||
to="portainer.settings.edgeCompute"
|
||||
data-cy="server-url-portainer-edge-settings-link"
|
||||
>
|
||||
here
|
||||
</Link>
|
||||
</TextTip>
|
||||
</EdgeScriptForm>
|
||||
</>
|
||||
|
|
|
@ -18,7 +18,11 @@ export function EdgeKeyDisplay({ edgeKey }: { edgeKey: string }) {
|
|||
|
||||
<Code>{edgeKey}</Code>
|
||||
|
||||
<CopyButton copyText={edgeKey} className="mt-2">
|
||||
<CopyButton
|
||||
copyText={edgeKey}
|
||||
className="mt-2"
|
||||
data-cy="copy-edge-key-button"
|
||||
>
|
||||
Copy token
|
||||
</CopyButton>
|
||||
</FormSection>
|
||||
|
|
|
@ -78,6 +78,7 @@ export function EnvironmentsDatatable({
|
|||
onClick={() => onRemove(selectedRows)}
|
||||
icon={Trash2}
|
||||
className="!m-0"
|
||||
data-cy="remove-environments-button"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
|
@ -87,6 +88,7 @@ export function EnvironmentsDatatable({
|
|||
{isBE && (
|
||||
<AddButton
|
||||
color="secondary"
|
||||
data-cy="environments-auto-onboarding-button"
|
||||
to="portainer.endpoints.edgeAutoCreateScript"
|
||||
>
|
||||
Auto onboarding
|
||||
|
@ -96,11 +98,13 @@ export function EnvironmentsDatatable({
|
|||
<AddButton
|
||||
to="portainer.wizard.endpoints"
|
||||
params={{ referrer: 'environments' }}
|
||||
data-cy="environments-add-environments-button"
|
||||
>
|
||||
Add environment
|
||||
</AddButton>
|
||||
</div>
|
||||
)}
|
||||
data-cy="environments-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,11 @@ export function ImportFdoDeviceButton() {
|
|||
|
||||
return (
|
||||
<div className="ml-[5px]">
|
||||
<AddButton color="secondary" to="portainer.endpoints.importDevice">
|
||||
<AddButton
|
||||
color="secondary"
|
||||
to="portainer.endpoints.importDevice"
|
||||
data-cy="import-fdo-device-button"
|
||||
>
|
||||
Import FDO device
|
||||
</AddButton>
|
||||
</div>
|
||||
|
|
|
@ -31,9 +31,11 @@ function Cell({
|
|||
props={{
|
||||
to: 'portainer.endpoints.endpoint.access',
|
||||
params: { id: environment.Id },
|
||||
'data-cy': `environment-manage-access-${environment.Name}`,
|
||||
}}
|
||||
color="link"
|
||||
icon={Users}
|
||||
data-cy={`environment-manage-access-button-${environment.Name}`}
|
||||
>
|
||||
Manage access
|
||||
</Button>
|
||||
|
|
|
@ -13,7 +13,11 @@ export const name = columnHelper.accessor('Name', {
|
|||
}
|
||||
|
||||
return (
|
||||
<Link to="portainer.endpoints.endpoint" params={{ id: environment.Id }}>
|
||||
<Link
|
||||
to="portainer.endpoints.endpoint"
|
||||
params={{ id: environment.Id }}
|
||||
data-cy={`environment-link-${environment.Id}`}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -50,6 +50,7 @@ function Cell({
|
|||
<div className="mt-2 text-right">
|
||||
<Button
|
||||
color="link"
|
||||
data-cy={`dismiss-error-${environment.Id}`}
|
||||
className="small !ml-0 p-0"
|
||||
onClick={handleDismissButton}
|
||||
>
|
||||
|
|
|
@ -43,8 +43,10 @@ export function TagsDatatable({
|
|||
disabled={selectedItems.length === 0}
|
||||
confirmMessage="Are you sure you want to remove the selected tag(s)?"
|
||||
onConfirmed={() => onRemove(selectedItems)}
|
||||
data-cy="remove-tag-button"
|
||||
/>
|
||||
)}
|
||||
data-cy="tags-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ export function TimePickerInput({
|
|||
<div className="flex flex-col">
|
||||
<Button
|
||||
color="link"
|
||||
data-cy="env-time-picker-hours-up-button"
|
||||
size="medium"
|
||||
className="!ml-0 w-full"
|
||||
icon={ChevronUp}
|
||||
|
@ -42,12 +43,14 @@ export function TimePickerInput({
|
|||
/>
|
||||
<Input
|
||||
type="text"
|
||||
data-cy="time-picker-hours-input"
|
||||
value={hours}
|
||||
className="w-12 !cursor-default text-center"
|
||||
disabled
|
||||
/>
|
||||
<Button
|
||||
color="link"
|
||||
data-cy="env-time-picker-hours-down-button"
|
||||
size="medium"
|
||||
className="!ml-0 w-full"
|
||||
icon={ChevronDown}
|
||||
|
@ -63,6 +66,7 @@ export function TimePickerInput({
|
|||
<div className="flex flex-col">
|
||||
<Button
|
||||
color="link"
|
||||
data-cy="env-time-picker-minutes-up-button"
|
||||
size="medium"
|
||||
className="!ml-0 w-full"
|
||||
icon={ChevronUp}
|
||||
|
@ -75,12 +79,14 @@ export function TimePickerInput({
|
|||
/>
|
||||
<Input
|
||||
type="text"
|
||||
data-cy="time-picker-minutes-input"
|
||||
value={minutes}
|
||||
className="w-12 !cursor-default text-center"
|
||||
disabled
|
||||
/>
|
||||
<Button
|
||||
color="link"
|
||||
data-cy="env-time-picker-minutes-down-button"
|
||||
size="medium"
|
||||
className="!ml-0 w-full"
|
||||
icon={ChevronDown}
|
||||
|
@ -94,6 +100,7 @@ export function TimePickerInput({
|
|||
</div>
|
||||
<Button
|
||||
color="default"
|
||||
data-cy="env-time-picker-ampm-button"
|
||||
className="h-[34px]"
|
||||
onClick={() => {
|
||||
const newTime = moment(localTime24h, valueFormat)
|
||||
|
|
|
@ -72,6 +72,7 @@ export function TimeWindowPicker({
|
|||
{!isEditMode && (
|
||||
<Button
|
||||
color="default"
|
||||
data-cy="edit-change-window-button"
|
||||
className="!ml-0"
|
||||
onClick={() => setIsEditMode(true)}
|
||||
>
|
||||
|
@ -81,6 +82,7 @@ export function TimeWindowPicker({
|
|||
{isEditMode && (
|
||||
<Button
|
||||
color="default"
|
||||
data-cy="cancel-change-window-button"
|
||||
className="!ml-0"
|
||||
onClick={() => {
|
||||
setIsEditMode(false);
|
||||
|
|
|
@ -90,7 +90,7 @@ export function TimeWindowPickerInputGroup({
|
|||
<Select<Option<string>>
|
||||
options={timeZoneOptions}
|
||||
value={timeZoneOptions[timeZoneOptionIndex]}
|
||||
className="basis-[fit-content] flex-1 min-w-fit max-w-xs"
|
||||
className="min-w-fit max-w-xs flex-1 basis-[fit-content]"
|
||||
onChange={(newTimeZone) => {
|
||||
if (!newTimeZone) return;
|
||||
// update the utc time so that the local time displayed remains the same
|
||||
|
@ -111,6 +111,7 @@ export function TimeWindowPickerInputGroup({
|
|||
});
|
||||
onChangeTimeZone(newTimeZone.value);
|
||||
}}
|
||||
data-cy="time-window-picker-timezone-select"
|
||||
/>
|
||||
</div>
|
||||
{errors?.StartTime && <FormError>{errors.StartTime}</FormError>}
|
||||
|
|
|
@ -27,6 +27,7 @@ export function EnvironmentGroupsDatatable() {
|
|||
renderTableActions={(selectedItems) => (
|
||||
<TableActions selectedItems={selectedItems} />
|
||||
)}
|
||||
data-cy="environment-groups-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,9 +20,10 @@ export function TableActions({
|
|||
disabled={selectedItems.length === 0}
|
||||
confirmMessage="Are you sure you want to remove the selected environment group(s)?"
|
||||
onConfirmed={handleRemove}
|
||||
data-cy="remove-environment-groups-button"
|
||||
/>
|
||||
|
||||
<AddButton>Add group</AddButton>
|
||||
<AddButton data-cy="add-environment-group-button">Add group</AddButton>
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EnvironmentGroup } from '../../types';
|
|||
const columnHelper = createColumnHelper<EnvironmentGroup>();
|
||||
|
||||
export const columns = [
|
||||
buildNameColumn<EnvironmentGroup>('Name', '.group'),
|
||||
buildNameColumn<EnvironmentGroup>('Name', '.group', 'environment-group-name'),
|
||||
columnHelper.display({
|
||||
header: 'Actions',
|
||||
cell: ActionsCell,
|
||||
|
@ -26,9 +26,11 @@ function ActionsCell({
|
|||
props={{
|
||||
to: '.group.access',
|
||||
params: { id: item.Id },
|
||||
'data-cy': `manage-access-link_${item.Name}`,
|
||||
}}
|
||||
color="link"
|
||||
icon={Users}
|
||||
data-cy={`manage-access-button_${item.Name}`}
|
||||
>
|
||||
Manage access
|
||||
</Button>
|
||||
|
|
|
@ -53,6 +53,7 @@ export function AssociatedEnvironmentsSelector({
|
|||
);
|
||||
}
|
||||
}}
|
||||
data-cy="edgeGroupCreate-associatedEndpoints"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -69,8 +69,13 @@ function CreateView() {
|
|||
<Widget.Body>
|
||||
<TextTip color="blue" className="mb-2">
|
||||
Devices need to be allocated to an Edge group, visit the{' '}
|
||||
<Link to="edge.groups">Edge Groups</Link> page to assign
|
||||
environments and create groups.
|
||||
<Link
|
||||
to="edge.groups"
|
||||
data-cy="update-schedules-create-edge-groups-link"
|
||||
>
|
||||
Edge Groups
|
||||
</Link>{' '}
|
||||
page to assign environments and create groups.
|
||||
</TextTip>
|
||||
|
||||
<Formik
|
||||
|
@ -106,6 +111,7 @@ function CreateView() {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
disabled={!isValid}
|
||||
data-cy="update-schedules-create-submit-button"
|
||||
isLoading={createMutation.isLoading}
|
||||
loadingText="Creating..."
|
||||
>
|
||||
|
|
|
@ -93,8 +93,13 @@ function ItemView() {
|
|||
<Widget.Body>
|
||||
<TextTip color="blue">
|
||||
Devices need to be allocated to an Edge group, visit the{' '}
|
||||
<Link to="edge.groups">Edge Groups</Link> page to assign
|
||||
environments and create groups.
|
||||
<Link
|
||||
to="edge.groups"
|
||||
data-cy="update-schedules-edge-groups-link"
|
||||
>
|
||||
Edge Groups
|
||||
</Link>{' '}
|
||||
page to assign environments and create groups.
|
||||
</TextTip>
|
||||
|
||||
<Formik
|
||||
|
@ -159,6 +164,7 @@ function ItemView() {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
disabled={!isValid}
|
||||
data-cy="update-schedule-button"
|
||||
isLoading={updateMutation.isLoading}
|
||||
loadingText="Updating..."
|
||||
>
|
||||
|
|
|
@ -76,6 +76,7 @@ export function ListView() {
|
|||
<TableActions selectedRows={selectedRows} />
|
||||
)}
|
||||
isRowSelectable={(row) => row.original.status === StatusType.Pending}
|
||||
data-cy="environment-update-schedules-datatable"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -92,9 +93,12 @@ function TableActions({
|
|||
<DeleteButton
|
||||
onConfirmed={() => handleRemove()}
|
||||
disabled={selectedRows.length === 0}
|
||||
data-cy="remove-update-schedules-button"
|
||||
confirmMessage="Are you sure you want to remove these schedules?"
|
||||
/>
|
||||
<AddButton to=".create">Add update & rollback schedule</AddButton>
|
||||
<AddButton to=".create" data-cy="add-update-schedules-button">
|
||||
Add update & rollback schedule
|
||||
</AddButton>
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { scheduledTime } from './scheduled-time';
|
|||
import { scheduleType } from './type';
|
||||
|
||||
export const columns = [
|
||||
buildNameColumn<DecoratedItem>('name', '.item'),
|
||||
buildNameColumn<DecoratedItem>('name', '.item', 'update-schedules-name'),
|
||||
scheduledTime,
|
||||
groups,
|
||||
scheduleType,
|
||||
|
|
|
@ -53,6 +53,7 @@ export function EdgeGroupsField({
|
|||
getOptionValue={(group) => group.Id.toString()}
|
||||
closeMenuOnSelect={false}
|
||||
isDisabled={disabled}
|
||||
data-cy="update-schedules-edge-groups-select"
|
||||
/>
|
||||
</FormControl>
|
||||
<TextTip color="blue">
|
||||
|
|
|
@ -55,7 +55,11 @@ export function ScheduledTimeField({ disabled }: Props) {
|
|||
minDate={new Date(Date.now() - 24 * 60 * 60 * 1000)}
|
||||
/>
|
||||
) : (
|
||||
<Input defaultValue={value} disabled />
|
||||
<Input
|
||||
defaultValue={value}
|
||||
disabled
|
||||
data-cy="update-schedules-time-input"
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
{!disabled && value && (
|
||||
|
|
|
@ -63,6 +63,7 @@ export function EnvironmentTypeSelectView() {
|
|||
</div>
|
||||
<Button
|
||||
disabled={types.length === 0}
|
||||
data-cy="start-wizard-button"
|
||||
onClick={() => startWizard()}
|
||||
>
|
||||
Start Wizard
|
||||
|
|
|
@ -93,10 +93,17 @@ export function EnvironmentCreationView() {
|
|||
'flex justify-between'
|
||||
)}
|
||||
>
|
||||
<Button disabled={isFirstStep} onClick={onPreviousClick}>
|
||||
<Button
|
||||
disabled={isFirstStep}
|
||||
onClick={onPreviousClick}
|
||||
data-cy="environment-wizard-previous-button"
|
||||
>
|
||||
<Icon icon={ArrowLeft} /> Previous
|
||||
</Button>
|
||||
<Button onClick={onNextClick}>
|
||||
<Button
|
||||
onClick={onNextClick}
|
||||
data-cy="environment-wizard-next-button"
|
||||
>
|
||||
{isLastStep ? 'Close' : 'Next'}
|
||||
<Icon icon={ArrowRight} />
|
||||
</Button>
|
||||
|
|
|
@ -127,6 +127,7 @@ export function WizardAzure({ onCreate }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
className="vertical-center"
|
||||
data-cy="create-azure-environment-button"
|
||||
loadingText="Connecting environment..."
|
||||
isLoading={mutation.isLoading}
|
||||
disabled={!dirty || !isValid}
|
||||
|
|
|
@ -87,6 +87,7 @@ export function APIForm({ onCreate }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
className="wizard-connect-button vertical-center"
|
||||
data-cy="docker-aconnect-button"
|
||||
loadingText="Connecting environment..."
|
||||
isLoading={mutation.isLoading}
|
||||
disabled={!dirty || !isValid}
|
||||
|
|
|
@ -52,7 +52,9 @@ function DeployCode({ code }: DeployCodeProps) {
|
|||
|
||||
<Code>{code}</Code>
|
||||
<div className="mt-2">
|
||||
<CopyButton copyText={code}>Copy command</CopyButton>
|
||||
<CopyButton copyText={code} data-cy="copy-deployment-command">
|
||||
Copy command
|
||||
</CopyButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -83,7 +83,9 @@ function DeployCode({ code }: DeployCodeProps) {
|
|||
<Code>{code}</Code>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<CopyButton copyText={code}>Copy command</CopyButton>
|
||||
<CopyButton copyText={code} data-cy="copy-deployment-script">
|
||||
Copy command
|
||||
</CopyButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -53,6 +53,7 @@ export function SocketForm({ onCreate }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
className="wizard-connect-button vertical-center"
|
||||
data-cy="docker-socket-connect-button"
|
||||
loadingText="Connecting environment..."
|
||||
isLoading={mutation.isLoading}
|
||||
disabled={!dirty || !isValid}
|
||||
|
@ -94,6 +95,7 @@ function OverrideSocketFieldset() {
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
checked={values.overridePath}
|
||||
data-cy="create-docker-env-socket-override-switch"
|
||||
onChange={(checked) => setFieldValue('overridePath', checked)}
|
||||
label="Override default socket path"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
|
|
|
@ -110,7 +110,9 @@ function DeployCode({
|
|||
)}
|
||||
<Code>{code}</Code>
|
||||
<div className="mt-2">
|
||||
<CopyButton copyText={code}>Copy command</CopyButton>
|
||||
<CopyButton copyText={code} data-cy="copy-deploy-agent-command-button">
|
||||
Copy command
|
||||
</CopyButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -78,13 +78,16 @@ export function KubeConfigTeaserForm() {
|
|||
required
|
||||
inputId="kubeconfig_file"
|
||||
>
|
||||
<Button disabled>Select a file</Button>
|
||||
<Button disabled data-cy="kubeconfig-file-upload">
|
||||
Select a file
|
||||
</Button>
|
||||
</FormControl>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
className="wizard-connect-button !ml-0"
|
||||
data-cy="kubeconfig-connect-environment-button"
|
||||
loadingText="Connecting environment..."
|
||||
isLoading={false}
|
||||
disabled
|
||||
|
|
|
@ -53,6 +53,7 @@ export function AgentForm({ onCreate }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
className="wizard-connect-button vertical-center"
|
||||
data-cy="agent-connect-environment-button"
|
||||
loadingText="Connecting environment..."
|
||||
isLoading={mutation.isLoading}
|
||||
disabled={!dirty || !isValid}
|
||||
|
|
|
@ -76,6 +76,7 @@ export function EdgeAgentForm({ onCreate, readonly, asyncMode }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
className="vertical-center"
|
||||
data-cy="edge-agent-form-submit-button"
|
||||
isLoading={createMutation.isLoading}
|
||||
loadingText="Creating environment..."
|
||||
disabled={!isValid}
|
||||
|
|
|
@ -50,7 +50,12 @@ export function EdgeAgentTab({ onCreate, commands, asyncMode = false }: Props) {
|
|||
|
||||
<div className="row">
|
||||
<div className="flex justify-end">
|
||||
<Button color="primary" type="reset" onClick={handleReset}>
|
||||
<Button
|
||||
color="primary"
|
||||
type="reset"
|
||||
onClick={handleReset}
|
||||
data-cy="edge-agent-tab-add-environment-button"
|
||||
>
|
||||
Add another environment
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,7 @@ export function GroupField({ name = 'meta.groupId' }: { name?: string }) {
|
|||
<FormControl label="Group" errors={metaProps.error}>
|
||||
<Select
|
||||
name={name}
|
||||
data-cy="environment-group-select"
|
||||
options={options}
|
||||
value={fieldProps.value}
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
|
|
|
@ -35,10 +35,10 @@ export function NameField({
|
|||
>
|
||||
<Input
|
||||
id={id}
|
||||
data-cy="environmentCreate-nameInput"
|
||||
name="name"
|
||||
onChange={(e) => setDebouncedValue(e.target.value)}
|
||||
value={debouncedValue}
|
||||
data-cy="endpointCreate-nameInput"
|
||||
placeholder={placeholder}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
|
|
|
@ -62,7 +62,11 @@ export function HomeView() {
|
|||
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{localEnvironmentAdded.status === 'success' && (
|
||||
<Link to="portainer.home" className={styles.link}>
|
||||
<Link
|
||||
to="portainer.home"
|
||||
className={styles.link}
|
||||
data-cy="wizard-get-started-link"
|
||||
>
|
||||
<Option
|
||||
icon={
|
||||
localEnvironmentAdded.type === EnvironmentType.Docker
|
||||
|
@ -75,7 +79,11 @@ export function HomeView() {
|
|||
/>
|
||||
</Link>
|
||||
)}
|
||||
<Link to="portainer.wizard.endpoints" className={styles.link}>
|
||||
<Link
|
||||
to="portainer.wizard.endpoints"
|
||||
className={styles.link}
|
||||
data-cy="wizard-add-environments-link"
|
||||
>
|
||||
<Option
|
||||
title="Add Environments"
|
||||
icon={Plug2}
|
||||
|
|
|
@ -22,6 +22,7 @@ export function AdditionalFileField({ onChange, value, errors }: Props) {
|
|||
addLabel="Add file"
|
||||
item={Item}
|
||||
itemBuilder={() => ''}
|
||||
data-cy="gitops-additional-files"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -32,6 +33,7 @@ function Item({
|
|||
disabled,
|
||||
error,
|
||||
readOnly,
|
||||
index,
|
||||
}: ItemProps<string>) {
|
||||
const [inputValue, updateInputValue] = useStateWrapper(item, onChange);
|
||||
|
||||
|
@ -47,6 +49,7 @@ function Item({
|
|||
onChange={(e) => {
|
||||
updateInputValue(e.target.value);
|
||||
}}
|
||||
data-cy={`gitops-additional-files_${index}`}
|
||||
/>
|
||||
</InputGroup>
|
||||
{error && (
|
||||
|
|
|
@ -40,6 +40,7 @@ export function CredentialSelector({
|
|||
isClearable
|
||||
noOptionsMessage={() => 'no saved credentials'}
|
||||
inputId="git-creds-selector"
|
||||
data-cy="git-credentials-selector"
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,7 @@ export function NewCredentialForm({
|
|||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="repository-save-credential"
|
||||
data-cy="gitops-save-credential-checkbox"
|
||||
label="save credential"
|
||||
checked={value.SaveCredential || false}
|
||||
className="[&+label]:mb-0"
|
||||
|
@ -30,6 +31,7 @@ export function NewCredentialForm({
|
|||
/>
|
||||
<Input
|
||||
value={value.NewCredentialName || ''}
|
||||
data-cy="gitops-new-credential-name-input"
|
||||
name="new_credential_name"
|
||||
placeholder="credential name"
|
||||
className="ml-4 w-48"
|
||||
|
|
|
@ -32,6 +32,7 @@ export function AutoUpdateFieldset({
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
name="autoUpdate"
|
||||
data-cy="gitops-auto-update-switch"
|
||||
checked={value.RepositoryAutomaticUpdates}
|
||||
label="GitOps updates"
|
||||
tooltip="When enabled, at each polling interval or webhook invocation, if the
|
||||
|
|
|
@ -72,6 +72,7 @@ export function AutoUpdateSettings({
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
name="forcePullImage"
|
||||
data-cy="gitops-force-pull-image-switch"
|
||||
featureId={FeatureId.STACK_PULL_IMAGE}
|
||||
checked={value.ForcePullImage || false}
|
||||
label="Re-pull image"
|
||||
|
|
|
@ -20,6 +20,7 @@ export function ForceDeploymentSwitch({
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
name="forceUpdate"
|
||||
data-cy="gitops-force-redeployment-switch"
|
||||
featureId={FeatureId.FORCE_REDEPLOYMENT}
|
||||
checked={checked}
|
||||
label={label}
|
||||
|
|
|
@ -26,6 +26,7 @@ export function IntervalField({
|
|||
>
|
||||
<Input
|
||||
mRef={ref}
|
||||
data-cy="repository-fetch-interval-input"
|
||||
id="repository_fetch_interval"
|
||||
name="repository_fetch_interval"
|
||||
placeholder="5m"
|
||||
|
|
|
@ -32,7 +32,11 @@ export function WebhookSettings({
|
|||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted">{truncateLeftRight(url)}</span>
|
||||
<CopyButton copyText={url} color="light">
|
||||
<CopyButton
|
||||
copyText={url}
|
||||
color="light"
|
||||
data-cy="copy-webhook-link-button"
|
||||
>
|
||||
Copy link
|
||||
</CopyButton>
|
||||
</div>
|
||||
|
|
|
@ -71,6 +71,7 @@ export function ComposePathField({
|
|||
) : (
|
||||
<Input
|
||||
value={inputValue}
|
||||
data-cy="stack-repository-path-input"
|
||||
onChange={(e) => {
|
||||
updateInputValue(e.target.value);
|
||||
}}
|
||||
|
|
|
@ -50,6 +50,7 @@ export function PathSelector({
|
|||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
inputId={inputId}
|
||||
data-cy="git-ops-path-selector"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -121,6 +121,7 @@ export function GitForm({
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
label="Skip TLS Verification"
|
||||
data-cy="gitops-skip-tls-verification-switch"
|
||||
checked={value.TLSSkipVerify || false}
|
||||
onChange={(value) => handleChange({ TLSSkipVerify: value })}
|
||||
name="TLSSkipVerify"
|
||||
|
|
|
@ -92,6 +92,7 @@ export function GitFormUrlField({
|
|||
|
||||
<Button
|
||||
onClick={onRefresh}
|
||||
data-cy="component-gitUrlRefreshButton"
|
||||
size="medium"
|
||||
className="vertical-center"
|
||||
color="light"
|
||||
|
|
|
@ -72,6 +72,7 @@ export function RefField({
|
|||
>
|
||||
<Input
|
||||
id={inputId}
|
||||
data-cy="repository-reference-input"
|
||||
value={inputValue}
|
||||
onChange={(e) => updateInputValue(e.target.value)}
|
||||
placeholder="refs/heads/main"
|
||||
|
|
|
@ -55,6 +55,7 @@ export function RelativePathFieldset({
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
name="EnableRelativePaths"
|
||||
data-cy="gitops-enable-relative-paths-switch"
|
||||
label="Enable relative path volumes"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
tooltip="Enabling this means you can specify relative path volumes in your Compose files, with Portainer pulling the content from your git repository to the environment the stack is deployed to."
|
||||
|
@ -86,6 +87,7 @@ export function RelativePathFieldset({
|
|||
>
|
||||
<Input
|
||||
name="FilesystemPath"
|
||||
data-cy="relative-path-filesystem-path-input"
|
||||
placeholder="/mnt"
|
||||
disabled={isEditing || !enableFsPath0}
|
||||
value={value.FilesystemPath}
|
||||
|
@ -114,6 +116,7 @@ export function RelativePathFieldset({
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
name="EnablePerDeviceConfigs"
|
||||
data-cy="gitops-enable-per-device-configs-switch"
|
||||
label="GitOps Edge configurations"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
tooltip="By enabling the GitOps Edge Configurations feature, you gain the ability to define relative path volumes in your configuration files. Portainer will then automatically fetch the content from your git repository by matching the folder name or file name with the Portainer Edge ID, and apply it to the environment where the stack is deployed"
|
||||
|
@ -148,6 +151,7 @@ export function RelativePathFieldset({
|
|||
>
|
||||
<Input
|
||||
name="FilesystemPath"
|
||||
data-cy="per-device-configs-filesystem-path-input"
|
||||
placeholder="/mnt"
|
||||
disabled={isEditing || !enableFsPath1}
|
||||
value={value.FilesystemPath}
|
||||
|
@ -210,6 +214,7 @@ export function RelativePathFieldset({
|
|||
<FormControl label="Device matching rule">
|
||||
<Select
|
||||
value={value.PerDeviceConfigsMatchType}
|
||||
data-cy="per-device-configs-match-type-select"
|
||||
onChange={(e) =>
|
||||
innerOnChange({
|
||||
PerDeviceConfigsMatchType: getPerDevConfigsFilterType(
|
||||
|
@ -242,6 +247,7 @@ export function RelativePathFieldset({
|
|||
<FormControl label="Group matching rule">
|
||||
<Select
|
||||
value={value.PerDeviceConfigsGroupMatchType}
|
||||
data-cy="per-device-configs-group-match-type-select"
|
||||
onChange={(e) =>
|
||||
innerOnChange({
|
||||
PerDeviceConfigsGroupMatchType:
|
||||
|
|
|
@ -38,7 +38,12 @@ const columns = [
|
|||
enableSorting: false,
|
||||
cell: ({ row, getValue }) =>
|
||||
getValue() ? (
|
||||
<Button color="link" onClick={() => row.toggleExpanded()} icon={Search}>
|
||||
<Button
|
||||
color="link"
|
||||
onClick={() => row.toggleExpanded()}
|
||||
icon={Search}
|
||||
data-cy={`activity-logs-inspect_${row.index}`}
|
||||
>
|
||||
inspect
|
||||
</Button>
|
||||
) : null,
|
||||
|
@ -95,6 +100,7 @@ export function ActivityLogsTable({
|
|||
totalCount={totalItems}
|
||||
disableSelect
|
||||
renderSubRow={(row) => <SubRow item={row.original} />}
|
||||
data-cy="activity-logs-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ export function FilterBar({
|
|||
icon={DownloadIcon}
|
||||
onClick={onExport}
|
||||
className="!ml-0"
|
||||
data-cy="activity-logs-export-csv-button"
|
||||
>
|
||||
Export as CSV
|
||||
</Button>
|
||||
|
|
|
@ -52,6 +52,7 @@ export function AuthenticationLogsTable({
|
|||
isServerSidePagination
|
||||
totalCount={totalItems}
|
||||
disableSelect
|
||||
data-cy="authentication-logs-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ export function NotificationsView() {
|
|||
)}
|
||||
getRowId={(row) => row.id}
|
||||
highlightedItemId={activeItemId}
|
||||
data-cy="notifications-datatable"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -65,6 +66,7 @@ function TableActions({ selectedRows }: { selectedRows: ToastNotification[] }) {
|
|||
<DeleteButton
|
||||
onConfirmed={() => handleRemove()}
|
||||
disabled={selectedRows.length === 0}
|
||||
data-cy="remove-notifications-button"
|
||||
confirmMessage="Are you sure you want to remove the selected notifications?"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -46,6 +46,7 @@ export function GitlabProjectTable({
|
|||
value.map(({ Id }) => `${Id}`)
|
||||
)}
|
||||
isRowSelectable={({ original: item }) => item.RegistryEnabled}
|
||||
data-cy="gitlab-projects-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export function RegistriesDatatable() {
|
|||
</>
|
||||
)}
|
||||
isRowSelectable={(row) => !!row.original.Id}
|
||||
data-cy="registries-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ export function DefaultRegistryAction() {
|
|||
<div className="vertical-center">
|
||||
<Button
|
||||
color="danger"
|
||||
data-cy="hide-default-registry-button"
|
||||
icon={EyeOff}
|
||||
onClick={() => handleShowOrHide(true)}
|
||||
disabled={isLimited}
|
||||
|
@ -47,7 +48,11 @@ export function DefaultRegistryAction() {
|
|||
</div>
|
||||
) : (
|
||||
<div className="vertical-center">
|
||||
<Button icon={Eye} onClick={() => handleShowOrHide(false)}>
|
||||
<Button
|
||||
data-cy="show-default-registry-button"
|
||||
icon={Eye}
|
||||
onClick={() => handleShowOrHide(false)}
|
||||
>
|
||||
Show for all users
|
||||
</Button>
|
||||
<Tooltip
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Button } from '@@/buttons';
|
|||
import { BEFeatureIndicator } from '@@/BEFeatureIndicator';
|
||||
|
||||
import { DecoratedRegistry } from '../types';
|
||||
import { RegistryId, RegistryTypes } from '../../../types/registry';
|
||||
import { RegistryTypes } from '../../../types/registry';
|
||||
|
||||
import { columnHelper } from './helper';
|
||||
import { DefaultRegistryAction } from './DefaultRegistryAction';
|
||||
|
@ -32,19 +32,17 @@ function Cell({
|
|||
return <DefaultRegistryAction />;
|
||||
}
|
||||
|
||||
return <BrowseButton registryId={item.Id} registryType={item.Type} />;
|
||||
return <BrowseButton registry={item} />;
|
||||
}
|
||||
|
||||
export function BrowseButton({
|
||||
registryId,
|
||||
registryType,
|
||||
registry,
|
||||
environmentId,
|
||||
}: {
|
||||
registryId: RegistryId;
|
||||
registryType: RegistryTypes;
|
||||
registry: DecoratedRegistry;
|
||||
environmentId?: EnvironmentId;
|
||||
}) {
|
||||
const canBrowse = !nonBrowsableTypes.includes(registryType);
|
||||
const canBrowse = !nonBrowsableTypes.includes(registry.Type);
|
||||
|
||||
if (!canBrowse) {
|
||||
return null;
|
||||
|
@ -58,10 +56,12 @@ export function BrowseButton({
|
|||
as={Link}
|
||||
props={{
|
||||
to: 'portainer.registries.registry.repositories',
|
||||
params: { id: registryId, endpointId: environmentId },
|
||||
params: { id: registry.Id, endpointId: environmentId },
|
||||
'data-cy': `browse-registry-link-${registry.Name}`,
|
||||
}}
|
||||
disabled={isLimited}
|
||||
icon={Search}
|
||||
data-cy={`browse-registry-button-${registry.Name}`}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
|
|
|
@ -36,7 +36,11 @@ export function NameCell({
|
|||
return (
|
||||
<>
|
||||
{isEdgeAdminQuery.isAdmin && hasLink ? (
|
||||
<Link to="portainer.registries.registry" params={{ id: item.Id }}>
|
||||
<Link
|
||||
to="portainer.registries.registry"
|
||||
params={{ id: item.Id }}
|
||||
data-cy={`registry-link-${item.Name}`}
|
||||
>
|
||||
{item.Name}
|
||||
</Link>
|
||||
) : (
|
||||
|
|
|
@ -34,6 +34,7 @@ export function EnvironmentRegistriesDatatable() {
|
|||
titleIcon={Radio}
|
||||
renderTableActions={() => <AddButton />}
|
||||
disableSelect
|
||||
data-cy="environment-registries-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -40,17 +40,18 @@ function Cell({
|
|||
color="link"
|
||||
icon={Users}
|
||||
as={Link}
|
||||
props={{ to: '.access', params: { id: item.Id } }}
|
||||
props={{
|
||||
to: '.access',
|
||||
params: { id: item.Id },
|
||||
'data-cy': `manage-access-link-${item.Name}`,
|
||||
}}
|
||||
data-cy={`registry-manage-access-button-${item.Name}`}
|
||||
>
|
||||
Manage access
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
<BrowseButton
|
||||
registryId={item.Id}
|
||||
registryType={item.Type}
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
<BrowseButton registry={item} environmentId={environmentId} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ export function TagsDatatable({
|
|||
<DeleteButton
|
||||
confirmMessage="Are you sure you want to remove the selected tags?"
|
||||
onConfirmed={() => onRemove(selectedItems)}
|
||||
data-cy="remove-registry-tags-button"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -52,6 +53,7 @@ export function TagsDatatable({
|
|||
},
|
||||
table: 'registry-repository-tags',
|
||||
})}
|
||||
data-cy="registry-tags-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ function ActionsCell({
|
|||
color="link"
|
||||
icon={TagIcon}
|
||||
onClick={() => state.setName(item.Name, tagDetails)}
|
||||
data-cy={`retag-${item.Name}`}
|
||||
>
|
||||
Retag
|
||||
</Button>
|
||||
|
@ -101,12 +102,23 @@ function EditTag({
|
|||
}}
|
||||
autoFocus
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
data-cy={`retag-input-${initialName}`}
|
||||
/>
|
||||
|
||||
{errors.name && <FormError>{errors.name}</FormError>}
|
||||
|
||||
<Button color="none" icon={X} onClick={onCancel} />
|
||||
<Button type="submit" color="none" icon={Check} />
|
||||
<Button
|
||||
color="none"
|
||||
icon={X}
|
||||
onClick={onCancel}
|
||||
data-cy={`retag-cancel-${initialName}`}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
color="none"
|
||||
icon={Check}
|
||||
data-cy={`retag-submit-${initialName}`}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
|
|
|
@ -17,6 +17,7 @@ const columns = [
|
|||
'Name',
|
||||
'portainer.registries.registry.repository.tag',
|
||||
'tag',
|
||||
'registry-tag-name',
|
||||
(item) => item.Name
|
||||
),
|
||||
helper.display({
|
||||
|
|
|
@ -18,6 +18,7 @@ export function RepositoriesDatatable({ dataset }: { dataset?: Repository[] }) {
|
|||
settingsManager={tableState}
|
||||
emptyContentLabel="No repository available."
|
||||
disableSelect
|
||||
data-cy="registry-repositories-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ function NameCell({ getValue }: CellContext<Repository, string>) {
|
|||
to="portainer.registries.registry.repository"
|
||||
params={{ repository: name, endpointId: environmentId }}
|
||||
title={name}
|
||||
data-cy={`repository-name-link-${name}`}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
|
|
@ -14,6 +14,7 @@ export function SaveAuthSettingsButton({ onSubmit, isLoading }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
loadingText="Saving..."
|
||||
data-cy="save-auth-settings-button"
|
||||
isLoading={isLoading}
|
||||
onClick={() => onSubmit()}
|
||||
>
|
||||
|
|
|
@ -24,6 +24,7 @@ export function LDAPGroupsTable({ dataset }: { dataset?: Value[] }) {
|
|||
settingsManager={tableState}
|
||||
emptyContentLabel="No groups found."
|
||||
disableSelect
|
||||
data-cy="ldap-groups-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export function LDAPUsersTable({ dataset }: { dataset?: string[] }) {
|
|||
settingsManager={tableState}
|
||||
emptyContentLabel="No users found."
|
||||
disableSelect
|
||||
data-cy="ldap-users-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ export function AutoEnvCreationSettingsForm({ settings }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
loadingText="generating..."
|
||||
data-cy="save-auto-env-settings-button"
|
||||
isLoading={mutation.isLoading}
|
||||
disabled={!isValid || !dirty}
|
||||
className="!ml-0"
|
||||
|
|
|
@ -18,6 +18,7 @@ export function EnabledWaitingRoomSwitch() {
|
|||
>
|
||||
<Switch
|
||||
id="edge_waiting_room"
|
||||
data-cy="edge-waiting-room-switch"
|
||||
name="EnableWaitingRoom"
|
||||
className="space-right"
|
||||
checked={inputProps.value}
|
||||
|
|
|
@ -69,6 +69,7 @@ export function EdgeComputeSettings({ settings, onSubmit }: Props) {
|
|||
>
|
||||
<Switch
|
||||
id="edge_enable"
|
||||
data-cy="edge-enable-switch"
|
||||
name="edge_enable"
|
||||
className="space-right"
|
||||
checked={values.EnableEdgeComputeFeatures}
|
||||
|
@ -103,6 +104,7 @@ export function EdgeComputeSettings({ settings, onSubmit }: Props) {
|
|||
>
|
||||
<Switch
|
||||
id="edge_enforce_id"
|
||||
data-cy="edge-enforce-id-switch"
|
||||
name="edge_enforce_id"
|
||||
className="space-right"
|
||||
checked={values.EnforceEdgeID}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue