[frontend-next] Refactored NgxConfigEditor

This commit is contained in:
0xJacky 2022-08-01 14:32:17 +08:00
parent 7681c53bde
commit 0d4b126dd4
13 changed files with 485 additions and 490 deletions

View file

@ -15,9 +15,11 @@ declare module '@vue/runtime-core' {
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
ADivider: typeof import('ant-design-vue/es')['Divider'] ADivider: typeof import('ant-design-vue/es')['Divider']
ADrawer: typeof import('ant-design-vue/es')['Drawer'] ADrawer: typeof import('ant-design-vue/es')['Drawer']
AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form'] AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem'] AFormItem: typeof import('ant-design-vue/es')['FormItem']
AInput: typeof import('ant-design-vue/es')['Input'] AInput: typeof import('ant-design-vue/es')['Input']
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
ALayout: typeof import('ant-design-vue/es')['Layout'] ALayout: typeof import('ant-design-vue/es')['Layout']
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
@ -38,7 +40,10 @@ declare module '@vue/runtime-core' {
ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ASwitch: typeof import('ant-design-vue/es')['Switch'] ASwitch: typeof import('ant-design-vue/es')['Switch']
ATable: typeof import('ant-design-vue/es')['Table'] ATable: typeof import('ant-design-vue/es')['Table']
ATabPane: typeof import('ant-design-vue/es')['TabPane']
ATabs: typeof import('ant-design-vue/es')['Tabs']
ATag: typeof import('ant-design-vue/es')['Tag'] ATag: typeof import('ant-design-vue/es')['Tag']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
Breadcrumb: typeof import('./src/components/Breadcrumb/Breadcrumb.vue')['default'] Breadcrumb: typeof import('./src/components/Breadcrumb/Breadcrumb.vue')['default']
CodeEditor: typeof import('./src/components/CodeEditor/CodeEditor.vue')['default'] CodeEditor: typeof import('./src/components/CodeEditor/CodeEditor.vue')['default']
FooterToolBar: typeof import('./src/components/FooterToolbar/FooterToolBar.vue')['default'] FooterToolBar: typeof import('./src/components/FooterToolbar/FooterToolBar.vue')['default']

View file

@ -3,7 +3,10 @@ import {VAceEditor} from 'vue3-ace-editor'
import 'ace-builds/src-noconflict/mode-nginx' import 'ace-builds/src-noconflict/mode-nginx'
import 'ace-builds/src-noconflict/theme-monokai' import 'ace-builds/src-noconflict/theme-monokai'
const {content} = defineProps(['content']) const {content, defaultHeight} = defineProps<{
content?: string
defaultHeight?: string
}>()
</script> </script>
<template> <template>
@ -11,7 +14,9 @@ const {content} = defineProps(['content'])
v-model:value="content" v-model:value="content"
lang="nginx" lang="nginx"
theme="monokai" theme="monokai"
style="min-height: 100vh"/> :style="{
minHeight: defaultHeight || '100vh'
}"/>
</template> </template>
<style scoped> <style scoped>

View file

@ -0,0 +1,3 @@
import CodeEditor from './CodeEditor'
export default CodeEditor

View file

@ -2,7 +2,7 @@
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue' import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
import CodeEditor from '@/components/CodeEditor/CodeEditor.vue' import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
// import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor' import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor'
import {useGettext} from 'vue3-gettext' import {useGettext} from 'vue3-gettext'
import {reactive, ref} from 'vue' import {reactive, ref} from 'vue'
import {useRoute} from 'vue-router' import {useRoute} from 'vue-router'
@ -17,6 +17,7 @@ const route = useRoute()
const name = ref(route.params.name.toString()) const name = ref(route.params.name.toString())
const update = ref(0) const update = ref(0)
const ngx_config = reactive({ const ngx_config = reactive({
filename: '', filename: '',
upstreams: [], upstreams: [],
@ -141,13 +142,12 @@ function disable() {
<a-form-item :label="$gettext('Enabled')"> <a-form-item :label="$gettext('Enabled')">
<a-switch v-model="enabled" @change="checked=>{checked?enable():disable()}"/> <a-switch v-model="enabled" @change="checked=>{checked?enable():disable()}"/>
</a-form-item> </a-form-item>
<ngx-config-editor
<!-- <ngx-config-editor--> ref="ngx_config_editor"
<!-- ref="ngx_config"--> :ngx_config="ngx_config"
<!-- :ngx_config="ngx_config"--> v-model:auto_cert="auto_cert"
<!-- v-model="auto_cert"--> :enabled="enabled"
<!-- :enabled="enabled"--> />
<!-- />-->
</div> </div>
</transition> </transition>

View file

@ -1,61 +1,48 @@
<script setup lang="ts">
import {CloseCircleOutlined, CheckCircleOutlined} from '@ant-design/icons-vue'
import dayjs from 'dayjs'
import {defineProps, reactive, ref} from 'vue'
import domain from '@/api/domain'
const props = defineProps(['domain'])
const ok = ref(false)
const cert = reactive({issuer_name: '', subject_name: '', not_after: '', not_before: ''})
get()
function get() {
domain.cert_info(props.domain).then((r: any) => {
Object.assign(cert, r)
ok.value = true
}).catch(() => {
ok.value = false
})
}
</script>
<template> <template>
<div class="cert-info" v-if="ok"> <div class="cert-info" v-if="ok">
<h4 v-translate>Certificate Status</h4> <h2 v-translate>Certificate Status</h2>
<p v-translate="{issuer: cert.issuer_name}">Intermediate Certification Authorities: %{issuer}</p> <p v-translate="{issuer: cert.issuer_name}">Intermediate Certification Authorities: %{issuer}</p>
<p v-translate="{name: cert.subject_name}">Subject Name: %{name}</p> <p v-translate="{name: cert.subject_name}">Subject Name: %{name}</p>
<p v-translate="{date: moment(cert.not_after).format('YYYY-MM-DD HH:mm:ss').toString()}"> <p v-translate="{date: dayjs(cert.not_after).format('YYYY-MM-DD HH:mm:ss').toString()}">
Expiration Date: %{date}</p> Expiration Date: %{date}</p>
<p v-translate="{date: moment(cert.not_before).format('YYYY-MM-DD HH:mm:ss').toString()}"> <p v-translate="{date: dayjs(cert.not_before).format('YYYY-MM-DD HH:mm:ss').toString()}">
Not Valid Before: %{date}</p> Not Valid Before: %{date}</p>
<div class="status"> <div class="status">
<template v-if="new Date().toISOString() < cert.not_before || new Date().toISOString() > cert.not_after"> <template v-if="new Date().toISOString() < cert.not_before || new Date().toISOString() > cert.not_after">
<a-icon :style="{ color: 'red' }" type="close-circle"/> <close-circle-outlined style="color: red"/>
<span v-translate>Certificate has expired</span> <span v-translate>Certificate has expired</span>
</template> </template>
<template v-else> <template v-else>
<a-icon :style="{ color: 'green' }" type="check-circle"/> <check-circle-outlined style="color: green"/>
<span v-translate>Certificate is valid</span> <span v-translate>Certificate is valid</span>
</template> </template>
</div> </div>
</div> </div>
</template> </template>
<script>
import moment from 'moment'
export default {
name: 'CertInfo',
data() {
return {
ok: false,
cert: {},
moment
}
},
props: {
domain: String
},
created() {
this.get()
},
watch: {
domain() {
this.get()
}
},
methods: {
get() {
this.$api.domain.cert_info(this.domain).then(r => {
this.cert = r
this.ok = true
}).catch(() => {
this.ok = false
})
}
}
}
</script>
<style lang="less" scoped> <style lang="less" scoped>
h4 { h4 {
padding-bottom: 10px; padding-bottom: 10px;
@ -67,7 +54,7 @@ h4 {
.status { .status {
span { span {
margin-left: 10px; margin-right: 10px;
} }
} }

View file

@ -1,75 +1,69 @@
<template> <script setup lang="ts">
<a-form-item :label="$gettext('Locations')" :key="update"> import CodeEditor from '@/components/CodeEditor'
<a-empty v-if="!locations"/> import {useGettext} from 'vue3-gettext'
<a-card v-for="(v,k) in locations" :key="k" import {reactive} from 'vue'
:title="$gettext('Location')" size="small">
<a-form-item :label="$gettext('Comments')" v-if="v.comments">
<p style="white-space: pre-wrap;">{{ v.comments }}</p>
</a-form-item>
<a-form-item :label="$gettext('Path')">
<a-input addon-before="location" v-model="v.path"/>
</a-form-item>
<a-form-item :label="$gettext('Content')">
<vue-itextarea v-model="v.content" :default-text-height="200"/>
</a-form-item>
</a-card>
<a-modal :title="$gettext('Add Location')" v-model="adding" @ok="save"> const {$gettext} = useGettext()
<a-form-item :label="$gettext('Comments')">
<a-textarea v-model="location.comments"></a-textarea>
</a-form-item>
<a-form-item :label="$gettext('Path')">
<a-input addon-before="location" v-model="location.path"/>
</a-form-item>
<a-form-item :label="$gettext('Content')">
<vue-itextarea v-model="location.content" :default-text-height="200"/>
</a-form-item>
</a-modal>
<div> const {locations} = defineProps<{
<a-button block @click="add">{{ $gettext('Add Location') }}</a-button> locations?: any[]
</div> }>()
</a-form-item>
</template>
<script> let location = reactive({})
import VueItextarea from '@/components/VueItextarea/VueItextarea'
export default {
name: 'LocationEditor', function add() {
components: {VueItextarea}, adding.value = true
props: { location = reactive({})
locations: Array }
},
data() { function save() {
return { this.adding = false
adding: false, if (this.locations) {
location: {}, this.locations.push(this.location)
update: 0 } else {
} this.locations = [this.location]
},
methods: {
add() {
this.adding = true
this.location = {}
},
save() {
this.adding = false
if (this.locations) {
this.locations.push(this.location)
} else {
this.locations = [this.location]
}
this.update++
},
remove(index) {
this.update++
this.locations.splice(index, 1)
}
} }
} }
function remove(index) {
this.locations.splice(index, 1)
}
</script> </script>
<template>
<h2 v-translate>Locations</h2>
<a-empty v-if="!locations"/>
<a-card v-for="(v,k) in locations" :key="k"
:title="$gettext('Location')" size="small">
<a-form-item :label="$gettext('Comments')" v-if="v.comments">
<p style="white-space: pre-wrap;">{{ v.comments }}</p>
</a-form-item>
<a-form-item :label="$gettext('Path')">
<a-input addon-before="location" v-model="v.path"/>
</a-form-item>
<a-form-item :label="$gettext('Content')">
<code-editor v-model:content="v.content" default-height="200px"/>
</a-form-item>
</a-card>
<a-modal :title="$gettext('Add Location')" v-model:visible="adding" @ok="save">
<a-form-item :label="$gettext('Comments')">
<a-textarea v-model="location.comments"></a-textarea>
</a-form-item>
<a-form-item :label="$gettext('Path')">
<a-input addon-before="location" v-model="location.path"/>
</a-form-item>
<a-form-item :label="$gettext('Content')">
<vue-itextarea v-model:content="location.content" default-height="200px"/>
</a-form-item>
</a-modal>
<div>
<a-button block @click="add">{{ $gettext('Add Location') }}</a-button>
</div>
</template>
<style lang="less" scoped> <style lang="less" scoped>
.ant-card { .ant-card {
margin: 10px 0; margin: 10px 0;

View file

@ -1,30 +1,158 @@
<script setup lang="ts">
import CertInfo from '@/views/domain/cert/CertInfo'
// import IssueCert from '@/views/domain/cert/IssueCert'
import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor'
import LocationEditor from '@/views/domain/ngx_conf/LocationEditor'
import {computed, ref} from 'vue'
import {useRoute} from 'vue-router'
import {useGettext} from 'vue3-gettext'
const {$gettext} = useGettext()
const {ngx_config, auto_cert, enabled} = defineProps(['ngx_config', 'auto_cert', 'enabled'])
const route = useRoute()
const current_server_index = ref(0)
const name = ref(route.params.name)
const init_ssl_status = ref(false)
function update_cert_info() {
// if (name.value && this.$refs['cert-info' + this.current_server_index]) {
// this.$refs['cert-info' + this.current_server_index].get()
// }
}
function change_tls(r: any) {
if (r) {
// deep copy servers[0] to servers[1]
const server = JSON.parse(JSON.stringify(ngx_config.servers[0]))
ngx_config.servers.push(server)
current_server_index.value = 1
const servers = ngx_config.servers
let i = 0
while (i < servers[1].directives.length) {
const v = servers[1].directives[i]
if (v.directive === 'listen') {
servers[1].directives.splice(i, 1)
} else {
i++
}
}
servers[1].directives.splice(0, 0, {
directive: 'listen',
params: '443 ssl http2'
}, {
directive: 'listen',
params: '[::]:443 ssl http2'
})
const server_name = directivesMap.value['server_name'][0]
if (!directivesMap.value['ssl_certificate']) {
servers[1].directives.splice(server_name.idx + 1, 0, {
directive: 'ssl_certificate',
params: ''
})
}
setTimeout(() => {
if (!directivesMap.value['ssl_certificate_key']) {
servers[1].directives.splice(server_name.idx + 2, 0, {
directive: 'ssl_certificate_key',
params: ''
})
}
}, 100)
} else {
// remove servers[1]
current_server_index.value = 0
if (ngx_config.servers.length === 2) {
ngx_config.servers.splice(1, 1)
}
}
}
const current_server_directives = computed(() => {
return ngx_config.servers[current_server_index.value].directives
})
const directivesMap = computed(() => {
const map = <any>{}
current_server_directives.value.forEach((v: any, k: any) => {
v.idx = k
if (map[v.directive]) {
map[v.directive].push(v)
} else {
map[v.directive] = [v]
}
})
return map
})
const support_ssl = computed(() => {
const servers = ngx_config.servers
for (const server_key in servers) {
for (const k in servers[server_key].directives) {
const v = servers[server_key].directives[k]
if (v.directive === 'listen' && v.params.indexOf('ssl') > 0) {
return true
}
}
}
return false
})
const current_support_ssl = computed(() => {
if (directivesMap.value.listen) {
for (const v of directivesMap.value.listen) {
if (v?.params.indexOf('ssl') > 0) {
return true
}
}
}
return false
})
</script>
<template> <template>
<div> <div>
<a-form-item :label="$gettext('Enable TLS')" v-if="!support_ssl"> <a-form-item :label="$gettext('Enable TLS')" v-if="!support_ssl">
<a-switch @change="change_tls"/> <a-switch @change="change_tls"/>
</a-form-item> </a-form-item>
<a-tabs v-model="current_server_index"> <a-tabs v-model:activeKey="current_server_index">
<a-tab-pane :tab="'Server '+(k+1)" v-for="(v,k) in ngx_config.servers" :key="k"> <a-tab-pane :tab="'Server '+(k+1)" v-for="(v,k) in ngx_config.servers" :key="k">
<div class="tab-content"> <div class="tab-content">
<template v-if="current_support_ssl&&enabled"> <template v-if="current_support_ssl&&enabled">
<cert-info :domain="name" v-if="name"/> <cert-info :domain="name" v-if="current_support_ssl"/>
<issue-cert <!-- <issue-cert-->
:current_server_directives="current_server_directives" <!-- :current_server_directives="current_server_directives"-->
:directives-map="directivesMap" <!-- :directives-map="directivesMap"-->
v-model="auto_cert" <!-- v-model="auto_cert"-->
/> <!-- />-->
<cert-info :current_server_directives="current_server_directives"
:directives-map="directivesMap"
v-model="auto_cert"/>
</template> </template>
<a-form-item :label="$gettext('Comments')" v-if="v.comments"> <template v-if="v.comments">
<h3 v-translate>Comments</h3>
<p style="white-space: pre-wrap;">{{ v.comments }}</p> <p style="white-space: pre-wrap;">{{ v.comments }}</p>
</a-form-item> </template>
<directive-editor :ngx_directives="v.directives" :key="update"/> <directive-editor :ngx_directives="v.directives"/>
<location-editor :locations="v.locations"/> <location-editor :locations="v.locations"/>
</div> </div>
@ -35,147 +163,6 @@
</template> </template>
<script>
import CertInfo from '@/views/domain/cert/CertInfo'
import IssueCert from '@/views/domain/cert/IssueCert'
import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor'
import LocationEditor from '@/views/domain/ngx_conf/LocationEditor'
export default {
name: 'NgxConfigEditor',
components: {LocationEditor, DirectiveEditor, IssueCert, CertInfo},
props: {
ngx_config: Object,
auto_cert: Boolean,
enabled: Boolean
},
data() {
return {
current_server_index: 0,
update: 0,
name: this.$route.params?.name?.toString() ?? '',
init_ssl_status: false
}
},
model: {
prop: 'auto_cert',
event: 'change_auto_cert'
},
methods: {
update_cert_info() {
if (this.name && this.$refs['cert-info' + this.current_server_index]) {
this.$refs['cert-info' + this.current_server_index].get()
}
},
change_tls(r) {
if (r) {
// deep copy servers[0] to servers[1]
const server = JSON.parse(JSON.stringify(this.ngx_config.servers[0]))
this.ngx_config.servers.push(server)
this.current_server_index = 1
const servers = this.ngx_config.servers
let i = 0
while (i < servers[1].directives.length) {
const v = servers[1].directives[i]
if (v.directive === 'listen') {
servers[1].directives.splice(i, 1)
} else {
i++
}
}
servers[1].directives.splice(0, 0, {
directive: 'listen',
params: '443 ssl http2'
}, {
directive: 'listen',
params: '[::]:443 ssl http2'
})
const directivesMap = this.directivesMap
const server_name = directivesMap['server_name'][0]
if (!directivesMap['ssl_certificate']) {
servers[1].directives.splice(server_name.idx + 1, 0, {
directive: 'ssl_certificate',
params: ''
})
}
setTimeout(() => {
if (!directivesMap['ssl_certificate_key']) {
servers[1].directives.splice(server_name.idx + 2, 0, {
directive: 'ssl_certificate_key',
params: ''
})
}
}, 100)
} else {
// remove servers[1]
this.current_server_index = 0
if (this.ngx_config.servers.length === 2) {
this.ngx_config.servers.splice(1, 1)
}
}
},
},
computed: {
directivesMap: {
get() {
const map = {}
this.current_server_directives.forEach((v, k) => {
v.idx = k
if (map[v.directive]) {
map[v.directive].push(v)
} else {
map[v.directive] = [v]
}
})
return map
}
},
current_server_directives: {
get() {
return this.ngx_config.servers[this.current_server_index].directives
}
},
support_ssl() {
const servers = this.ngx_config.servers
for (const server_key in servers) {
for (const k in servers[server_key].directives) {
const v = servers[server_key].directives[k]
if (v.directive === 'listen' && v.params.indexOf('ssl') > 0) {
return true
}
}
}
return false
},
current_support_ssl: {
get() {
if (this.directivesMap.listen) {
for (const v of this.directivesMap.listen) {
if (v?.params.indexOf('ssl') > 0) {
return true
}
}
}
return false
}
},
}
}
</script>
<style scoped> <style scoped>
</style> </style>

View file

@ -1,22 +1,69 @@
<script setup lang="ts">
import {If} from '@/views/domain/ngx_conf'
import CodeEditor from '@/components/CodeEditor'
import {reactive, ref} from 'vue'
import {useGettext} from 'vue3-gettext'
import {CloseOutlined} from '@ant-design/icons-vue'
const {$gettext} = useGettext()
const emit = defineEmits(['save'])
const {ngx_directives, idx} = defineProps(['ngx_directives', 'idx'])
let directive = reactive({directive: '', params: ''})
const adding = ref(false)
const mode = ref('default')
function add() {
adding.value = true
directive = reactive({directive: '', params: ''})
}
function save() {
adding.value = false
if (mode.value === If) {
directive.directive = If
}
if (idx) {
ngx_directives.splice(idx + 1, 0, directive)
} else {
ngx_directives.push(directive)
}
emit('save', idx)
}
</script>
<template> <template>
<div> <div>
<div class="add-directive-temp" v-if="adding"> <div class="add-directive-temp" v-if="adding">
<a-select v-model="mode" default-value="default" style="min-width: 150px"> <a-form-item>
<a-select-option value="default"> <a-select v-model:value="mode" default-value="default" style="width: 150px">
{{ $gettext('Single Directive') }} <a-select-option value="default">
</a-select-option> {{ $gettext('Single Directive') }}
<a-select-option value="if"> </a-select-option>
if <a-select-option value="if">
</a-select-option> if
</a-select> </a-select-option>
<vue-itextarea v-if="mode===If" :default-text-height="100" v-model="directive.params"/> </a-select>
<a-input-group compact v-else> </a-form-item>
<a-input style="width: 30%" :placeholder="$gettext('Directive')" v-model="directive.directive"/> <a-form-item>
<a-input style="width: 70%" :placeholder="$gettext('Params')" v-model="directive.params"> <code-editor v-if="mode===If" default-height="100px" v-model:content="directive.params"/>
<a-icon slot="suffix" type="close" style="color: rgba(0,0,0,.45);font-size: 10px;"
@click="adding=false"/> <a-input-group compact v-else>
</a-input>
</a-input-group> <a-input style="width: 30%" :placeholder="$gettext('Directive')" v-model="directive.directive"/>
<a-input style="width: 70%" :placeholder="$gettext('Params')" v-model="directive.params">
<template #suffix>
<CloseOutlined @click="adding=false" style="color: rgba(0,0,0,.45);font-size: 10px;"/>
</template>
</a-input>
</a-input-group>
</a-form-item>
</div> </div>
<a-button block v-if="!adding" @click="add">{{ $gettext('Add Directive Below') }}</a-button> <a-button block v-if="!adding" @click="add">{{ $gettext('Add Directive Below') }}</a-button>
<a-button type="primary" v-else block @click="save" <a-button type="primary" v-else block @click="save"
@ -25,50 +72,6 @@
</div> </div>
</template> </template>
<script>
import {If} from '@/views/domain/ngx_conf/ngx_constant'
import VueItextarea from '@/components/VueItextarea/VueItextarea'
export default {
name: 'DirectiveAdd',
components: {
VueItextarea
},
props: {
ngx_directives: Array,
idx: Number,
},
data() {
return {
adding: false,
directive: {},
mode: 'default',
If
}
},
methods: {
add() {
this.adding = true
this.directive = {}
},
save() {
this.adding = false
if (this.mode === If) {
this.directive.directive = If
}
if (this.idx) {
this.ngx_directives.splice(this.idx + 1, 0, this.directive)
} else {
this.ngx_directives.push(this.directive)
}
this.$emit('save', this.idx)
}
}
}
</script>
<style lang="less" scoped> <style lang="less" scoped>
</style> </style>

View file

@ -1,76 +1,78 @@
<script setup lang="ts">
import CodeEditor from '@/components/CodeEditor'
import {If} from '@/views/domain/ngx_conf'
import DirectiveAdd from '@/views/domain/ngx_conf/directive/DirectiveAdd'
import {useGettext} from 'vue3-gettext'
import {reactive, ref} from 'vue'
import {CloseOutlined} from '@ant-design/icons-vue'
const {$gettext} = useGettext()
const {ngx_directives} = defineProps<{
ngx_directives: any[]
}>()
const adding = ref(false)
let directive = reactive({})
const current_idx = ref(-1)
function add() {
adding.value = true
directive = reactive({})
}
function save() {
adding.value = false
ngx_directives.push(directive)
}
function remove(index: number) {
ngx_directives.splice(index, 1)
}
function onSave(idx: number) {
setTimeout(() => {
current_idx.value = idx + 1
}, 50)
}
</script>
<template> <template>
<a-form-item :label="$gettext('Directives')"> <h2>{{ $gettext('Directives') }}</h2>
<div v-for="(directive,k) in ngx_directives" :key="k" @click="current_idx=k">
<vue-itextarea v-if="directive.directive === If" v-model="directive.params" :default-text-height="100"/> <a-form-item v-for="(directive,index) in ngx_directives" @click="current_idx=index">
<a-input :addon-before="directive.directive" v-model="directive.params" @click="current_idx=k" v-else> <code-editor v-if="directive.directive === If" v-model:content="directive.params"
<a-popconfirm slot="suffix" @confirm="remove(k)" defaultHeight="100px"/>
<a-input :addon-before="directive.directive" v-model:value="directive.params" @click="current_idx=k"
v-else>
<template #suffix>
<a-popconfirm @confirm="remove(index)"
:title="$gettext('Are you sure you want to remove this directive?')" :title="$gettext('Are you sure you want to remove this directive?')"
:ok-text="$gettext('Yes')" :ok-text="$gettext('Yes')"
:cancel-text="$gettext('No')"> :cancel-text="$gettext('No')">
<a-icon type="close" <CloseOutlined style="color: rgba(0,0,0,.45);font-size: 10px;"/>
style="color: rgba(0,0,0,.45);font-size: 10px;"
/>
</a-popconfirm> </a-popconfirm>
</a-input> </template>
<transition name="slide"> </a-input>
<div v-if="current_idx===k" class="extra"> <transition name="slide">
<div class="extra-content"> <div v-if="current_idx===index" class="extra">
<div class="extra-content">
<a-form layout="vertical">
<a-form-item :label="$gettext('Comments')"> <a-form-item :label="$gettext('Comments')">
<a-textarea v-model="directive.comments"/> <a-textarea v-model="directive.comments"/>
</a-form-item> </a-form-item>
<directive-add :ngx_directives="ngx_directives" :idx="k" @save="onSave(k)"/> </a-form>
</div> <directive-add :ngx_directives="ngx_directives" :idx="index" @save="onSave(index)"/>
</div> </div>
</transition> </div>
</div> </transition>
<directive-add :ngx_directives="ngx_directives"/>
</a-form-item> </a-form-item>
<directive-add :ngx_directives="ngx_directives"/>
</template> </template>
<script>
import VueItextarea from '@/components/VueItextarea/VueItextarea'
import {If} from '../ngx_constant'
import DirectiveAdd from '@/views/domain/ngx_conf/directive/DirectiveAdd'
export default {
name: 'DirectiveEditor',
props: {
ngx_directives: Array
},
components: {
DirectiveAdd,
VueItextarea
},
data() {
return {
adding: false,
directive: {},
If,
current_idx: -1,
}
},
methods: {
add() {
this.adding = true
this.directive = {}
},
save() {
this.adding = false
this.ngx_directives.push(this.directive)
},
remove(index) {
this.ngx_directives.splice(index, 1)
},
onSave(idx) {
const that = this
setTimeout(() => {
that.current_idx = idx + 1
}, 50)
}
}
}
</script>
<style lang="less" scoped> <style lang="less" scoped>
.extra { .extra {
background-color: #fafafa; background-color: #fafafa;
@ -79,14 +81,14 @@ export default {
} }
.slide-enter-active, .slide-leave-active { .slide-enter-active, .slide-leave-active {
transition: max-height .5s ease; transition: max-height .3s ease;
} }
.slide-enter, .slide-leave-to { .slide-enter-from, .slide-leave-to {
max-height: 0; max-height: 0;
} }
.slide-enter-to, .slide-leave { .slide-enter-to, .slide-leave-from {
max-height: 600px; max-height: 600px;
} }
</style> </style>

View file

@ -1 +1 @@
{"version":"1.5.0","build_id":6,"total_build":76} {"version":"1.5.0","build_id":8,"total_build":78}

View file

@ -72,4 +72,7 @@ export default defineConfig({
}, },
}, },
}, },
build: {
chunkSizeWarningLimit: 600
}
}) })

View file

@ -1,128 +1,134 @@
package api package api
import ( import (
"github.com/0xJacky/Nginx-UI/server/tool" "github.com/0xJacky/Nginx-UI/server/tool"
"github.com/0xJacky/Nginx-UI/server/tool/nginx" "github.com/0xJacky/Nginx-UI/server/tool/nginx"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"log" "log"
"net/http" "net/http"
"os" "os"
) )
func CertInfo(c *gin.Context) { func CertInfo(c *gin.Context) {
domain := c.Param("domain") domain := c.Param("domain")
key, err := tool.GetCertInfo(domain) key, err := tool.GetCertInfo(domain)
c.JSON(http.StatusOK, gin.H{ if err != nil {
"error": err, c.JSON(http.StatusOK, gin.H{
"subject_name": key.Subject.CommonName, "error": err,
"issuer_name": key.Issuer.CommonName, })
"not_after": key.NotAfter, return
"not_before": key.NotBefore, }
})
c.JSON(http.StatusOK, gin.H{
"subject_name": key.Subject.CommonName,
"issuer_name": key.Issuer.CommonName,
"not_after": key.NotAfter,
"not_before": key.NotBefore,
})
} }
func IssueCert(c *gin.Context) { func IssueCert(c *gin.Context) {
domain := c.Param("domain") domain := c.Param("domain")
var upGrader = websocket.Upgrader{ var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
return true return true
}, },
} }
// upgrade http to websocket // upgrade http to websocket
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
defer func(ws *websocket.Conn) { defer func(ws *websocket.Conn) {
err := ws.Close() err := ws.Close()
if err != nil { if err != nil {
log.Println("defer websocket close err", err) log.Println("defer websocket close err", err)
} }
}(ws) }(ws)
// read // read
mt, message, err := ws.ReadMessage() mt, message, err := ws.ReadMessage()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
if mt == websocket.TextMessage && string(message) == "go" { if mt == websocket.TextMessage && string(message) == "go" {
err = tool.IssueCert(domain) err = tool.IssueCert(domain)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
err = ws.WriteJSON(gin.H{ err = ws.WriteJSON(gin.H{
"status": "error", "status": "error",
"message": err.Error(), "message": err.Error(),
}) })
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
return return
} }
sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
_, err = os.Stat(sslCertificatePath) _, err = os.Stat(sslCertificatePath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
log.Println("[found]", "fullchain.cer") log.Println("[found]", "fullchain.cer")
err = ws.WriteJSON(gin.H{ err = ws.WriteJSON(gin.H{
"status": "success", "status": "success",
"message": "[found] fullchain.cer", "message": "[found] fullchain.cer",
}) })
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key") sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
_, err = os.Stat(sslCertificateKeyPath) _, err = os.Stat(sslCertificateKeyPath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
log.Println("[found]", "cert key") log.Println("[found]", "cert key")
err = ws.WriteJSON(gin.H{ err = ws.WriteJSON(gin.H{
"status": "success", "status": "success",
"message": "[found] Certificate Key", "message": "[found] Certificate Key",
}) })
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
err = ws.WriteJSON(gin.H{ err = ws.WriteJSON(gin.H{
"status": "success", "status": "success",
"message": "Issued certificate successfully", "message": "Issued certificate successfully",
"ssl_certificate": sslCertificatePath, "ssl_certificate": sslCertificatePath,
"ssl_certificate_key": sslCertificateKeyPath, "ssl_certificate_key": sslCertificateKeyPath,
}) })
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
} }
} }