mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
feat: deploy site config to remote server
This commit is contained in:
parent
0bae0e0912
commit
4135146b67
16 changed files with 350 additions and 158 deletions
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
|
@ -63,6 +63,7 @@ declare module '@vue/runtime-core' {
|
|||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
BreadcrumbBreadcrumb: typeof import('./src/components/Breadcrumb/Breadcrumb.vue')['default']
|
||||
ChartAreaChart: typeof import('./src/components/Chart/AreaChart.vue')['default']
|
||||
ChartRadialBarChart: typeof import('./src/components/Chart/RadialBarChart.vue')['default']
|
||||
|
|
|
@ -185,93 +185,74 @@ async function regenerate(index: number) {
|
|||
|
||||
const editing_idx = ref(-1)
|
||||
|
||||
const show = computed(() => messages?.value?.length > 1)
|
||||
const show = computed(() => messages?.value?.length === 0)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card class="chatgpt" title="ChatGPT" v-if="show">
|
||||
<div class="chatgpt-container">
|
||||
<a-list
|
||||
class="chatgpt-log"
|
||||
item-layout="horizontal"
|
||||
:data-source="messages"
|
||||
>
|
||||
<template #renderItem="{ item, index }">
|
||||
<a-list-item>
|
||||
<a-comment :author="item.role" :avatar="item.avatar">
|
||||
<template #content>
|
||||
<div class="content" v-if="item.role==='assistant'||editing_idx!=index"
|
||||
v-html="marked.parse(item.content)"></div>
|
||||
<a-input style="padding: 0" v-else v-model:value="item.content"
|
||||
:bordered="false"/>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="chat-start" v-if="show">
|
||||
<a-button @click="send" :loading="loading">
|
||||
<Icon v-if="!loading" :component="ChatGPT_logo"/>
|
||||
{{ $gettext('Ask ChatGPT for Help') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="chatgpt-container" v-else>
|
||||
<a-list
|
||||
class="chatgpt-log"
|
||||
item-layout="horizontal"
|
||||
:data-source="messages"
|
||||
>
|
||||
<template #renderItem="{ item, index }">
|
||||
<a-list-item>
|
||||
<a-comment :author="item.role==='assistant'?$gettext('Assistant'):$gettext('User')">
|
||||
<template #content>
|
||||
<div class="content" v-if="item.role==='assistant'||editing_idx!=index"
|
||||
v-html="marked.parse(item.content)"></div>
|
||||
<a-input style="padding: 0" v-else v-model:value="item.content"
|
||||
:bordered="false"/>
|
||||
</template>
|
||||
<template #actions>
|
||||
<span v-if="item.role==='user'&&editing_idx!==index" @click="editing_idx=index">
|
||||
{{ $gettext('Modify') }}
|
||||
</span>
|
||||
<template v-else-if="editing_idx==index">
|
||||
<span @click="regenerate(index+1)">{{ $gettext('Save') }}</span>
|
||||
<span @click="editing_idx=-1">{{ $gettext('Cancel') }}</span>
|
||||
</template>
|
||||
<span v-else-if="!loading" @click="regenerate(index)" :disabled="loading">
|
||||
<template v-else-if="editing_idx==index">
|
||||
<span @click="regenerate(index+1)">{{ $gettext('Save') }}</span>
|
||||
<span @click="editing_idx=-1">{{ $gettext('Cancel') }}</span>
|
||||
</template>
|
||||
<span v-else-if="!loading" @click="regenerate(index)" :disabled="loading">
|
||||
{{ $gettext('Reload') }}
|
||||
</span>
|
||||
</template>
|
||||
</a-comment>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<div class="input-msg">
|
||||
<div class="control-btn">
|
||||
<a-space v-show="!loading">
|
||||
<a-popconfirm
|
||||
:cancelText="$gettext('No')"
|
||||
:okText="$gettext('OK')"
|
||||
:title="$gettext('Are you sure you want to clear the record of chat?')"
|
||||
@confirm="clear_record">
|
||||
<a-button type="text">{{ $gettext('Clear') }}</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button type="text" @click="regenerate(messages?.length-1)">
|
||||
{{ $gettext('Regenerate response') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-textarea auto-size v-model:value="ask_buffer"/>
|
||||
<div class="sned-btn">
|
||||
<a-button size="small" type="text" :loading="loading" @click="send">
|
||||
<send-outlined/>
|
||||
</template>
|
||||
</a-comment>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<div class="input-msg">
|
||||
<div class="control-btn">
|
||||
<a-space v-show="!loading">
|
||||
<a-popconfirm
|
||||
:cancelText="$gettext('No')"
|
||||
:okText="$gettext('OK')"
|
||||
:title="$gettext('Are you sure you want to clear the record of chat?')"
|
||||
@confirm="clear_record">
|
||||
<a-button type="text">{{ $gettext('Clear') }}</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button type="text" @click="regenerate(messages?.length-1)">
|
||||
{{ $gettext('Regenerate response') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-textarea auto-size v-model:value="ask_buffer"/>
|
||||
<div class="sned-btn">
|
||||
<a-button size="small" type="text" :loading="loading" @click="send">
|
||||
<send-outlined/>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<template v-else>
|
||||
<div class="chat-start">
|
||||
<a-button size="large" shape="circle" @click="send" :loading="loading">
|
||||
<Icon v-if="!loading" :component="ChatGPT_logo"/>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chatgpt {
|
||||
position: sticky;
|
||||
top: 78px;
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
max-height: 100vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-start {
|
||||
position: fixed !important;
|
||||
right: 36px;
|
||||
bottom: 78px;
|
||||
}
|
||||
|
||||
.chatgpt-container {
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
|
@ -285,6 +266,10 @@ const show = computed(() => messages?.value?.length > 1)
|
|||
}
|
||||
}
|
||||
|
||||
:deep(.ant-list-item) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-comment-content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -12,9 +12,8 @@ const settingsStore = useSettingsStore()
|
|||
const {environment} = storeToRefs(settingsStore)
|
||||
const router = useRouter()
|
||||
|
||||
function clear_env() {
|
||||
router.push('/dashboard')
|
||||
location.reload()
|
||||
async function clear_env() {
|
||||
await router.push('/dashboard')
|
||||
settingsStore.clear_environment()
|
||||
}
|
||||
|
||||
|
@ -24,8 +23,8 @@ const is_local = computed(() => {
|
|||
|
||||
const node_id = computed(() => environment.value.id)
|
||||
|
||||
watch(node_id, () => {
|
||||
router.push('/dashboard')
|
||||
watch(node_id, async () => {
|
||||
await router.push('/dashboard')
|
||||
location.reload()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@ import {useGettext} from 'vue3-gettext'
|
|||
|
||||
const {$gettext} = useGettext()
|
||||
|
||||
const props = defineProps(['target', 'map'])
|
||||
const props = defineProps(['target', 'map', 'hidden_local'])
|
||||
const emit = defineEmits(['update:target'])
|
||||
|
||||
const data = ref([])
|
||||
|
@ -35,15 +35,15 @@ const value = computed({
|
|||
|
||||
<template>
|
||||
<a-checkbox-group v-model:value="value" style="width: 100%">
|
||||
<a-row>
|
||||
<a-col :span="8">
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="8" v-if="!hidden_local">
|
||||
<a-checkbox :value="0">{{ $gettext('Local') }}</a-checkbox>
|
||||
<a-badge color="green"/>
|
||||
<a-tag color="blue">{{ $gettext('Online') }}</a-tag>
|
||||
</a-col>
|
||||
<a-col :span="8" v-for="node in data">
|
||||
<a-checkbox :value="node.id">{{ node.name }}</a-checkbox>
|
||||
<a-badge color="green" v-if="node.status"/>
|
||||
<a-badge color="red" v-else/>
|
||||
<a-tag color="blue" v-if="node.status">{{ $gettext('Online') }}</a-tag>
|
||||
<a-tag color="error" v-else>{{ $gettext('Offline') }}</a-tag>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
|
|
|
@ -65,15 +65,12 @@ function format_code() {
|
|||
})
|
||||
}
|
||||
|
||||
const editor_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 16 : 24)
|
||||
const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 24)
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :xs="24" :sm="24" :md="editor_md">
|
||||
<a-col :xs="24" :sm="24" :md="18">
|
||||
<a-card :title="$gettext('Edit Configuration')">
|
||||
<inspect-config ref="inspect_config"/>
|
||||
<code-editor v-model:content="configText"/>
|
||||
|
@ -93,9 +90,11 @@ const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 2
|
|||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col class="col-right" :xs="24" :sm="24" :md="chat_md">
|
||||
<chat-g-p-t :content="configText" :path="file_path"
|
||||
v-model:history_messages="history_chatgpt_record"/>
|
||||
<a-col class="col-right" :xs="24" :sm="24" :md="6">
|
||||
<a-card>
|
||||
<chat-g-p-t :content="configText" :path="file_path"
|
||||
v-model:history_messages="history_chatgpt_record"/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
|
|
@ -58,8 +58,10 @@ const visible = computed(() => {
|
|||
<a-tag color="blue" v-if="item.status">{{ $gettext('Online') }}</a-tag>
|
||||
<a-tag color="error" v-else>{{ $gettext('Offline') }}</a-tag>
|
||||
<div class="runtime-meta">
|
||||
<span><Icon :component="pulse"/> {{ formatDateTime(item.response_at) }}</span>
|
||||
<span><thunderbolt-outlined/>{{ item.version }}</span>
|
||||
<template v-if="item.status">
|
||||
<span><Icon :component="pulse"/> {{ formatDateTime(item.response_at) }}</span>
|
||||
<span><thunderbolt-outlined/>{{ item.version }}</span>
|
||||
</template>
|
||||
<span><link-outlined/>{{ item.url }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -85,7 +87,6 @@ const visible = computed(() => {
|
|||
|
||||
.runtime-meta {
|
||||
display: inline-flex;
|
||||
margin-left: 8px;
|
||||
|
||||
span {
|
||||
font-weight: 400;
|
||||
|
|
|
@ -8,10 +8,9 @@ import {computed, provide, reactive, ref, watch} from 'vue'
|
|||
import {useRoute, useRouter} from 'vue-router'
|
||||
import domain from '@/api/domain'
|
||||
import ngx from '@/api/ngx'
|
||||
import Modal from 'ant-design-vue/lib/modal'
|
||||
import {message} from 'ant-design-vue'
|
||||
import config from '@/api/config'
|
||||
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
|
||||
import RightSettings from '@/views/domain/components/RightSettings.vue'
|
||||
|
||||
const {$gettext, interpolate} = useGettext()
|
||||
|
||||
|
@ -42,6 +41,7 @@ const saving = ref(false)
|
|||
const filename = ref('')
|
||||
const parse_error_status = ref(false)
|
||||
const parse_error_message = ref('')
|
||||
const data = ref({})
|
||||
|
||||
init()
|
||||
|
||||
|
@ -74,6 +74,7 @@ function handle_response(r: any) {
|
|||
enabled.value = r.enabled
|
||||
auto_cert.value = r.auto_cert
|
||||
history_chatgpt_record.value = r.chatgpt_messages
|
||||
data.value = r
|
||||
Object.assign(ngx_config, r.tokenized)
|
||||
Object.assign(cert_info_map, r.cert_info)
|
||||
}
|
||||
|
@ -149,49 +150,18 @@ const save = async () => {
|
|||
})
|
||||
}
|
||||
|
||||
function enable() {
|
||||
domain.enable(name.value).then(() => {
|
||||
message.success($gettext('Enabled successfully'))
|
||||
enabled.value = true
|
||||
}).catch(r => {
|
||||
message.error(interpolate($gettext('Failed to enable %{msg}'), {msg: r.message ?? ''}), 10)
|
||||
})
|
||||
}
|
||||
|
||||
function disable() {
|
||||
domain.disable(name.value).then(() => {
|
||||
message.success($gettext('Disabled successfully'))
|
||||
enabled.value = false
|
||||
}).catch(r => {
|
||||
message.error(interpolate($gettext('Failed to disable %{msg}'), {msg: r.message ?? ''}))
|
||||
})
|
||||
}
|
||||
|
||||
function on_change_enabled(checked: boolean) {
|
||||
Modal.confirm({
|
||||
title: checked ? $gettext('Do you want to enable this site?') : $gettext('Do you want to disable this site?'),
|
||||
mask: false,
|
||||
centered: true,
|
||||
okText: $gettext('OK'),
|
||||
cancelText: $gettext('Cancel'),
|
||||
async onOk() {
|
||||
if (checked) {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const editor_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 16 : 24)
|
||||
const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 24)
|
||||
|
||||
provide('save_site_config', save)
|
||||
provide('configText', configText)
|
||||
provide('ngx_config', ngx_config)
|
||||
provide('history_chatgpt_record', history_chatgpt_record)
|
||||
provide('enabled', enabled)
|
||||
provide('name', name)
|
||||
provide('filename', filename)
|
||||
provide('data', data)
|
||||
</script>
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :xs="24" :sm="24" :md="editor_md">
|
||||
<a-col :xs="24" :sm="24" :md="18">
|
||||
<a-card :bordered="false">
|
||||
<template #title>
|
||||
<span style="margin-right: 10px">{{ interpolate($gettext('Edit %{n}'), {n: name}) }}</span>
|
||||
|
@ -217,13 +187,6 @@ provide('save_site_config', save)
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<a-form-item :label="$gettext('Enabled')">
|
||||
<a-switch :checked="enabled" @change="on_change_enabled"/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$gettext('Name')">
|
||||
<a-input v-model:value="filename"/>
|
||||
</a-form-item>
|
||||
|
||||
<transition name="slide-fade">
|
||||
<div v-if="advance_mode" key="advance">
|
||||
<div class="parse-error-alert-wrapper" v-if="parse_error_status">
|
||||
|
@ -252,9 +215,8 @@ provide('save_site_config', save)
|
|||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col class="col-right" :xs="24" :sm="24" :md="chat_md">
|
||||
<chat-g-p-t :content="configText" :path="ngx_config.file_name"
|
||||
v-model:history_messages="history_chatgpt_record"/>
|
||||
<a-col class="col-right" :xs="24" :sm="24" :md="6">
|
||||
<right-settings/>
|
||||
</a-col>
|
||||
|
||||
<footer-tool-bar>
|
||||
|
|
|
@ -7,7 +7,7 @@ import domain from '@/api/domain'
|
|||
import {Badge, message} from 'ant-design-vue'
|
||||
import {h, ref} from 'vue'
|
||||
import {input} from '@/components/StdDataEntry'
|
||||
import SiteDuplicate from '@/views/domain/SiteDuplicate.vue'
|
||||
import SiteDuplicate from '@/views/domain/components/SiteDuplicate.vue'
|
||||
|
||||
const {$gettext, interpolate} = useGettext()
|
||||
|
||||
|
|
108
frontend/src/views/domain/components/Deploy.vue
Normal file
108
frontend/src/views/domain/components/Deploy.vue
Normal file
|
@ -0,0 +1,108 @@
|
|||
<script setup lang="ts">
|
||||
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
|
||||
import {useGettext} from 'vue3-gettext'
|
||||
import {inject, reactive, ref} from 'vue'
|
||||
import {InfoCircleOutlined} from '@ant-design/icons-vue'
|
||||
import Modal from 'ant-design-vue/lib/modal'
|
||||
import domain from '@/api/domain'
|
||||
import {notification} from 'ant-design-vue'
|
||||
import template from '@/api/template'
|
||||
|
||||
const {$gettext, $ngettext} = useGettext()
|
||||
|
||||
const node_map = reactive({})
|
||||
const target = ref([])
|
||||
const overwrite = ref(false)
|
||||
const enabled = ref(false)
|
||||
const name = inject('name')
|
||||
|
||||
function deploy() {
|
||||
Modal.confirm({
|
||||
title: () => $ngettext('Do you want to deploy this file to remote server?',
|
||||
'Do you want to deploy this file to remote servers?', target.value.length),
|
||||
mask: false,
|
||||
centered: true,
|
||||
okText: $gettext('OK'),
|
||||
cancelText: $gettext('Cancel'),
|
||||
onOk() {
|
||||
target.value.forEach(id => {
|
||||
const node_name = node_map[id]
|
||||
// get source content
|
||||
domain.get(name.value).then(r => {
|
||||
domain.save(name.value, {
|
||||
name: name.value,
|
||||
content: r.config,
|
||||
overwrite: overwrite.value
|
||||
}, {headers: {'X-Node-ID': id}}).then(async () => {
|
||||
notification.success({
|
||||
message: $gettext('Deploy successfully'),
|
||||
description:
|
||||
$gettext('Deploy %{conf_name} to %{node_name} successfully',
|
||||
{conf_name: name.value, node_name: node_name})
|
||||
})
|
||||
if (enabled.value) {
|
||||
domain.enable(name.value).then(() => {
|
||||
notification.success({
|
||||
message: $gettext('Enable successfully'),
|
||||
description:
|
||||
$gettext(`Enable %{conf_name} in %{node_name} successfully`,
|
||||
{conf_name: name.value, node_name: node_name})
|
||||
})
|
||||
}).catch(e => {
|
||||
notification.error({
|
||||
message: $gettext('Enable %{conf_name} in %{node_name} failed', {
|
||||
conf_name: name.value,
|
||||
node_name: node_name
|
||||
}),
|
||||
description: $gettext(e?.message ?? 'Server error')
|
||||
})
|
||||
})
|
||||
}
|
||||
}).catch(e => {
|
||||
notification.error({
|
||||
message: $gettext('Deploy %{conf_name} to %{node_name} failed', {
|
||||
conf_name: name.value,
|
||||
node_name: node_name
|
||||
}),
|
||||
description: $gettext(e?.message ?? 'Server error')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<node-selector v-model:target="target" :hidden_local="true" :map="node_map"/>
|
||||
<div class="node-deploy-control">
|
||||
<a-checkbox v-model:checked="enabled">{{ $gettext('Enabled') }}</a-checkbox>
|
||||
<div class="overwrite">
|
||||
<a-checkbox v-model:checked="overwrite">{{ $gettext('Overwrite') }}</a-checkbox>
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ $gettext('Overwrite exist file') }}</template>
|
||||
<info-circle-outlined/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<a-button :disabled="target.length===0" type="primary" @click="deploy" ghost>{{ $gettext('Deploy') }}</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.overwrite {
|
||||
margin-right: 15px;
|
||||
|
||||
span {
|
||||
color: #9b9b9b;
|
||||
}
|
||||
}
|
||||
|
||||
.node-deploy-control {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
104
frontend/src/views/domain/components/RightSettings.vue
Normal file
104
frontend/src/views/domain/components/RightSettings.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<script setup lang="ts">
|
||||
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
|
||||
import {useGettext} from 'vue3-gettext'
|
||||
import {inject, ref} from 'vue'
|
||||
import Modal from 'ant-design-vue/lib/modal'
|
||||
import domain from '@/api/domain'
|
||||
import {message} from 'ant-design-vue'
|
||||
import {formatDateTime} from '@/lib/helper'
|
||||
import Deploy from '@/views/domain/components/Deploy.vue'
|
||||
import {useSettingsStore} from '@/pinia'
|
||||
|
||||
const settings = useSettingsStore()
|
||||
const {$gettext} = useGettext()
|
||||
const configText = inject('configText')
|
||||
const ngx_config = inject('ngx_config')
|
||||
const enabled = inject('enabled')
|
||||
const name = inject('name')
|
||||
const history_chatgpt_record = inject('history_chatgpt_record')
|
||||
const filename = inject('filename')
|
||||
const data: any = inject('data')
|
||||
|
||||
const active_key = ref('1')
|
||||
|
||||
function enable() {
|
||||
domain.enable(name.value).then(() => {
|
||||
message.success($gettext('Enabled successfully'))
|
||||
enabled.value = true
|
||||
}).catch(r => {
|
||||
message.error($gettext('Failed to enable %{msg}', {msg: r.message ?? ''}), 10)
|
||||
})
|
||||
}
|
||||
|
||||
function disable() {
|
||||
domain.disable(name.value).then(() => {
|
||||
message.success($gettext('Disabled successfully'))
|
||||
enabled.value = false
|
||||
}).catch(r => {
|
||||
message.error($gettext('Failed to disable %{msg}', {msg: r.message ?? ''}))
|
||||
})
|
||||
}
|
||||
|
||||
function on_change_enabled(checked: boolean) {
|
||||
Modal.confirm({
|
||||
title: checked ? $gettext('Do you want to enable this site?') : $gettext('Do you want to disable this site?'),
|
||||
mask: false,
|
||||
centered: true,
|
||||
okText: $gettext('OK'),
|
||||
cancelText: $gettext('Cancel'),
|
||||
async onOk() {
|
||||
if (checked) {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card class="right-settings">
|
||||
<a-collapse v-model:activeKey="active_key" ghost>
|
||||
<a-collapse-panel key="1" :header="$gettext('Basic')">
|
||||
<a-form-item :label="$gettext('Enabled')">
|
||||
<a-switch :checked="enabled" @change="on_change_enabled"/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$gettext('Name')">
|
||||
<a-input v-model:value="filename"/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$gettext('Updated at')">
|
||||
{{ formatDateTime(data.modified_at) }}
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="2" header="Deploy" v-if="!settings.is_remote">
|
||||
<deploy/>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="3" header="ChatGPT">
|
||||
<chat-g-p-t :content="configText" :path="ngx_config.file_name"
|
||||
v-model:history_messages="history_chatgpt_record"/>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.right-settings {
|
||||
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>
|
|
@ -1 +1 @@
|
|||
{"version":"1.9.9","build_id":115,"total_build":185}
|
||||
{"version":"1.9.9","build_id":116,"total_build":186}
|
16
go.mod
16
go.mod
|
@ -19,9 +19,10 @@ require (
|
|||
github.com/jpillora/overseer v1.1.6
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pretty66/websocketproxy v0.0.0-20220507015215-930b3a686308
|
||||
github.com/sashabaranov/go-openai v1.9.4
|
||||
github.com/shirou/gopsutil/v3 v3.23.4
|
||||
github.com/spf13/cast v1.5.0
|
||||
github.com/spf13/cast v1.5.1
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20230508164033-d7b72d6cd0d5
|
||||
github.com/unknwon/com v1.0.1
|
||||
go.uber.org/zap v1.24.0
|
||||
|
@ -50,10 +51,10 @@ require (
|
|||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.318 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.320 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.262 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.263 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.8.8 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
|
@ -139,7 +140,6 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/pquerna/otp v1.4.0 // indirect
|
||||
github.com/pretty66/websocketproxy v0.0.0-20220507015215-930b3a686308 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.7 // indirect
|
||||
github.com/sacloud/go-http v0.1.5 // indirect
|
||||
|
@ -154,8 +154,8 @@ require (
|
|||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.2 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.655 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.655 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.657 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.657 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/transip/gotransip/v6 v6.20.0 // indirect
|
||||
|
@ -164,8 +164,8 @@ require (
|
|||
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect
|
||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20230511103421-ecb0cd1514ab // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230511104317-0ccfef4d3a91 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20230515103554-c6064682c41e // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230515104003-a4cf880c2959 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
|
|
14
go.sum
14
go.sum
|
@ -70,6 +70,8 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.62.281 h1:sN94THxWQA+nPMDZD0esg1PGy6pm
|
|||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.281/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.318 h1:1ntKWopst53IVWKlEVrgutJpEgQN+FyNZXO+h6ePgXw=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.318/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.320 h1:KiT5EgbU/rxcx4wiH1sFKaR3KyzGqB89D7JSN1vH40A=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.320/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
|
@ -84,6 +86,8 @@ github.com/aws/aws-sdk-go v1.44.242 h1:bb6Rqd7dxh1gTUoVXLJTNC2c+zNaHpLRlNKk0kGN3
|
|||
github.com/aws/aws-sdk-go v1.44.242/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I=
|
||||
github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go v1.44.263 h1:Dkt5fcdtL8QtK3cz0bOTQ84m9dGx+YDeTsDl+wY2yW4=
|
||||
github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
|
@ -702,6 +706,8 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
|
|||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
|
@ -732,10 +738,14 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.637 h1:qFqi
|
|||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.637/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.655 h1:wXBlXLfBbqTBpsiKBBULW63KvMy3wsu3/CD25cR9NEA=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.655/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.657 h1:daDlYUdKRzgi2PxIcXj4vU1enWs6aqrL7K5qD3fKpmo=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.657/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.637 h1:9r85LEYF4CcKDbQQhJ5b3hYh5vj1WNvjsHrWHAV3c60=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.637/go.mod h1:5z3RG36i3UQvMr3aHVjPfrEzLdmk+sTiLgip3aFvKBo=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.655 h1:LgLA3nzvsBggdt1NRDNi6KVk9HRHLwBUltxXupdRMeM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.655/go.mod h1:v8wyOnL22mqDNeBqsasAQzP6eQI0Lpa+cAxFtVThHTk=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.657 h1:9FAIqzmy29PS+CVlec2LaTbwSg0j8Zk55GxPMjrZUqM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.657/go.mod h1:O2Xg2eAwl+TLAso+0F7Iao9ru2Abf7Mj+Dgv++pvQFw=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
|
@ -776,10 +786,14 @@ github.com/yandex-cloud/go-genproto v0.0.0-20230410092700-15216dc82345 h1:GpSllt
|
|||
github.com/yandex-cloud/go-genproto v0.0.0-20230410092700-15216dc82345/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20230511103421-ecb0cd1514ab h1:Y9sWstUXfHwHufw95mI58ZEvZ720KWyR+niLQbd2q1k=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20230511103421-ecb0cd1514ab/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20230515103554-c6064682c41e h1:2jQIfPgSk+/6zl44MEYLehRLRtGRGYvVyfKLXeNFGUM=
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20230515103554-c6064682c41e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230403093608-cc5174142a48 h1:C3yjOqP3gGxwiW3bXDAGI8tS+eKjxySJ9Ix7lpdtKZw=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230403093608-cc5174142a48/go.mod h1:+bvtdW+7bn1Yc7xUCbITnEalQ+hwkAAbUFHpeIY2wUQ=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230511104317-0ccfef4d3a91 h1:bYY90Y33XH7xJh8Qa5ZIgmjyWDp2S6sixTRxYbHCQLU=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230511104317-0ccfef4d3a91/go.mod h1:QOnqdE3DjwgoKvhw4Scx6HTCfAlYHZMoUzyaC8kcdzk=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230515104003-a4cf880c2959 h1:jFoq7f55et+ssQYFgZCWbUV2kUcDx22e1jZx+KhnBcU=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20230515104003-a4cf880c2959/go.mod h1:JNuOWjkSmssXYwJXPpOxc3IhjWbPKkODDm1gAqPFD9Q=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/query"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -88,8 +89,16 @@ func GetDomain(c *gin.Context) {
|
|||
}
|
||||
|
||||
path := nginx.GetConfPath("sites-available", name)
|
||||
file, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": "file not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
enabled := true
|
||||
|
||||
if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) {
|
||||
enabled = false
|
||||
}
|
||||
|
@ -102,6 +111,10 @@ func GetDomain(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if chatgpt.Content == nil {
|
||||
chatgpt.Content = make([]openai.ChatCompletionMessage, 0)
|
||||
}
|
||||
|
||||
s := query.Site
|
||||
site, err := s.Where(s.Path.Eq(path)).FirstOrInit()
|
||||
|
||||
|
@ -110,7 +123,11 @@ func GetDomain(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
certModel, _ := model.FirstCert(name)
|
||||
certModel, err := model.FirstCert(name)
|
||||
|
||||
if err != nil {
|
||||
logger.Warn("cert", err)
|
||||
}
|
||||
|
||||
if site.Advanced {
|
||||
origContent, err := os.ReadFile(path)
|
||||
|
@ -120,6 +137,7 @@ func GetDomain(c *gin.Context) {
|
|||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"modified_at": file.ModTime(),
|
||||
"advanced": site.Advanced,
|
||||
"enabled": enabled,
|
||||
"name": name,
|
||||
|
@ -167,6 +185,7 @@ func GetDomain(c *gin.Context) {
|
|||
c.Set("maybe_error", "nginx_config_syntax_error")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"modified_at": file.ModTime(),
|
||||
"advanced": site.Advanced,
|
||||
"enabled": enabled,
|
||||
"name": name,
|
||||
|
@ -253,7 +272,7 @@ func SaveDomain(c *gin.Context) {
|
|||
if helper.FileExists(enabledConfigFilePath) {
|
||||
// Test nginx configuration
|
||||
output := nginx.TestConf()
|
||||
if nginx.GetLogLevel(output) >= nginx.Warn {
|
||||
if nginx.GetLogLevel(output) > nginx.Warn {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
"error": "nginx_config_syntax_error",
|
||||
|
@ -263,7 +282,7 @@ func SaveDomain(c *gin.Context) {
|
|||
|
||||
output = nginx.Reload()
|
||||
|
||||
if nginx.GetLogLevel(output) >= nginx.Warn {
|
||||
if nginx.GetLogLevel(output) > nginx.Warn {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
|
|
|
@ -17,7 +17,7 @@ func (j *JSON) Scan(value interface{}) error {
|
|||
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
|
||||
}
|
||||
|
||||
var result []openai.ChatCompletionMessage
|
||||
result := make([]openai.ChatCompletionMessage, 0)
|
||||
err := json.Unmarshal(bytes, &result)
|
||||
*j = result
|
||||
return err
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue