-
Notifications
You must be signed in to change notification settings - Fork 181
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add & list server regions (#3423)
* WIP create modal * babababa * create dialog looks ok * FE largely there * workss * cleanup * fixed up test plumbing to avoid deadlocks and simplify GQL calls * test fix * added all tests * CI fix
- Loading branch information
Showing
45 changed files
with
1,248 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,4 +71,7 @@ minio-data/ | |
postgres-data/ | ||
redis-data/ | ||
|
||
.tshy-build | ||
.tshy-build | ||
|
||
# Server | ||
multiregion.json |
65 changes: 65 additions & 0 deletions
65
packages/frontend-2/components/settings/server/Regions.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<template> | ||
<section> | ||
<div class="md:max-w-5xl md:mx-auto pb-6 md:pb-0"> | ||
<SettingsSectionHeader | ||
title="Regions" | ||
text="Manage the regions available for customizing data residency" | ||
/> | ||
<div class="flex flex-col space-y-6"> | ||
<div class="flex flex-row-reverse"> | ||
<div v-tippy="disabledMessage"> | ||
<FormButton | ||
:disabled="!canCreateRegion" | ||
@click="isAddEditDialogOpen = true" | ||
> | ||
Create | ||
</FormButton> | ||
</div> | ||
</div> | ||
<SettingsServerRegionsTable :items="tableItems" /> | ||
</div> | ||
</div> | ||
<SettingsServerRegionsAddEditDialog | ||
v-model:open="isAddEditDialogOpen" | ||
:available-region-keys="availableKeys" | ||
/> | ||
</section> | ||
</template> | ||
<script setup lang="ts"> | ||
import { useQuery } from '@vue/apollo-composable' | ||
import { graphql } from '~~/lib/common/generated/gql' | ||
const isAddEditDialogOpen = ref(false) | ||
const query = graphql(` | ||
query SettingsServerRegions { | ||
serverInfo { | ||
multiRegion { | ||
regions { | ||
id | ||
...SettingsServerRegionsTable_ServerRegionItem | ||
} | ||
availableKeys | ||
} | ||
} | ||
} | ||
`) | ||
const pageFetchPolicy = usePageQueryStandardFetchPolicy() | ||
const { result } = useQuery(query, undefined, () => ({ | ||
fetchPolicy: pageFetchPolicy.value | ||
})) | ||
const tableItems = computed(() => result.value?.serverInfo?.multiRegion?.regions) | ||
const availableKeys = computed( | ||
() => result.value?.serverInfo?.multiRegion?.availableKeys || [] | ||
) | ||
const canCreateRegion = computed(() => availableKeys.value.length > 0) | ||
const disabledMessage = computed(() => { | ||
if (canCreateRegion.value) return undefined | ||
if (!availableKeys.value.length) return 'No available region keys' | ||
return undefined | ||
}) | ||
</script> |
116 changes: 116 additions & 0 deletions
116
packages/frontend-2/components/settings/server/regions/AddEditDialog.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<template> | ||
<LayoutDialog | ||
v-model:open="open" | ||
max-width="sm" | ||
:buttons="dialogButtons" | ||
hide-closer | ||
prevent-close-on-click-outside | ||
:on-submit="onSubmit" | ||
> | ||
<template #header>Create a new region</template> | ||
<div class="flex flex-col gap-y-4 mb-2"> | ||
<FormTextInput | ||
name="name" | ||
label="Region name" | ||
placeholder="Name" | ||
color="foundation" | ||
:rules="[isRequired, isStringOfLength({ maxLength: 64 })]" | ||
auto-focus | ||
autocomplete="off" | ||
show-required | ||
show-label | ||
help="Human readable name for the region." | ||
/> | ||
<FormTextArea | ||
name="description" | ||
label="Region description" | ||
placeholder="Description" | ||
color="foundation" | ||
size="lg" | ||
show-label | ||
show-optional | ||
:rules="[isStringOfLength({ maxLength: 65536 })]" | ||
/> | ||
<SettingsServerRegionsKeySelect | ||
show-label | ||
name="key" | ||
:items="availableRegionKeys" | ||
label="Region key" | ||
:rules="[isRequired]" | ||
show-required | ||
help="These keys come from the server multi region configuration file." | ||
/> | ||
</div> | ||
</LayoutDialog> | ||
</template> | ||
<script lang="ts" setup> | ||
import { isRequired, isStringOfLength } from '~~/lib/common/helpers/validation' | ||
import type { LayoutDialogButton } from '@speckle/ui-components' | ||
import { graphql } from '~/lib/common/generated/gql' | ||
import type { SettingsServerRegionsAddEditDialog_ServerRegionItemFragment } from '~/lib/common/generated/gql/graphql' | ||
import { useForm } from 'vee-validate' | ||
import { useCreateRegion } from '~/lib/multiregion/composables/management' | ||
import { useMutationLoading } from '@vue/apollo-composable' | ||
graphql(` | ||
fragment SettingsServerRegionsAddEditDialog_ServerRegionItem on ServerRegionItem { | ||
id | ||
name | ||
description | ||
key | ||
} | ||
`) | ||
type ServerRegionItem = SettingsServerRegionsAddEditDialog_ServerRegionItemFragment | ||
type DialogModel = Omit<ServerRegionItem, 'id'> | ||
defineProps<{ | ||
availableRegionKeys: string[] | ||
}>() | ||
const open = defineModel<boolean>('open', { required: true }) | ||
const model = defineModel<DialogModel>() | ||
const { handleSubmit, setValues } = useForm<DialogModel>() | ||
const createRegion = useCreateRegion() | ||
const loading = useMutationLoading() | ||
const dialogButtons = computed((): LayoutDialogButton[] => { | ||
return [ | ||
{ | ||
text: 'Cancel', | ||
props: { color: 'outline' }, | ||
onClick: () => (open.value = false) | ||
}, | ||
{ | ||
text: 'Create', | ||
props: { | ||
submit: true, | ||
disabled: loading.value | ||
}, | ||
onClick: noop | ||
} | ||
] | ||
}) | ||
const isEditMode = computed(() => !!model.value) | ||
const onSubmit = handleSubmit(async (values) => { | ||
if (isEditMode.value) return // TODO: | ||
const res = await createRegion({ | ||
input: values | ||
}) | ||
if (res?.id) { | ||
open.value = false | ||
} | ||
}) | ||
watch( | ||
model, | ||
(newVal, oldVal) => { | ||
if (newVal && newVal !== oldVal) { | ||
setValues(newVal) | ||
} | ||
}, | ||
{ immediate: true } | ||
) | ||
</script> |
57 changes: 57 additions & 0 deletions
57
packages/frontend-2/components/settings/server/regions/KeySelect.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<template> | ||
<FormSelectBase | ||
v-bind="props" | ||
v-model="selectedValue" | ||
:name="name || 'regions-key'" | ||
:allow-unset="false" | ||
mount-menu-on-body | ||
> | ||
<template #option="{ item }"> | ||
<div class="flex items-center"> | ||
<span class="truncate">{{ item }}</span> | ||
</div> | ||
</template> | ||
<template #nothing-selected> | ||
{{ multiple ? 'Select region keys' : 'Select a region key' }} | ||
</template> | ||
<template #something-selected="{ value }"> | ||
<template v-if="isArray(value)"> | ||
{{ value.join(', ') }} | ||
</template> | ||
<template v-else> | ||
{{ value }} | ||
</template> | ||
</template> | ||
</FormSelectBase> | ||
</template> | ||
<script setup lang="ts"> | ||
import { isArray } from 'lodash-es' | ||
import type { RuleExpression } from 'vee-validate' | ||
import { useFormSelectChildInternals } from '~/lib/form/composables/select' | ||
type ValueType = string | string[] | undefined | ||
const emit = defineEmits<{ | ||
(e: 'update:modelValue', v: ValueType): void | ||
}>() | ||
const props = defineProps<{ | ||
modelValue?: ValueType | ||
label: string | ||
items: string[] | ||
multiple?: boolean | ||
name?: string | ||
showOptional?: boolean | ||
showRequired?: boolean | ||
showLabel?: boolean | ||
labelId?: string | ||
buttonId?: string | ||
help?: string | ||
rules?: RuleExpression<string | string[] | undefined> | ||
}>() | ||
const { selectedValue } = useFormSelectChildInternals<string>({ | ||
props: toRefs(props), | ||
emit | ||
}) | ||
</script> |
77 changes: 77 additions & 0 deletions
77
packages/frontend-2/components/settings/server/regions/Table.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<template> | ||
<div> | ||
<LayoutTable | ||
:items="items" | ||
:columns="[ | ||
{ id: 'name', header: 'Name', classes: 'col-span-3 truncate' }, | ||
{ id: 'key', header: 'Key', classes: 'col-span-2 truncate' }, | ||
{ id: 'description', header: 'Description', classes: 'col-span-6 truncate' }, | ||
{ id: 'actions', header: '', classes: 'col-span-1' } | ||
]" | ||
empty-message="No regions defined" | ||
> | ||
<template #name="{ item }"> | ||
{{ item.name }} | ||
</template> | ||
<template #key="{ item }"> | ||
<span class="text-foreground-2">{{ item.key }}</span> | ||
</template> | ||
<template #description="{ item }"> | ||
<span class="text-foreground-2">{{ item.description }}</span> | ||
</template> | ||
<template #actions="{ item }"> | ||
<template v-if="true"> | ||
<!-- Hiding actions for now --> | ||
| ||
</template> | ||
<LayoutMenu | ||
v-else | ||
v-model:open="showActionsMenu[item.id]" | ||
:items="actionItems" | ||
mount-menu-on-body | ||
:menu-position="HorizontalDirection.Left" | ||
> | ||
<FormButton | ||
:color="showActionsMenu[item.id] ? 'outline' : 'subtle'" | ||
hide-text | ||
:icon-right="showActionsMenu[item.id] ? XMarkIcon : EllipsisHorizontalIcon" | ||
@click.stop="toggleMenu(item.id)" | ||
/> | ||
</LayoutMenu> | ||
</template> | ||
</LayoutTable> | ||
</div> | ||
</template> | ||
<script setup lang="ts"> | ||
import type { LayoutMenuItem } from '@speckle/ui-components' | ||
import { HorizontalDirection } from '~~/lib/common/composables/window' | ||
import { EllipsisHorizontalIcon, XMarkIcon } from '@heroicons/vue/24/outline' | ||
import { graphql } from '~/lib/common/generated/gql' | ||
import type { SettingsServerRegionsTable_ServerRegionItemFragment } from '~/lib/common/generated/gql/graphql' | ||
graphql(` | ||
fragment SettingsServerRegionsTable_ServerRegionItem on ServerRegionItem { | ||
id | ||
name | ||
key | ||
description | ||
} | ||
`) | ||
enum ActionTypes { | ||
Edit = 'edit' | ||
} | ||
defineProps<{ | ||
items: SettingsServerRegionsTable_ServerRegionItemFragment[] | undefined | ||
}>() | ||
const showActionsMenu = ref<Record<string, boolean>>({}) | ||
const actionItems: LayoutMenuItem[][] = [ | ||
[{ title: 'Edit region', id: ActionTypes.Edit }] | ||
] | ||
const toggleMenu = (itemId: string) => { | ||
showActionsMenu.value[itemId] = !showActionsMenu.value[itemId] | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.