Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
2eha0 committed Jan 15, 2025
1 parent 2a2b7b6 commit 5dd3f3a
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
<template>
<h2>Konnect API</h2>
<template v-if="false">
<h2>Konnect API</h2>
<RedisConfigurationForm
:config="konnectConfig"
/>
</template>

<h2>Kong Manager API</h2>
<RedisConfigurationForm
:config="konnectConfig"
:config="kongManagerConfig"
/>
</template>

<script setup lang="ts">
import { RedisConfigurationForm } from '../../src'
import type { KonnectRedisConfigurationFormConfig } from '../../src'
import type {
KonnectRedisConfigurationFormConfig,
KongManagerRedisConfigurationFormConfig,
} from '../../src'
const controlPlaneId = import.meta.env.VITE_KONNECT_CONTROL_PLANE_ID || ''
Expand All @@ -16,4 +26,11 @@ const konnectConfig: KonnectRedisConfigurationFormConfig = {
apiBaseUrl: '/us/kong-api',
controlPlaneId,
}
const kongManagerConfig: KongManagerRedisConfigurationFormConfig = {
app: 'kongManager',
workspace: 'default',
apiBaseUrl: '/kong-manager', // For local dev server proxy
cancelRoute: { name: 'home' },
}
</script>
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<template>
<div class="kong-ui-entities-redis-configurations-form">
<div>
<EntityBaseForm
:can-submit="canSubmit"
:config="config"
:edit-id="partialId"
:entity-type="SupportedEntityType.RedisConfiguration"
:error-message="undefined"
:fetch-url="undefined"
:error-message="form.errorMessage"
:fetch-url="fetchUrl"
:form-fields="payload"
:is-readonly="form.readonly"
@cancel="noop"
@fetch:error="noop"
@fetch:success="noop"
@fetch:success="updateFormValues"
@loading="noop"
@submit="noop"
@submit="submit"
>
<EntityFormSection
:description="t('form.sections.type.description')"
Expand Down Expand Up @@ -59,6 +59,7 @@
tooltipAttributes: { maxWidth: '400' },
}"
:readonly="form.readonly"
required
/>
<KSelect
v-model="form.fields.config.sentinel_role"
Expand All @@ -69,6 +70,7 @@
tooltipAttributes: { maxWidth: '400' },
}"
:readonly="form.readonly"
required
/>
<SentinelNodes
v-model="form.fields.config.sentinel_nodes"
Expand Down Expand Up @@ -325,19 +327,28 @@ import { useRedisConfigurationForm } from '../composables/useRedisConfigurationF
import ClusterNodes from './ClusterNodes.vue'
import composables from '../composables'
import SentinelNodes from './SentinelNodes.vue'
import endpoints from '../partials-endpoints'
import type { PropType } from 'vue'
import type { KonnectRedisConfigurationFormConfig } from '../types'
import type {
KongManagerRedisConfigurationFormConfig,
KonnectRedisConfigurationFormConfig,
} from '../types'
import type { SelectItem } from '@kong/kongponents/dist/types'
const props = defineProps({
config: {
type: Object as PropType<KonnectRedisConfigurationFormConfig>,
type: Object as PropType<KonnectRedisConfigurationFormConfig | KongManagerRedisConfigurationFormConfig>,
required: true,
validator: (config: unknown) => {
validator: (config: KonnectRedisConfigurationFormConfig | KongManagerRedisConfigurationFormConfig) => {
if (!config || !['konnect', 'kongManager'].includes(config?.app)) return false
if (config?.app === 'konnect' && !config?.controlPlaneId) return false
if (config?.app === 'kongManager' && typeof config?.workspace !== 'string') return false
if (!config?.cancelRoute) return false
return true
},
},
/** If a valid partialId is provided, it will put the form in Edit mode instead of Create */
partialId: {
type: String,
required: false,
Expand Down Expand Up @@ -413,16 +424,23 @@ const {
isEdit,
userSelectedRedisType,
redisType,
fetchUrl,
submit,
} = useRedisConfigurationForm({
partialId: props.partialId,
config: props.config,
})
</script>
<style lang="scss" scoped>
.kong-ui-entities-redis-configurations-form {
// Add component styles as needed
const updateFormValues = (data: Record<string, any>) => {
form.fields.config = Object.assign({}, form.fields.config, data.config)
form.fields.config.sentinel_nodes = data.config.sentinel_nodes ?? []
form.fields.config.cluster_nodes = data.config.cluster_nodes ?? []
form.fields.name = data.name
form.fields.type = data.type
}
</script>

<style lang="scss" scoped>
.secret-picker-provider {
margin-top: $kui-space-40 !important;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { computed, reactive, ref, watch } from 'vue'
import { EntityBaseFormType, useAxios, useErrors } from '@kong-ui-public/entities-shared'

import { getRedisType, mapRedisTypeToPartialType, shallowCopyWithoutId } from '../helpers'
import { getRedisType, mapRedisTypeToPartialType, shallowCopyWithoutId, standardize as s } from '../helpers'
import { RedisType } from '../types'
import { DEFAULT_REDIS_TYPE } from '../constants'
import endpoints from '../partials-endpoints'

import type { RedisConfigurationFormState } from '../types'
import type { KongManagerRedisConfigurationFormConfig, KonnectRedisConfigurationFormConfig, RedisConfigurationFormState } from '../types'

export type Options = {
partialId?: string
config: KonnectRedisConfigurationFormConfig | KongManagerRedisConfigurationFormConfig
}

export const useRedisConfigurationForm = (options: Options) => {
const { partialId } = options
const { partialId, config } = options
const isEdit = !!partialId
const { axiosInstance } = useAxios(config.axiosRequestConfig)
const { getMessageFromError } = useErrors()
const formType = computed((): EntityBaseFormType => partialId
? EntityBaseFormType.Edit
: EntityBaseFormType.Create)


const form = reactive<RedisConfigurationFormState>({
fields: {
name: '',
Expand Down Expand Up @@ -74,7 +84,7 @@ export const useRedisConfigurationForm = (options: Options) => {
case RedisType.CLUSTER:
return !!config.cluster_nodes.length && config.cluster_nodes.every((node) => node.ip.length > 0 && node.port > 0)
case RedisType.SENTINEL:
return !!config.sentinel_nodes.length && config.sentinel_nodes.every((node) => node.host.length > 0 && node.port > 0)
return !!config.sentinel_nodes.length && config.sentinel_nodes.every((node) => node.host.length > 0 && node.port > 0) && config.sentinel_master.length > 0 && config.sentinel_role && config.sentinel_role.length > 0
default:
throw new Error('Invalid redis type')
}
Expand All @@ -89,55 +99,53 @@ export const useRedisConfigurationForm = (options: Options) => {
config: {
host: form.fields.config.host,
port: form.fields.config.port,
timeout: form.fields.config.timeout,
username: form.fields.config.username,
database: form.fields.config.database,
password: form.fields.config.password,
timeout: s.integer(form.fields.config.timeout),
username: s.string(form.fields.config.username, null),
database: s.integer(form.fields.config.database),
password: s.string(form.fields.config.password, null),
ssl: form.fields.config.ssl,
ssl_verify: form.fields.config.ssl_verify,
server_name: form.fields.config.server_name,
server_name: s.string(form.fields.config.server_name, null),
},
}
case RedisType.HOST_PORT_EE:
return {
name: form.fields.name,
type: form.fields.type,
config: {
connect_timeout: s.integer(form.fields.config.connect_timeout),
connection_is_proxied: form.fields.config.connection_is_proxied,
database: s.integer(form.fields.config.database),
host: form.fields.config.host,
port: form.fields.config.port,
timeout: form.fields.config.timeout,
username: form.fields.config.username,
database: form.fields.config.database,
password: form.fields.config.password,
ssl: form.fields.config.ssl,
keepalive_backlog: s.integer(form.fields.config.keepalive_backlog),
keepalive_pool_size: s.integer(form.fields.config.keepalive_pool_size),
password: s.string(form.fields.config.password, null),
port: s.integer(form.fields.config.port),
read_timeout: s.integer(form.fields.config.read_timeout),
send_timeout: s.integer(form.fields.config.send_timeout),
server_name: s.string(form.fields.config.server_name, null),
ssl_verify: form.fields.config.ssl_verify,
server_name: form.fields.config.server_name,
connect_timeout: form.fields.config.connect_timeout,
send_timeout: form.fields.config.send_timeout,
read_timeout: form.fields.config.read_timeout,
keepalive_pool_size: form.fields.config.keepalive_pool_size,
keepalive_backlog: form.fields.config.keepalive_backlog,
connection_is_proxied: form.fields.config.connection_is_proxied,
ssl: form.fields.config.ssl,
username: s.string(form.fields.config.username, null),
},
}
case RedisType.CLUSTER:
return {
name: form.fields.name,
type: form.fields.type,
config: {
cluster_nodes: form.fields.config.cluster_nodes.map(shallowCopyWithoutId),
cluster_max_redirections: form.fields.config.cluster_max_redirections,
timeout: form.fields.config.timeout,
username: form.fields.config.username,
password: form.fields.config.password,
cluster_nodes: s.clusterNodes(form.fields.config.cluster_nodes),
cluster_max_redirections: s.integer(form.fields.config.cluster_max_redirections),
username: s.string(form.fields.config.username, null),
password: s.string(form.fields.config.password, null),
ssl: form.fields.config.ssl,
ssl_verify: form.fields.config.ssl_verify,
server_name: form.fields.config.server_name,
connect_timeout: form.fields.config.connect_timeout,
send_timeout: form.fields.config.send_timeout,
read_timeout: form.fields.config.read_timeout,
keepalive_pool_size: form.fields.config.keepalive_pool_size,
keepalive_backlog: form.fields.config.keepalive_backlog,
server_name: s.string(form.fields.config.server_name, null),
connect_timeout: s.integer(form.fields.config.connect_timeout),
send_timeout: s.integer(form.fields.config.send_timeout),
read_timeout: s.integer(form.fields.config.read_timeout),
keepalive_pool_size: s.integer(form.fields.config.keepalive_pool_size),
keepalive_backlog: s.integer(form.fields.config.keepalive_backlog),
connection_is_proxied: form.fields.config.connection_is_proxied,
},
}
Expand All @@ -146,19 +154,19 @@ export const useRedisConfigurationForm = (options: Options) => {
name: form.fields.name,
type: form.fields.type,
config: {
sentinel_master: form.fields.config.sentinel_master,
sentinel_nodes: form.fields.config.sentinel_nodes.map(shallowCopyWithoutId),
timeout: form.fields.config.timeout,
username: form.fields.config.username,
password: form.fields.config.password,
sentinel_master: s.string(form.fields.config.sentinel_master, null),
sentinel_nodes: s.sentinelNodes(form.fields.config.sentinel_nodes),
sentinel_role: s.string(form.fields.config.sentinel_role, null),
username: s.string(form.fields.config.username, null),
password: s.string(form.fields.config.password, null),
ssl: form.fields.config.ssl,
ssl_verify: form.fields.config.ssl_verify,
server_name: form.fields.config.server_name,
connect_timeout: form.fields.config.connect_timeout,
send_timeout: form.fields.config.send_timeout,
read_timeout: form.fields.config.read_timeout,
keepalive_pool_size: form.fields.config.keepalive_pool_size,
keepalive_backlog: form.fields.config.keepalive_backlog,
server_name: s.string(form.fields.config.server_name, null),
connect_timeout: s.integer(form.fields.config.connect_timeout),
send_timeout: s.integer(form.fields.config.send_timeout),
read_timeout: s.integer(form.fields.config.read_timeout),
keepalive_pool_size: s.integer(form.fields.config.keepalive_pool_size),
keepalive_backlog: s.integer(form.fields.config.keepalive_backlog),
connection_is_proxied: form.fields.config.connection_is_proxied,
},
}
Expand All @@ -167,12 +175,48 @@ export const useRedisConfigurationForm = (options: Options) => {
}
})

const submitUrl = computed<string>(() => {
let url = `${config.apiBaseUrl}${endpoints.form[config.app][formType.value]}`

if (config.app === 'konnect') {
url = url.replace(/{controlPlaneId}/gi, config?.controlPlaneId || '')
} else if (config.app === 'kongManager') {
url = url.replace(/\/{workspace}/gi, config?.workspace ? `/${config.workspace}` : '')
}

// Always replace the id when editing
url = url.replace(/{id}/gi, partialId || '')

return url
})

const fetchUrl = computed<string>(() => endpoints.form[config?.app]?.edit)

const submit = async () => {
try {
form.readonly = true
form.errorMessage = ''

if (formType.value === EntityBaseFormType.Create) {
await axiosInstance.post(submitUrl.value, payload.value)
} else {
await axiosInstance.patch(submitUrl.value, payload.value)
}
} catch (e: unknown) {
form.errorMessage = getMessageFromError(e)
form.readonly = false
}
}

return {
form,
canSubmit,
payload,
isEdit,
redisType,
userSelectedRedisType,
formType,
fetchUrl,
submit,
}
}
36 changes: 33 additions & 3 deletions packages/entities/entities-redis-configurations/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { v4 as uuidv4 } from 'uuid'

import { DEFAULT_CLUSTER_NODE, DEFAULT_SENTINEL_NODE } from './constants'
import type { Identifiable, PartialType, RedisConfigurationFields } from './types'
import { PartialType, type ClusterNode, type Identifiable, type RedisConfigurationFields, type SentinelNode } from './types'
import { RedisType } from './types'

export const shallowCopyWithId = <T extends Record<any, any>>(node: T): Identifiable<T> => {
Expand All @@ -18,7 +18,7 @@ export const genDefaultSentinelNode = () => shallowCopyWithId(DEFAULT_SENTINEL_N
export const genDefaultClusterNode = () => shallowCopyWithId(DEFAULT_CLUSTER_NODE)

export const getRedisType = (fields: RedisConfigurationFields): RedisType => {
if (fields.type === 'redis-ce') {
if (fields.type === PartialType.REDIS_CE) {
return RedisType.HOST_PORT_CE
}

Expand All @@ -34,5 +34,35 @@ export const getRedisType = (fields: RedisConfigurationFields): RedisType => {
}

export const mapRedisTypeToPartialType = (type: RedisType): PartialType => {
return type === RedisType.HOST_PORT_CE ? 'redis-ce' : 'redis-ee'
return type === RedisType.HOST_PORT_CE ? PartialType.REDIS_CE : PartialType.REDIS_EE
}

export const standardize = {
integer<T>(value: string | number | undefined | null, defaultValue?: T): number | T {
if (value === undefined || value === null) {
return defaultValue as T
}
return parseInt(value.toString(), 10)
},

string<T>(value: string | number | undefined | null, defaultValue?: T): string | T {
if (value === undefined || value === null || value === '') {
return defaultValue as T
}
return value.toString()
},

clusterNodes(nodes: Identifiable<ClusterNode>[]): ClusterNode[] {
return nodes.map(node => ({
...shallowCopyWithoutId(node),
port: standardize.integer(node.port)!,
}))
},

sentinelNodes(nodes: Identifiable<SentinelNode>[]): SentinelNode[] {
return nodes.map(node => ({
...shallowCopyWithoutId(node),
port: standardize.integer(node.port)!,
}))
},
}
Loading

0 comments on commit 5dd3f3a

Please sign in to comment.