mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
8.3 KiB
265 lines
8.3 KiB
2 years ago
|
<script setup lang="ts">
|
||
|
import { ref } from 'vue'
|
||
|
import { useToast } from 'vue-toastification'
|
||
2 years ago
|
import type { PluginType } from 'nocodb-sdk'
|
||
2 years ago
|
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
|
||
2 years ago
|
import CloseIcon from '~icons/material-symbols/close-rounded'
|
||
2 years ago
|
import MdiPlusIcon from '~icons/mdi/plus'
|
||
2 years ago
|
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
|
||
2 years ago
|
|
||
|
interface Props {
|
||
|
id: string
|
||
|
}
|
||
|
|
||
2 years ago
|
type Plugin = PluginType & {
|
||
|
formDetails: Record<string, any>
|
||
|
parsedInput: Record<string, any>
|
||
|
}
|
||
|
|
||
2 years ago
|
const { id } = defineProps<Props>()
|
||
|
|
||
2 years ago
|
const emits = defineEmits(['saved', 'close'])
|
||
2 years ago
|
|
||
2 years ago
|
enum Action {
|
||
|
Save = 'save',
|
||
|
Test = 'test',
|
||
|
}
|
||
|
|
||
2 years ago
|
const toast = useToast()
|
||
2 years ago
|
|
||
2 years ago
|
const { $api } = useNuxtApp()
|
||
2 years ago
|
|
||
2 years ago
|
const formRef = ref()
|
||
2 years ago
|
|
||
|
let plugin = $ref<Plugin | null>(null)
|
||
|
let pluginFormData = $ref<Record<string, any>>({})
|
||
2 years ago
|
let isLoading = $ref(true)
|
||
2 years ago
|
let loadingAction = $ref<null | Action>(null)
|
||
|
|
||
2 years ago
|
const layout = {
|
||
2 years ago
|
labelCol: { span: 14, pull: 4 },
|
||
|
wrapperCol: { span: 20, pull: 4 },
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
const addSetting = () => pluginFormData.push({})
|
||
2 years ago
|
|
||
|
const saveSettings = async () => {
|
||
2 years ago
|
loadingAction = Action.Save
|
||
2 years ago
|
|
||
|
try {
|
||
|
await formRef.value?.validateFields()
|
||
|
|
||
|
await $api.plugin.update(id, {
|
||
2 years ago
|
input: JSON.stringify(pluginFormData),
|
||
2 years ago
|
active: true,
|
||
|
})
|
||
|
|
||
|
emits('saved')
|
||
2 years ago
|
toast.success(plugin?.formDetails.msgOnInstall || 'Plugin settings saved successfully')
|
||
2 years ago
|
} catch (_e: any) {
|
||
|
const e = await extractSdkResponseErrorMsg(_e)
|
||
|
toast.error(e.message)
|
||
|
} finally {
|
||
2 years ago
|
loadingAction = null
|
||
2 years ago
|
}
|
||
|
}
|
||
|
|
||
|
const testSettings = async () => {
|
||
2 years ago
|
loadingAction = Action.Test
|
||
|
|
||
2 years ago
|
try {
|
||
|
const res = await $api.plugin.test({
|
||
2 years ago
|
input: pluginFormData,
|
||
|
id: plugin?.id,
|
||
|
category: plugin?.category,
|
||
|
title: plugin?.title,
|
||
2 years ago
|
})
|
||
2 years ago
|
|
||
2 years ago
|
if (res) {
|
||
|
toast.success('Successfully tested plugin settings')
|
||
|
} else {
|
||
|
toast.info('Invalid credentials')
|
||
|
}
|
||
|
} catch (_e: any) {
|
||
|
const e = await extractSdkResponseErrorMsg(_e)
|
||
|
toast.error(e.message)
|
||
|
} finally {
|
||
2 years ago
|
loadingAction = null
|
||
2 years ago
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
const doAction = async (action: Action) => {
|
||
|
switch (action) {
|
||
|
case Action.Save:
|
||
2 years ago
|
await saveSettings()
|
||
|
break
|
||
2 years ago
|
case Action.Test:
|
||
2 years ago
|
await testSettings()
|
||
|
break
|
||
|
default:
|
||
2 years ago
|
// noop
|
||
2 years ago
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const readPluginDetails = async () => {
|
||
|
try {
|
||
2 years ago
|
isLoading = true
|
||
2 years ago
|
|
||
2 years ago
|
const res = await $api.plugin.read(id)
|
||
|
const formDetails = JSON.parse(res.input_schema ?? '{}')
|
||
|
const emptyParsedInput = formDetails.array ? [{}] : {}
|
||
|
const parsedInput = res.input ? JSON.parse(res.input) : emptyParsedInput
|
||
|
|
||
2 years ago
|
plugin = { ...res, formDetails, parsedInput }
|
||
|
pluginFormData = plugin.parsedInput
|
||
2 years ago
|
} catch (e) {
|
||
|
console.log(e)
|
||
|
} finally {
|
||
2 years ago
|
isLoading = false
|
||
2 years ago
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
const deleteFormRow = (index: number) => pluginFormData.splice(index, 1)
|
||
2 years ago
|
|
||
|
onMounted(async () => {
|
||
2 years ago
|
if (!plugin) {
|
||
2 years ago
|
await readPluginDetails()
|
||
|
}
|
||
|
})
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
2 years ago
|
<div v-if="isLoading" class="flex flex-row w-full justify-center items-center h-52">
|
||
|
<a-spin size="large" />
|
||
|
</div>
|
||
2 years ago
|
|
||
2 years ago
|
<template v-else>
|
||
|
<div class="flex flex-col">
|
||
2 years ago
|
<div class="w-full relative">
|
||
|
<div class="flex flex-row justify-center pb-4 mb-2 border-b-1 w-full gap-x-1">
|
||
|
<div
|
||
|
v-if="plugin.logo"
|
||
|
class="mr-1 flex items-center justify-center"
|
||
|
:class="[plugin.title === 'SES' ? 'p-2 bg-[#242f3e]' : '']"
|
||
|
>
|
||
|
<img :src="`/${plugin.logo}`" class="h-6" />
|
||
|
</div>
|
||
2 years ago
|
|
||
2 years ago
|
<span class="font-semibold text-lg">{{ plugin.formDetails.title }}</span>
|
||
|
</div>
|
||
|
<div class="absolute -right-2 -top-0.5">
|
||
|
<a-button type="text" class="!rounded-md mr-1" @click="emits('close')">
|
||
|
<template #icon>
|
||
|
<CloseIcon class="flex mx-auto" />
|
||
|
</template>
|
||
|
</a-button>
|
||
|
</div>
|
||
2 years ago
|
</div>
|
||
2 years ago
|
|
||
2 years ago
|
<a-form ref="formRef" v-bind="plugin?.formDetails.array ? {} : layout" :model="pluginFormData" class="mx-4 mt-3">
|
||
2 years ago
|
<!-- Form with multiple entry -->
|
||
2 years ago
|
<div v-if="plugin.formDetails.array" class="flex flex-row justify-center">
|
||
2 years ago
|
<table>
|
||
2 years ago
|
<thead>
|
||
2 years ago
|
<tr>
|
||
|
<th v-for="(columnData, columnIndex) in plugin.formDetails.items" :key="columnIndex">
|
||
2 years ago
|
<div class="text-center font-normal mb-2">
|
||
2 years ago
|
{{ columnData.label }} <span v-if="columnData.required" class="text-red-600">*</span>
|
||
|
</div>
|
||
|
</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
<tr v-for="(itemRow, itemIndex) in plugin.parsedInput" :key="itemIndex">
|
||
|
<td v-for="(columnData, columnIndex) in plugin.formDetails.items" :key="columnIndex" class="px-2">
|
||
|
<a-form-item
|
||
|
class="relative mb-3"
|
||
|
:name="[`${itemIndex}`, columnData.key]"
|
||
|
:rules="[{ required: columnData.required, message: `${columnData.label} is required` }]"
|
||
|
>
|
||
|
<a-input-password
|
||
|
v-if="columnData.type === 'Password'"
|
||
|
v-model:value="itemRow[columnData.key]"
|
||
|
:placeholder="columnData.placeholder"
|
||
|
/>
|
||
|
<a-textarea
|
||
|
v-else-if="columnData.type === 'LongText'"
|
||
|
v-model:value="itemRow[columnData.key]"
|
||
|
:placeholder="columnData.placeholder"
|
||
|
/>
|
||
|
<a-switch
|
||
|
v-else-if="columnData.type === 'Checkbox'"
|
||
|
v-model:value="itemRow[columnData.key]"
|
||
|
:placeholder="columnData.placeholder"
|
||
|
/>
|
||
|
<a-input v-else v-model:value="itemRow[columnData.key]" :placeholder="columnData.placeholder" />
|
||
|
<div
|
||
|
v-if="itemIndex !== 0 && columnIndex === plugin.formDetails.items.length - 1"
|
||
|
class="absolute flex flex-col justify-start mt-2 -right-6 top-0"
|
||
|
>
|
||
|
<MdiDeleteOutlineIcon class="hover:text-red-400 cursor-pointer" @click="deleteFormRow(itemIndex)" />
|
||
|
</div>
|
||
|
</a-form-item>
|
||
|
</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
|
||
2 years ago
|
<tr>
|
||
2 years ago
|
<td :colspan="plugin.formDetails.items.length" class="text-center">
|
||
|
<a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1" @click="addSetting">
|
||
|
<template #icon>
|
||
|
<MdiPlusIcon class="flex mx-auto" />
|
||
|
</template>
|
||
|
</a-button>
|
||
2 years ago
|
</td>
|
||
|
</tr>
|
||
2 years ago
|
</table>
|
||
|
</div>
|
||
2 years ago
|
|
||
|
<!-- Form with only one entry -->
|
||
2 years ago
|
<template v-else>
|
||
2 years ago
|
<a-form-item
|
||
|
v-for="(columnData, i) in plugin.formDetails.items"
|
||
|
:key="i"
|
||
|
:label="columnData.label"
|
||
|
:name="columnData.key"
|
||
|
:rules="[{ required: columnData.required, message: `${columnData.label} is required` }]"
|
||
|
>
|
||
|
<a-input-password
|
||
|
v-if="columnData.type === 'Password'"
|
||
|
v-model:value="pluginFormData[columnData.key]"
|
||
|
:placeholder="columnData.placeholder"
|
||
|
/>
|
||
|
<a-textarea
|
||
|
v-else-if="columnData.type === 'LongText'"
|
||
|
v-model:value="pluginFormData[columnData.key]"
|
||
|
:placeholder="columnData.placeholder"
|
||
|
/>
|
||
|
<a-switch
|
||
|
v-else-if="columnData.type === 'Checkbox'"
|
||
|
v-model:checked="pluginFormData[columnData.key]"
|
||
|
:placeholder="columnData.placeholder"
|
||
|
/>
|
||
|
<a-input v-else v-model:value="pluginFormData[columnData.key]" :placeholder="columnData.placeholder" />
|
||
|
</a-form-item>
|
||
2 years ago
|
</template>
|
||
2 years ago
|
<div class="flex flex-row space-x-4 justify-center mt-4">
|
||
|
<a-button
|
||
|
v-for="(action, i) in plugin.formDetails.actions"
|
||
|
:key="i"
|
||
|
:loading="loadingAction === action.key"
|
||
2 years ago
|
:type="action.key === Action.Save ? 'primary' : 'default'"
|
||
2 years ago
|
:disabled="!!loadingAction"
|
||
2 years ago
|
@click="doAction(action.key)"
|
||
2 years ago
|
>
|
||
|
{{ action.label }}
|
||
|
</a-button>
|
||
|
</div>
|
||
|
</a-form>
|
||
|
</div>
|
||
|
</template>
|
||
2 years ago
|
</template>
|
||
|
|
||
2 years ago
|
<style scoped lang="scss"></style>
|