Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DomainName]: Fix status binding #3923

Merged
merged 4 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions packages/playground/src/components/form_validator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,20 @@ export default {
}),
);
watch(valid, valid => emit("update:modelValue", valid), { immediate: true });

const invalid = computed(() => [...statusMap.value.values()].some(status => status === ValidatorStatus.Invalid));
const pending = computed(() => [...statusMap.value.values()].some(status => status === ValidatorStatus.Pending));
/**
* The validation is considered "init" if:
* - There are no invalid (`ValidatorStatus.Invalid`) statuses.
* - There are no pending (`ValidatorStatus.Pending`) statuses.
* - At least one status is `ValidatorStatus.Init`.
*/
const init = computed(
() =>
!invalid.value &&
!pending.value &&
[...statusMap.value.values()].some(status => status === ValidatorStatus.Init),
);
const form: FormValidatorService = {
register(uid, service) {
statusMap.value.set(uid, ValidatorStatus.Init);
Expand Down Expand Up @@ -72,8 +85,9 @@ export default {
get: uid => serviceMap.value.get(uid),

valid,
invalid: computed(() => [...statusMap.value.values()].some(status => status === ValidatorStatus.Invalid)),
pending: computed(() => [...statusMap.value.values()].some(status => status === ValidatorStatus.Pending)),
invalid,
pending,
init,
validOnInit: props.validOnInit,
inputs: computed(() => [...serviceMap.value.values()] as any),
};
Expand Down
141 changes: 83 additions & 58 deletions packages/playground/src/components/node_selector/TfDomainName.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@
</input-tooltip>

<div ref="input">
<VForm v-model="domainNameValid">
<form-validator ref="domainFormRef">
<VExpandTransition>
<input-tooltip tooltip="Domain Name that will point to this instance" v-if="enableCustomDomain">
<VTextField
ref="customDomainInput"
label="Custom Domain"
placeholder="Your custom domain"
v-model="customDomain"
@vue:mounted="customDomain && ($refs.customDomainInput as VInput).validate()"
validate-on="input"
<InputValidator
:rules="[
d => (d ? true : 'Domain name is required.'),
d => validators.isFQDN('Please provide a valid domain name.')(d)?.message || true,
validators.required('Domain name is required.'),
validators.isFQDN('Please provide a valid domain name.'),
]"
@blur="($refs.customDomainInput as VInput).validate()"
/>
v-model:value="customDomain"
ref="customInputRef"
#="{ props }"
><VTextField
v-bind="props"
label="Custom Domain"
placeholder="Your custom domain"
v-model="customDomain"
/>
</InputValidator>
</input-tooltip>
</VExpandTransition>

Expand All @@ -33,42 +35,48 @@
tooltip="Creates a subdomain for your instance on the selected domain to be able to access your instance from the browser."
v-if="!disableSelectedDomain"
>
<VAutocomplete
<InputValidator
#="{ props }"
ref="domainInput"
validate-on="input"
label="Select domain"
placeholder="Select a domain"
:items="loadedDomains"
:loading="domainsTask.loading"
item-title="publicConfig.domain"
v-model="selectedDomain"
:error-messages="domainsTask.error?.message"
@vue:mounted="selectedDomain && ($refs.domainInput as VInput).validate()"
:rules="[d => (d ? true : 'Domain is required.')]"
@update:menu="opened => !opened && $nextTick().then(() => ($refs.domainInput as VInput).validate())"
@blur="$nextTick().then(() => ($refs.domainInput as VInput).validate())"
return-object
:rules="[validators.required('Domain is required.')]"
:value="(selectedDomain as INode)"
>
<template #append-item v-if="pagination.page !== -1">
<VContainer>
<VBtn
@click="loadDomains"
block
color="secondary"
variant="tonal"
:loading="domainsTask.loading"
prepend-icon="mdi-reload"
>
Load More Domains
</VBtn>
</VContainer>
</template>
<template v-slot:append>
<v-slide-x-reverse-transition mode="out-in">
<v-icon icon="mdi-reload" @click="reloadDomains"></v-icon>
</v-slide-x-reverse-transition>
</template>
</VAutocomplete>
<VAutocomplete
v-bind="props"
validate-on="input"
label="Select domain"
placeholder="Select a domain"
:items="loadedDomains"
:loading="domainsTask.loading"
item-title="publicConfig.domain"
v-model="selectedDomain"
:error-messages="[
...props?.errorMessages,
...(domainsTask.error?.message ? [domainsTask.error.message] : []),
]"
return-object
>
<template #append-item v-if="pagination.page !== -1">
<VContainer>
<VBtn
@click="loadDomains"
block
color="secondary"
variant="tonal"
:loading="domainsTask.loading"
prepend-icon="mdi-reload"
>
Load More Domains
</VBtn>
</VContainer>
</template>
<template v-slot:append>
<v-slide-x-reverse-transition mode="out-in">
<v-icon icon="mdi-reload" @click="reloadDomains"></v-icon>
</v-slide-x-reverse-transition>
</template>
</VAutocomplete>
</InputValidator>
</input-tooltip>
</VExpandTransition>

Expand All @@ -88,7 +96,7 @@
<span class="font-weight-bold">{{ customDomain }}</span> pointing to
<span class="font-weight-bold">{{ selectedDomain.publicConfig.ipv4.split("/")[0] }}</span>
</v-alert>
</VForm>
</form-validator>
</div>
</section>
</template>
Expand All @@ -101,12 +109,14 @@ import { onMounted } from "vue";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { VInput } from "vuetify/components/VInput";

import type { InputValidatorService } from "@/hooks/input_validator";
import { type InputValidatorService, useInputRef } from "@/hooks/input_validator";

import { useAsync, usePagination, useWatchDeep } from "../../hooks";
import { useForm, ValidatorStatus } from "../../hooks/form_validator";
import { useForm, useFormRef, ValidatorStatus } from "../../hooks/form_validator";
import { useGrid } from "../../stores";
import type { DomainInfo, NetworkFeatures, SelectionDetailsFilters } from "../../types/nodeSelector";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { INode } from "../../utils/filter_nodes";
import { getNodePageCount, loadNodes } from "../../utils/nodeSelector";

export default {
Expand Down Expand Up @@ -159,11 +169,12 @@ export default {
const selectedDomain = ref<NodeInfo | null>(null);
const loadDomains = () => domainsTask.value.run(gridStore, filters.value);

const domainInput = useInputRef();
const reloadDomains = async (_filters: FilterOptions = filters.value) => {
domainInput.value?.reset();
if (selectedDomain.value) {
selectedDomain.value = null;
bindModelValue();
bindStatus();
}
await pageCountTask.value.run(gridStore, _filters);
pagination.value.reset(pageCountTask.value.data as number);
Expand All @@ -184,11 +195,7 @@ export default {
{ immediate: true, deep: true, ignoreFields: ["page"] },
);
const customDomain = ref("");

const domainNameValid = ref<boolean | null>(null);
watch(domainNameValid, valid => {
bindStatus(valid === null ? ValidatorStatus.Init : valid ? ValidatorStatus.Valid : ValidatorStatus.Invalid);
});
const domainFormRef = useFormRef();

const disableSelectedDomain = computed(() => enableCustomDomain.value && props.filters.ipv4 === true);
const useFQDN = computed(() => enableCustomDomain.value && (props.useFqdn || props.filters.ipv4 === false));
Expand Down Expand Up @@ -228,25 +235,41 @@ export default {
};

onMounted(() => {
bindStatus();
loadDomains();
form?.register(uid.toString(), fakeService);
});
onUnmounted(() => form?.unregister(uid.toString()));

onMounted(bindStatus);
function bindStatus(status?: ValidatorStatus): void {
const s = status || ValidatorStatus.Init;
fakeService.status = s;
form?.updateStatus(uid.toString(), s);
ctx.emit("update:status", s);
}

const status = computed(() => {
const _domain = domainFormRef?.value;
if (!_domain) return ValidatorStatus.Init;

switch (true) {
case _domain.valid as unknown as boolean:
return ValidatorStatus.Valid;
case _domain.invalid as unknown as boolean:
return ValidatorStatus.Invalid;
case _domain.pending as unknown as boolean:
return ValidatorStatus.Pending;
default:
return ValidatorStatus.Init;
}
});

watch(status, () => bindStatus(status.value), { immediate: true });

return {
pagination,
input,

domainNameValid,

enableCustomDomain,
customDomain,

Expand All @@ -256,6 +279,8 @@ export default {
loadDomains,
reloadDomains,

domainFormRef,
domainInput,
disableSelectedDomain,
useFQDN,
};
Expand Down
1 change: 1 addition & 0 deletions packages/playground/src/hooks/form_validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface FormValidatorService {
valid: ComputedRef<boolean>;
invalid: ComputedRef<boolean>;
pending: ComputedRef<boolean>;
init: ComputedRef<boolean>;
validOnInit: boolean;
inputs: ComputedRef<InputValidatorService[]>;
}
Expand Down
Loading