nginx-ui/app/src/views/config/ConfigEditor.vue
2024-07-25 18:19:42 +08:00

232 lines
6.1 KiB
Vue

<script setup lang="ts">
import { message } from 'ant-design-vue'
import type { Ref } from 'vue'
import { formatDateTime } from '@/lib/helper'
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
import config from '@/api/config'
import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
import ngx from '@/api/ngx'
import InspectConfig from '@/views/config/InspectConfig.vue'
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
import type { ChatComplicationMessage } from '@/api/openai'
const route = useRoute()
const router = useRouter()
const refForm = ref()
const refInspectConfig = ref()
const origName = ref('')
const addMode = computed(() => !route.params.name)
const errors = ref({})
const basePath = computed(() => {
if (route.query.basePath)
return route?.query?.basePath?.toString().replaceAll('/', '')
else if (typeof route.params.name === 'object')
return (route.params.name as string[]).slice(0, -1).join('/')
else
return ''
})
const data = ref({
name: '',
content: '',
filepath: '',
})
const historyChatgptRecord = ref([]) as Ref<ChatComplicationMessage[]>
const activeKey = ref(['basic', 'chatgpt'])
const modifiedAt = ref('')
const nginxConfigBase = ref('')
const newPath = computed(() => [nginxConfigBase.value, basePath.value, data.value.name]
.filter(v => v)
.join('/'))
const relativePath = computed(() => (route.params.name as string[]).join('/'))
async function init() {
const { name } = route.params
data.value.name = name?.[name?.length - 1] ?? ''
origName.value = data.value.name
if (data.value.name) {
config.get(relativePath.value).then(r => {
data.value = r
historyChatgptRecord.value = r.chatgpt_messages
modifiedAt.value = r.modified_at
}).catch(r => {
message.error(r.message ?? $gettext('Server error'))
})
}
else {
data.value.content = ''
historyChatgptRecord.value = []
data.value.filepath = ''
}
}
onMounted(async () => {
await config.get_base_path().then(r => {
nginxConfigBase.value = r.base_path
})
await init()
})
function save() {
refForm.value.validate().then(() => {
config.save(addMode.value ? null : relativePath.value, {
name: data.value.name,
filepath: data.value.filepath,
new_filepath: newPath.value,
content: data.value.content,
}).then(r => {
data.value.content = r.content
message.success($gettext('Saved successfully'))
router.push(`/config/${r.filepath.replaceAll(`${nginxConfigBase.value}/`, '')}/edit`)
}).catch(e => {
errors.value = e.errors
message.error($gettext('Save error %{msg}', { msg: e.message ?? '' }))
}).finally(() => {
refInspectConfig.value.test()
})
})
}
function formatCode() {
ngx.format_code(data.value.content).then(r => {
data.value.content = r.content
message.success($gettext('Format successfully'))
}).catch(r => {
message.error($gettext('Format error %{msg}', { msg: r.message ?? '' }))
})
}
function goBack() {
router.push({
path: '/config',
query: {
dir: basePath.value || undefined,
},
})
}
</script>
<template>
<ARow :gutter="16">
<ACol
:xs="24"
:sm="24"
:md="18"
>
<ACard :title="addMode ? $gettext('Add Configuration') : $gettext('Edit Configuration')">
<InspectConfig
v-show="!addMode"
ref="refInspectConfig"
/>
<CodeEditor v-model:content="data.content" />
<FooterToolBar>
<ASpace>
<AButton @click="goBack">
{{ $gettext('Back') }}
</AButton>
<AButton @click="formatCode">
{{ $gettext('Format Code') }}
</AButton>
<AButton
type="primary"
@click="save"
>
{{ $gettext('Save') }}
</AButton>
</ASpace>
</FooterToolBar>
</ACard>
</ACol>
<ACol
:xs="24"
:sm="24"
:md="6"
>
<ACard class="col-right">
<ACollapse
v-model:activeKey="activeKey"
ghost
>
<ACollapsePanel
key="basic"
:header="$gettext('Basic')"
>
<AForm
ref="refForm"
layout="vertical"
:model="data"
:rules="{
name: [
{ required: true, message: $gettext('Please input a filename') },
{ pattern: /^[^\\/]+$/, message: $gettext('Invalid filename') },
],
}"
>
<AFormItem
name="name"
:label="$gettext('Name')"
>
<AInput v-model:value="data.name" />
</AFormItem>
<AFormItem
v-if="!addMode"
:label="$gettext('Path')"
>
{{ data.filepath }}
</AFormItem>
<AFormItem
v-show="data.name !== origName"
:label="addMode ? $gettext('New Path') : $gettext('Changed Path')"
required
>
{{ newPath }}
</AFormItem>
<AFormItem
v-if="!addMode"
:label="$gettext('Updated at')"
>
{{ formatDateTime(modifiedAt) }}
</AFormItem>
</AForm>
</ACollapsePanel>
<ACollapsePanel
key="chatgpt"
header="ChatGPT"
>
<ChatGPT
v-model:history-messages="historyChatgptRecord"
:content="data.content"
:path="data.filepath"
/>
</ACollapsePanel>
</ACollapse>
</ACard>
</ACol>
</ARow>
</template>
<style lang="less" scoped>
.col-right {
position: sticky;
top: 78px;
:deep(.ant-card-body) {
max-height: 100vh;
overflow-y: scroll;
}
}
:deep(.ant-collapse-ghost > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box) {
padding: 0;
}
:deep(.ant-collapse > .ant-collapse-item > .ant-collapse-header) {
padding: 0 0 10px 0;
}
</style>