mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
Refactored nginx configuration editor
This commit is contained in:
parent
f526cd0ade
commit
b19ecdda9c
31 changed files with 1476 additions and 956 deletions
|
@ -27,8 +27,8 @@ const domain = {
|
|||
return http.post(base_url + '/' + name + '/disable')
|
||||
},
|
||||
|
||||
get_template(name) {
|
||||
return http.get('template/' + name)
|
||||
get_template() {
|
||||
return http.get('template')
|
||||
},
|
||||
|
||||
cert_info(domain) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import user from './user'
|
|||
import install from './install'
|
||||
import analytic from './analytic'
|
||||
import settings from './settings'
|
||||
import ngx from './ngx'
|
||||
|
||||
export default {
|
||||
domain,
|
||||
|
@ -13,5 +14,6 @@ export default {
|
|||
user,
|
||||
install,
|
||||
analytic,
|
||||
settings
|
||||
settings,
|
||||
ngx
|
||||
}
|
||||
|
|
13
frontend/src/api/ngx.js
Normal file
13
frontend/src/api/ngx.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import http from '@/lib/http'
|
||||
|
||||
const ngx = {
|
||||
build_config(ngxConfig) {
|
||||
return http.post('/ngx/build_config', ngxConfig)
|
||||
},
|
||||
|
||||
tokenize_config(content) {
|
||||
return http.post('/ngx/tokenize_config', {content})
|
||||
}
|
||||
}
|
||||
|
||||
export default ngx
|
|
@ -72,7 +72,7 @@
|
|||
:okText="ok_text"
|
||||
:title="restore_title_text"
|
||||
@confirm="restore(record[rowKey])">
|
||||
<a href="javascript:;">{{restore_action_text}}</a>
|
||||
<a href="javascript:;">{{ restore_action_text }}</a>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm
|
||||
v-else
|
||||
|
@ -80,7 +80,7 @@
|
|||
:okText="ok_text"
|
||||
:title="destroy_title_text"
|
||||
@confirm="destroy(record[rowKey])">
|
||||
<a href="javascript:;">{{destroy_action_text}}</a>
|
||||
<a href="javascript:;">{{ destroy_action_text }}</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -93,6 +93,7 @@
|
|||
import StdPagination from './StdPagination'
|
||||
import moment from 'moment'
|
||||
import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
|
||||
import $gettext, {$interpolate} from '@/lib/translate/gettext'
|
||||
|
||||
export default {
|
||||
name: 'StdTable',
|
||||
|
@ -230,10 +231,10 @@ export default {
|
|||
destroy(id) {
|
||||
this.api.destroy(id).then(() => {
|
||||
this.get_list()
|
||||
this.$message.success('删除 ID: ' + id + ' 成功')
|
||||
this.$message.success($interpolate($gettext('Delete ID: %{id}'), {id: id}))
|
||||
}).catch(e => {
|
||||
console.log(e)
|
||||
this.$message.error(e?.message ?? '系统错误')
|
||||
this.$message.error(e?.message ?? $gettext('Server error'))
|
||||
})
|
||||
},
|
||||
get_list(page_num = null) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<editor v-model="current_value" @init="editorInit" lang="nginx" theme="monokai" width="100%" height="1000"></editor>
|
||||
<editor v-model="current_value" @init="editorInit" lang="nginx" theme="monokai" width="100%" :height="defaultTextHeight"></editor>
|
||||
</template>
|
||||
<style lang="less">
|
||||
.cm-s-monokai {
|
||||
|
@ -20,6 +20,10 @@ export default {
|
|||
},
|
||||
props: {
|
||||
value: {},
|
||||
defaultTextHeight: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
|
@ -36,16 +40,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
current_value: this.value ?? '',
|
||||
cmOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'text/x-nginx-conf',
|
||||
theme: 'monokai',
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
highlightDifferences: true,
|
||||
defaultTextHeight: 1000,
|
||||
// more CodeMirror options...
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -57,7 +57,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
collapsed: this.collapse(),
|
||||
zh_CN,
|
||||
clientWidth: document.body.clientWidth,
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,39 +2,62 @@
|
|||
<a-card :title="$gettext('Add Site')">
|
||||
<div class="domain-add-container">
|
||||
<a-steps :current="current_step" size="small">
|
||||
<a-step :title="$gettext('Base information')" />
|
||||
<a-step :title="$gettext('Configure SSL')" />
|
||||
<a-step :title="$gettext('Finished')" />
|
||||
<a-step :title="$gettext('Base information')"/>
|
||||
<a-step :title="$gettext('Configure SSL')"/>
|
||||
<a-step :title="$gettext('Finished')"/>
|
||||
</a-steps>
|
||||
|
||||
<std-data-entry :data-list="columns" :data-source="config" :error="error" v-show="current_step===0"/>
|
||||
<template v-if="current_step===0">
|
||||
<a-form-item :label="$gettext('Configuration Name')">
|
||||
<a-input v-model="config.name"/>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="current_step===1">
|
||||
<a-button
|
||||
@click="issue_cert"
|
||||
type="primary" ghost
|
||||
style="margin: 10px 0"
|
||||
:disabled="is_demo"
|
||||
:loading="issuing_cert"
|
||||
<directive-editor :ngx_directives="ngx_config.servers[0].directives"/>
|
||||
|
||||
<location-editor :locations="ngx_config.servers[0].locations"/>
|
||||
|
||||
<a-alert
|
||||
v-if="!has_server_name"
|
||||
:message="$gettext('Warning')"
|
||||
type="warning"
|
||||
show-icon
|
||||
>
|
||||
<translate>Getting Certificate from Let's Encrypt</translate>
|
||||
</a-button>
|
||||
<p v-if="is_demo" v-translate>This feature is not available in demo.</p>
|
||||
|
||||
<std-data-entry :data-list="columnsSSL" :data-source="config" :error="error" />
|
||||
|
||||
<a-space style="margin-right: 10px">
|
||||
<a-button
|
||||
v-if="current_step===1"
|
||||
@click="current_step++"
|
||||
>
|
||||
<translate>Skip</translate>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<template slot="description">
|
||||
<span v-translate>
|
||||
server_name parameter is required
|
||||
</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
<br/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="current_step===1">
|
||||
|
||||
<a-form-item :label="$gettext('Enable TLS')">
|
||||
<a-switch @change="change_tls"/>
|
||||
</a-form-item>
|
||||
|
||||
<ngx-config-editor
|
||||
ref="ngx_config"
|
||||
:ngx_config="ngx_config"
|
||||
v-model="auto_cert"
|
||||
:enabled="enabled"
|
||||
/>
|
||||
|
||||
</template>
|
||||
|
||||
<a-space v-if="current_step<2">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="save"
|
||||
:disabled="!config.name||!has_server_name"
|
||||
>
|
||||
<translate>Next</translate>
|
||||
</a-button>
|
||||
</a-space>
|
||||
|
||||
<a-result
|
||||
v-if="current_step===2"
|
||||
v-else-if="current_step===2"
|
||||
status="success"
|
||||
:title="$gettext('Domain Config Created Successfully')"
|
||||
>
|
||||
|
@ -48,118 +71,139 @@
|
|||
</template>
|
||||
</a-result>
|
||||
|
||||
<a-space v-if="current_step<2">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="save"
|
||||
:disabled="!config.name"
|
||||
>
|
||||
<translate>Next</translate>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
|
||||
import {columns, columnsSSL} from '@/views/domain/columns'
|
||||
import {unparse, issue_cert} from '@/views/domain/methods'
|
||||
import $gettext, {$interpolate} from "@/lib/translate/gettext"
|
||||
import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor'
|
||||
import LocationEditor from '@/views/domain/ngx_conf/LocationEditor'
|
||||
import $gettext, {$interpolate} from '@/lib/translate/gettext'
|
||||
import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor'
|
||||
|
||||
export default {
|
||||
name: 'DomainAdd',
|
||||
components: {StdDataEntry},
|
||||
components: {NgxConfigEditor, LocationEditor, DirectiveEditor},
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
http_listen_port: 80,
|
||||
https_listen_port: 443
|
||||
config: {},
|
||||
ngx_config: {
|
||||
servers: [{}]
|
||||
},
|
||||
columns: columns.slice(0, -1), // 隐藏SSL支持开关
|
||||
error: {},
|
||||
current_step: 0,
|
||||
columnsSSL,
|
||||
issuing_cert: false
|
||||
enabled: true,
|
||||
auto_cert: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'config.auto_cert'() {
|
||||
this.change_auto_cert()
|
||||
}
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.$api.domain.get_template().then(r => {
|
||||
this.ngx_config = r.tokenized
|
||||
})
|
||||
},
|
||||
save() {
|
||||
if (this.current_step===0) {
|
||||
this.$api.domain.get_template('http-conf').then(r => {
|
||||
let text = unparse(r.template, this.config)
|
||||
this.$api.ngx.build_config(this.ngx_config).then(r => {
|
||||
this.$api.domain.save(this.config.name, {content: r.content, enabled: true}).then(() => {
|
||||
this.$message.success($gettext('Saved successfully'))
|
||||
|
||||
this.$api.domain.save(this.config.name, {content: text, enabled: true}).then(() => {
|
||||
this.$message.success($gettext('Saved successfully'))
|
||||
|
||||
this.$api.domain.enable(this.config.name).then(() => {
|
||||
this.$message.success($gettext('Enabled successfully'))
|
||||
this.current_step++
|
||||
}).catch(r => {
|
||||
this.$message.error(r.message ?? $gettext('Enable failed'), 10)
|
||||
})
|
||||
|
||||
}).catch(r => {
|
||||
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ""}), 10)
|
||||
})
|
||||
})
|
||||
} else if (this.current_step === 1) {
|
||||
this.$api.domain.get_template('https-conf').then(r => {
|
||||
let text = unparse(r.template, this.config)
|
||||
|
||||
this.$api.domain.save(this.config.name, {content: text, enabled: true}).then(() => {
|
||||
this.$message.success($gettext('Saved successfully'))
|
||||
this.$api.domain.enable(this.config.name).then(() => {
|
||||
this.$message.success($gettext('Enabled successfully'))
|
||||
this.current_step++
|
||||
}).catch(r => {
|
||||
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ""}), 10)
|
||||
this.$message.error(r.message ?? $gettext('Enable failed'), 10)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
issue_cert() {
|
||||
this.issuing_cert = true
|
||||
issue_cert(this.config.server_name, this.callback)
|
||||
},
|
||||
callback(ssl_certificate, ssl_certificate_key) {
|
||||
this.$set(this.config, 'ssl_certificate', ssl_certificate)
|
||||
this.$set(this.config, 'ssl_certificate_key', ssl_certificate_key)
|
||||
this.issuing_cert = false
|
||||
}).catch(r => {
|
||||
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ''}), 10)
|
||||
})
|
||||
})
|
||||
},
|
||||
goto_modify() {
|
||||
this.$router.push('/domain/'+this.config.name)
|
||||
this.$router.push('/domain/' + this.config.name)
|
||||
},
|
||||
create_another() {
|
||||
this.current_step = 0
|
||||
this.config = {
|
||||
http_listen_port: 80,
|
||||
https_listen_port: 443
|
||||
this.config = {}
|
||||
this.ngx_config = {
|
||||
servers: [{}]
|
||||
}
|
||||
},
|
||||
change_auto_cert() {
|
||||
if (this.config.auto_cert) {
|
||||
this.$api.domain.add_auto_cert(this.config.name).then(() => {
|
||||
this.$message.success($interpolate($gettext('Auto-renewal enabled for %{name}'), {name: this.config.name}))
|
||||
}).catch(e => {
|
||||
this.$message.error(e.message ?? $interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: this.config.name}))
|
||||
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.$refs.ngx_config.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.$refs.ngx_config.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 {
|
||||
this.$api.domain.remove_auto_cert(this.config.name).then(() => {
|
||||
this.$message.success($interpolate($gettext('Auto-renewal disabled for %{name}'), {name: this.config.name}))
|
||||
}).catch(e => {
|
||||
this.$message.error(e.message ?? $interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: this.config.name}))
|
||||
})
|
||||
// remove servers[1]
|
||||
this.$refs.ngx_config.current_server_index = 0
|
||||
if (this.ngx_config.servers.length === 2) {
|
||||
this.ngx_config.servers.splice(1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
is_demo() {
|
||||
return this.$store.getters.env.demo === true
|
||||
has_server_name() {
|
||||
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 === 'server_name' && v.params.trim() !== '') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
</a-tag>
|
||||
</template>
|
||||
<template v-slot:extra>
|
||||
<a-switch size="small" v-model="advance_mode"/>
|
||||
<a-switch size="small" v-model="advance_mode" @change="on_mode_change"/>
|
||||
<template v-if="advance_mode">
|
||||
{{ $gettext('Advance') }}
|
||||
{{ $gettext('Advance Mode') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $gettext('Basic') }}
|
||||
{{ $gettext('Basic Mode') }}
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
@ -29,22 +29,13 @@
|
|||
<a-form-item :label="$gettext('Enabled')">
|
||||
<a-switch v-model="enabled" @change="checked=>{checked?enable():disable()}"/>
|
||||
</a-form-item>
|
||||
<p v-translate>The following values will only take effect if you have the corresponding fields in your configuration file. The configuration filename cannot be changed after it has been created.</p>
|
||||
<std-data-entry :data-list="columns" v-model="config"/>
|
||||
<template v-if="config.support_ssl">
|
||||
<cert-info :domain="name" ref="cert-info" v-if="name"/>
|
||||
<a-button
|
||||
@click="issue_cert"
|
||||
type="primary" ghost
|
||||
style="margin: 10px 0"
|
||||
:disabled="is_demo"
|
||||
:loading="issuing_cert"
|
||||
>
|
||||
<translate>Getting Certificate from Let's Encrypt</translate>
|
||||
</a-button>
|
||||
<p v-if="is_demo" v-translate>This feature is not available in demo.</p>
|
||||
<p v-else v-translate>Make sure you have configured a reverse proxy for .well-known directory to HTTPChallengePort (default: 9180) before getting the certificate.</p>
|
||||
</template>
|
||||
|
||||
<ngx-config-editor
|
||||
ref="ngx_config"
|
||||
:ngx_config="ngx_config"
|
||||
v-model="auto_cert"
|
||||
:enabled="enabled"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
|
@ -55,7 +46,7 @@
|
|||
<a-button @click="$router.go(-1)">
|
||||
<translate>Back</translate>
|
||||
</a-button>
|
||||
<a-button type="primary" @click="save">
|
||||
<a-button type="primary" @click="save" :loading="saving">
|
||||
<translate>Save</translate>
|
||||
</a-button>
|
||||
</a-space>
|
||||
|
@ -65,60 +56,39 @@
|
|||
|
||||
|
||||
<script>
|
||||
import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
|
||||
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
|
||||
import VueItextarea from '@/components/VueItextarea/VueItextarea'
|
||||
import {columns, columnsSSL} from '@/views/domain/columns'
|
||||
import {unparse, issue_cert} from '@/views/domain/methods'
|
||||
import CertInfo from '@/views/domain/CertInfo'
|
||||
import {$gettext, $interpolate} from '@/lib/translate/gettext'
|
||||
import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'DomainEdit',
|
||||
components: {CertInfo, FooterToolBar, StdDataEntry, VueItextarea},
|
||||
components: {NgxConfigEditor, FooterToolBar, VueItextarea},
|
||||
data() {
|
||||
return {
|
||||
name: this.$route.params.name.toString(),
|
||||
config: {
|
||||
http_listen_port: 80,
|
||||
https_listen_port: null,
|
||||
server_name: '',
|
||||
index: '',
|
||||
root: '',
|
||||
ssl_certificate: '',
|
||||
ssl_certificate_key: '',
|
||||
support_ssl: false,
|
||||
auto_cert: false
|
||||
update: 0,
|
||||
ngx_config: {
|
||||
filename: '',
|
||||
upstreams: [],
|
||||
servers: []
|
||||
},
|
||||
auto_cert: false,
|
||||
current_server_index: 0,
|
||||
enabled: false,
|
||||
configText: '',
|
||||
ws: null,
|
||||
ok: false,
|
||||
issuing_cert: false,
|
||||
advance_mode: false,
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route'() {
|
||||
this.init()
|
||||
},
|
||||
config: {
|
||||
handler() {
|
||||
this.unparse()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
'config.support_ssl'() {
|
||||
if (this.ok) {
|
||||
this.change_support_ssl()
|
||||
}
|
||||
},
|
||||
'config.auto_cert'() {
|
||||
if (this.ok) {
|
||||
this.change_auto_cert()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
|
@ -133,106 +103,53 @@ export default {
|
|||
if (this.name) {
|
||||
this.$api.domain.get(this.name).then(r => {
|
||||
this.configText = r.config
|
||||
this.config.auto_cert = r.auto_cert
|
||||
this.enabled = r.enabled
|
||||
this.parse(r).then(() => {
|
||||
this.ok = true
|
||||
})
|
||||
this.ngx_config = r.tokenized
|
||||
this.auto_cert = r.auto_cert
|
||||
}).catch(r => {
|
||||
console.log(r)
|
||||
this.$message.error($gettext('Server error'))
|
||||
this.$message.error(r.message ?? $gettext('Server error'))
|
||||
})
|
||||
}
|
||||
},
|
||||
async parse(r) {
|
||||
const text = r.config
|
||||
const reg = {
|
||||
http_listen_port: /listen[\s](.*);/i,
|
||||
https_listen_port: /listen[\s](.*) ssl/i,
|
||||
server_name: /server_name[\s](.*);/i,
|
||||
index: /index[\s](.*);/i,
|
||||
root: /root[\s](.*);/i,
|
||||
ssl_certificate: /ssl_certificate[\s](.*);/i,
|
||||
ssl_certificate_key: /ssl_certificate_key[\s](.*);/i
|
||||
}
|
||||
this.config['name'] = r.name
|
||||
for (let r in reg) {
|
||||
const match = text.match(reg[r])
|
||||
// console.log(r, match)
|
||||
if (match !== null) {
|
||||
if (match[1] !== undefined) {
|
||||
this.config[r] = match[1].trim()
|
||||
} else {
|
||||
this.config[r] = match[0].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.config.https_listen_port) {
|
||||
this.config.support_ssl = true
|
||||
}
|
||||
},
|
||||
async unparse() {
|
||||
this.configText = unparse(this.configText, this.config)
|
||||
},
|
||||
async get_template() {
|
||||
if (this.config.support_ssl) {
|
||||
await this.$api.domain.get_template('https-conf').then(r => {
|
||||
this.configText = r.template
|
||||
})
|
||||
on_mode_change(advance_mode) {
|
||||
if (advance_mode) {
|
||||
this.build_config()
|
||||
} else {
|
||||
await this.$api.domain.get_template('http-conf').then(r => {
|
||||
this.configText = r.template
|
||||
return this.$api.ngx.tokenize_config(this.configText).then(r => {
|
||||
this.ngx_config = r
|
||||
}).catch(r => {
|
||||
this.$message.error(r.message ?? $gettext('Server error'))
|
||||
})
|
||||
}
|
||||
await this.unparse()
|
||||
},
|
||||
change_support_ssl() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: $gettext('Do you want to change the template to support the TLS?'),
|
||||
content: $gettext('This operation will lose the custom configuration.'),
|
||||
onOk() {
|
||||
that.get_template()
|
||||
},
|
||||
onCancel() {
|
||||
},
|
||||
build_config() {
|
||||
return this.$api.ngx.build_config(this.ngx_config).then(r => {
|
||||
this.configText = r.content
|
||||
}).catch(r => {
|
||||
this.$message.error(r.message ?? $gettext('Server error'))
|
||||
})
|
||||
},
|
||||
save() {
|
||||
async save() {
|
||||
this.saving = true
|
||||
|
||||
if (!this.advance_mode) {
|
||||
await this.build_config()
|
||||
}
|
||||
|
||||
this.$api.domain.save(this.name, {content: this.configText}).then(r => {
|
||||
this.parse(r)
|
||||
this.configText = r.config
|
||||
this.enabled = r.enabled
|
||||
this.ngx_config = r.tokenized
|
||||
this.$message.success($gettext('Saved successfully'))
|
||||
if (this.name) {
|
||||
if (this.$refs['cert-info']) this.$refs['cert-info'].get()
|
||||
}
|
||||
|
||||
this.$refs.ngx_config.update_cert_info()
|
||||
|
||||
}).catch(r => {
|
||||
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ''}), 10)
|
||||
}).finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
},
|
||||
issue_cert() {
|
||||
this.issuing_cert = true
|
||||
issue_cert(this.config.server_name, this.callback)
|
||||
},
|
||||
callback(ssl_certificate, ssl_certificate_key) {
|
||||
this.$set(this.config, 'ssl_certificate', ssl_certificate)
|
||||
this.$set(this.config, 'ssl_certificate_key', ssl_certificate_key)
|
||||
if (this.$refs['cert-info']) this.$refs['cert-info'].get()
|
||||
this.issuing_cert = false
|
||||
},
|
||||
change_auto_cert() {
|
||||
if (this.config.auto_cert) {
|
||||
this.$api.domain.add_auto_cert(this.name).then(() => {
|
||||
this.$message.success($interpolate($gettext('Auto-renewal enabled for %{name}'), {name: this.name}))
|
||||
}).catch(e => {
|
||||
this.$message.error(e.message ?? $interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: this.name}))
|
||||
})
|
||||
} else {
|
||||
this.$api.domain.remove_auto_cert(this.name).then(() => {
|
||||
this.$message.success($interpolate($gettext('Auto-renewal disabled for %{name}'), {name: this.name}))
|
||||
}).catch(e => {
|
||||
this.$message.error(e.message ?? $interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: this.name}))
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
enable() {
|
||||
this.$api.domain.enable(this.name).then(() => {
|
||||
|
@ -252,15 +169,6 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
columns: {
|
||||
get() {
|
||||
if (this.config.support_ssl) {
|
||||
return [...columns, ...columnsSSL]
|
||||
} else {
|
||||
return [...columns]
|
||||
}
|
||||
}
|
||||
},
|
||||
is_demo() {
|
||||
return this.$store.getters.env.demo === true
|
||||
}
|
||||
|
@ -274,16 +182,15 @@ export default {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.ant-card {
|
||||
// margin: 10px;
|
||||
@media (max-width: 512px) {
|
||||
margin: 10px 0;
|
||||
}
|
||||
margin: 10px 0;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
.domain-edit-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
/deep/.ant-form-item-label > label::after {
|
||||
|
||||
/deep/ .ant-form-item-label > label::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
@ -291,12 +198,26 @@ export default {
|
|||
.slide-fade-enter-active {
|
||||
transition: all .5s ease-in-out;
|
||||
}
|
||||
|
||||
.slide-fade-leave-active {
|
||||
transition: all .5s cubic-bezier(1.0, 0.5, 0.8, 1.0);
|
||||
}
|
||||
|
||||
.slide-fade-enter, .slide-fade-leave-to
|
||||
/* .slide-fade-leave-active for below version 2.1.8 */ {
|
||||
transform: translateX(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.location-block {
|
||||
|
||||
}
|
||||
|
||||
.directive-params-wrapper {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
44
frontend/src/views/domain/cert/Cert.vue
Normal file
44
frontend/src/views/domain/cert/Cert.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<div>
|
||||
<cert-info ref="info" :domain="name" v-if="name"/>
|
||||
<issue-cert
|
||||
:current_server_directives="current_server_directives"
|
||||
:directives-map="directivesMap"
|
||||
v-model="auto_cert"
|
||||
@callback="callback"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CertInfo from '@/views/domain/cert/CertInfo'
|
||||
import IssueCert from '@/views/domain/cert/IssueCert'
|
||||
|
||||
export default {
|
||||
name: 'Cert',
|
||||
components: {IssueCert, CertInfo},
|
||||
props: {
|
||||
directivesMap: Object,
|
||||
current_server_directives: Array,
|
||||
auto_cert: Boolean
|
||||
},
|
||||
model: {
|
||||
prop: 'auto_cert',
|
||||
event: 'change_auto_cert'
|
||||
},
|
||||
methods: {
|
||||
callback() {
|
||||
this.$refs.info.get()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
return this.directivesMap['server_name'][0].params.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-if="ok">
|
||||
<h3 v-translate>Certificate Status</h3>
|
||||
<div class="cert-info" v-if="ok">
|
||||
<h4 v-translate>Certificate Status</h4>
|
||||
<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="{date: moment(cert.not_after).format('YYYY-MM-DD HH:mm:ss').toString()}">
|
||||
|
@ -57,6 +57,14 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
h4 {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.cert-info {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
span {
|
||||
margin-left: 10px;
|
163
frontend/src/views/domain/cert/IssueCert.vue
Normal file
163
frontend/src/views/domain/cert/IssueCert.vue
Normal file
|
@ -0,0 +1,163 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form-item :label="$gettext('Encrypt website with Let\'s Encrypt')">
|
||||
<a-switch
|
||||
:loading="issuing_cert"
|
||||
v-model="M_enabled"
|
||||
@change="onchange"
|
||||
:disabled="no_server_name||server_name_more_than_one"
|
||||
/>
|
||||
<a-alert
|
||||
v-if="no_server_name||server_name_more_than_one"
|
||||
:message="$gettext('Warning')"
|
||||
type="warning"
|
||||
show-icon
|
||||
>
|
||||
<template slot="description">
|
||||
<span v-if="no_server_name" v-translate>
|
||||
server_name parameter is required
|
||||
</span>
|
||||
<span v-if="server_name_more_than_one" v-translate>
|
||||
server_name parameters more than one
|
||||
</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-form-item>
|
||||
<p v-translate>Note: The server_name in the current configuration must be the domain name you need to get the
|
||||
certificate.</p>
|
||||
<p v-if="enabled" v-translate>The certificate for the domain will be checked every hour,
|
||||
and will be renewed if it has been more than 1 month since it was last issued.</p>
|
||||
<p v-translate>Make sure you have configured a reverse proxy for .well-known
|
||||
directory to HTTPChallengePort (default: 9180) before getting the certificate.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {issue_cert} from '@/views/domain/methods'
|
||||
import $gettext, {$interpolate} from '@/lib/translate/gettext'
|
||||
|
||||
export default {
|
||||
name: 'IssueCert',
|
||||
props: {
|
||||
directivesMap: Object,
|
||||
current_server_directives: Array,
|
||||
enabled: Boolean
|
||||
},
|
||||
model: {
|
||||
prop: 'enabled',
|
||||
event: 'changeEnabled'
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
issuing_cert: false,
|
||||
M_enabled: this.enabled,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onchange(r) {
|
||||
this.$emit('changeEnabled', r)
|
||||
this.change_auto_cert(r)
|
||||
if (r) {
|
||||
this.job()
|
||||
}
|
||||
},
|
||||
job() {
|
||||
this.issuing_cert = true
|
||||
|
||||
if (this.no_server_name) {
|
||||
this.$message.error($gettext('server_name not found in directives'))
|
||||
this.issuing_cert = false
|
||||
return
|
||||
}
|
||||
|
||||
if (this.server_name_more_than_one) {
|
||||
this.$message.error($gettext('server_name parameters more than one'))
|
||||
this.issuing_cert = false
|
||||
return
|
||||
}
|
||||
|
||||
const server_name = this.directivesMap['server_name'][0]
|
||||
|
||||
if (!this.directivesMap['ssl_certificate']) {
|
||||
this.current_server_directives.splice(server_name.idx + 1, 0, {
|
||||
directive: 'ssl_certificate',
|
||||
params: ''
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (!this.directivesMap['ssl_certificate_key']) {
|
||||
const ssl_certificate = this.directivesMap['ssl_certificate'][0]
|
||||
this.current_server_directives.splice(ssl_certificate.idx + 1, 0, {
|
||||
directive: 'ssl_certificate_key',
|
||||
params: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
issue_cert(this.name, this.callback)
|
||||
}, 100)
|
||||
},
|
||||
callback(ssl_certificate, ssl_certificate_key) {
|
||||
this.$set(this.directivesMap['ssl_certificate'][0], 'params', ssl_certificate)
|
||||
this.$set(this.directivesMap['ssl_certificate_key'][0], 'params', ssl_certificate_key)
|
||||
this.issuing_cert = false
|
||||
this.$emit('callback')
|
||||
},
|
||||
change_auto_cert(r) {
|
||||
if (r) {
|
||||
this.$api.domain.add_auto_cert(this.name).then(() => {
|
||||
this.$message.success($interpolate($gettext('Auto-renewal enabled for %{name}'), {name: this.name}))
|
||||
}).catch(e => {
|
||||
this.$message.error(e.message ?? $interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: this.name}))
|
||||
})
|
||||
} else {
|
||||
this.$api.domain.remove_auto_cert(this.name).then(() => {
|
||||
this.$message.success($interpolate($gettext('Auto-renewal disabled for %{name}'), {name: this.name}))
|
||||
}).catch(e => {
|
||||
this.$message.error(e.message ?? $interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: this.name}))
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
server_name_more_than_one() {
|
||||
this.M_enabled = false
|
||||
this.onchange(false)
|
||||
},
|
||||
no_server_name() {
|
||||
this.M_enabled = false
|
||||
this.onchange(false)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
is_demo() {
|
||||
return this.$store.getters.env.demo === true
|
||||
},
|
||||
server_name_more_than_one() {
|
||||
return this.directivesMap['server_name'] && (this.directivesMap['server_name'].length > 1 ||
|
||||
this.directivesMap['server_name'][0].params.trim().indexOf(' ') > 0)
|
||||
},
|
||||
no_server_name() {
|
||||
return !this.directivesMap['server_name']
|
||||
},
|
||||
name() {
|
||||
return this.directivesMap['server_name'][0].params.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.switch-wrapper {
|
||||
position: relative;
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,4 +1,4 @@
|
|||
import $gettext from "@/lib/translate/gettext";
|
||||
import $gettext from '@/lib/translate/gettext'
|
||||
|
||||
const columns = [{
|
||||
title: $gettext('Configuration Name'),
|
||||
|
@ -12,6 +12,13 @@ const columns = [{
|
|||
edit: {
|
||||
type: 'input'
|
||||
}
|
||||
}, {
|
||||
title: $gettext('HTTP Listen Port'),
|
||||
dataIndex: 'http_listen_port',
|
||||
edit: {
|
||||
type: 'number',
|
||||
min: 80
|
||||
}
|
||||
}, {
|
||||
title: $gettext('Root Directory (root)'),
|
||||
dataIndex: 'root',
|
||||
|
@ -24,51 +31,46 @@ const columns = [{
|
|||
edit: {
|
||||
type: 'input'
|
||||
}
|
||||
}, {
|
||||
title: $gettext('HTTP Listen Port'),
|
||||
dataIndex: 'http_listen_port',
|
||||
edit: {
|
||||
type: 'number',
|
||||
min: 80
|
||||
}
|
||||
}, {
|
||||
title: $gettext('Enable TLS'),
|
||||
dataIndex: 'support_ssl',
|
||||
edit: {
|
||||
type: 'switch',
|
||||
event: 'change_support_ssl'
|
||||
}
|
||||
}]
|
||||
|
||||
const columnsSSL = [{
|
||||
title: $gettext('Certificate Auto-renewal'),
|
||||
dataIndex: 'auto_cert',
|
||||
edit: {
|
||||
type: 'switch',
|
||||
event: 'change_auto_cert'
|
||||
},
|
||||
description: $gettext('The certificate for the domain will be checked every hour, ' +
|
||||
'and will be renewed if it has been more than 1 month since it was last issued.' +
|
||||
'<br/>If you do not have a certificate before, please click "Getting Certificate from Let\'s Encrypt" first.')
|
||||
}, {
|
||||
title: $gettext('HTTPS Listen Port'),
|
||||
dataIndex: 'https_listen_port',
|
||||
edit: {
|
||||
type: 'number',
|
||||
min: 443
|
||||
const columnsSSL = [
|
||||
{
|
||||
title: $gettext('Enable TLS'),
|
||||
dataIndex: 'support_ssl',
|
||||
edit: {
|
||||
type: 'switch',
|
||||
event: 'change_support_ssl'
|
||||
}
|
||||
}, {
|
||||
title: $gettext('Certificate Auto-renewal'),
|
||||
dataIndex: 'auto_cert',
|
||||
edit: {
|
||||
type: 'switch',
|
||||
event: 'change_auto_cert'
|
||||
},
|
||||
description: $gettext('The certificate for the domain will be checked every hour, ' +
|
||||
'and will be renewed if it has been more than 1 month since it was last issued.' +
|
||||
'<br/>If you do not have a certificate before, please click "Getting Certificate from Let\'s Encrypt" first.')
|
||||
}, {
|
||||
title: $gettext('HTTPS Listen Port'),
|
||||
dataIndex: 'https_listen_port',
|
||||
edit: {
|
||||
type: 'number',
|
||||
min: 443
|
||||
}
|
||||
}, {
|
||||
title: $gettext('Certificate Path (ssl_certificate)'),
|
||||
dataIndex: 'ssl_certificate',
|
||||
edit: {
|
||||
type: 'input'
|
||||
}
|
||||
}, {
|
||||
title: $gettext('Private Key Path (ssl_certificate_key)'),
|
||||
dataIndex: 'ssl_certificate_key',
|
||||
edit: {
|
||||
type: 'input'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: $gettext('Certificate Path (ssl_certificate)'),
|
||||
dataIndex: 'ssl_certificate',
|
||||
edit: {
|
||||
type: 'input'
|
||||
}
|
||||
}, {
|
||||
title: $gettext('Private Key Path (ssl_certificate_key)'),
|
||||
dataIndex: 'ssl_certificate_key',
|
||||
edit: {
|
||||
type: 'input'
|
||||
}
|
||||
}]
|
||||
]
|
||||
|
||||
export {columns, columnsSSL}
|
||||
|
|
|
@ -1,36 +1,8 @@
|
|||
import $gettext from '@/lib/translate/gettext'
|
||||
import store from '@/lib/store'
|
||||
import Vue from 'vue'
|
||||
const unparse = (text, config) => {
|
||||
// http_listen_port: /listen (.*);/i,
|
||||
// https_listen_port: /listen (.*) ssl/i,
|
||||
const reg = {
|
||||
server_name: /server_name[\s](.*);/ig,
|
||||
index: /index[\s](.*);/i,
|
||||
root: /root[\s](.*);/i,
|
||||
ssl_certificate: /ssl_certificate[\s](.*);/i,
|
||||
ssl_certificate_key: /ssl_certificate_key[\s](.*);/i
|
||||
}
|
||||
text = text.replace(/listen[\s](.*);/i, 'listen\t'
|
||||
+ config['http_listen_port'] + ';')
|
||||
text = text.replace(/listen[\s](.*) ssl/i, 'listen\t'
|
||||
+ config['https_listen_port'] + ' ssl')
|
||||
|
||||
text = text.replace(/listen(.*):(.*);/i, 'listen\t[::]:'
|
||||
+ config['http_listen_port'] + ';')
|
||||
text = text.replace(/listen(.*):(.*) ssl/i, 'listen\t[::]:'
|
||||
+ config['https_listen_port'] + ' ssl')
|
||||
|
||||
for (let k in reg) {
|
||||
text = text.replace(new RegExp(reg[k]), k + '\t' +
|
||||
(config[k] !== undefined ? config[k] : ' ') + ';')
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
const issue_cert = (server_name, callback) => {
|
||||
Vue.prototype.$message.info($gettext('Note: The server_name in the current configuration must be the domain name you need to get the certificate.'), 15)
|
||||
Vue.prototype.$message.info($gettext('Getting the certificate, please wait...'), 15)
|
||||
const ws = new WebSocket(Vue.prototype.getWebSocketRoot() + '/cert/issue/' + server_name
|
||||
+ '?token=' + btoa(store.state.user.token))
|
||||
|
@ -57,6 +29,9 @@ const issue_cert = (server_name, callback) => {
|
|||
callback(r.ssl_certificate, r.ssl_certificate_key)
|
||||
}
|
||||
}
|
||||
// setTimeout(() => {
|
||||
// callback('a', 'b')
|
||||
// }, 10000)
|
||||
}
|
||||
|
||||
export {unparse, issue_cert}
|
||||
export {issue_cert}
|
||||
|
|
78
frontend/src/views/domain/ngx_conf/LocationEditor.vue
Normal file
78
frontend/src/views/domain/ngx_conf/LocationEditor.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<a-form-item :label="$gettext('Locations')" :key="update">
|
||||
<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')">
|
||||
<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">
|
||||
<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>
|
||||
<a-button block @click="add">{{ $gettext('Add Location') }}</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueItextarea from '@/components/VueItextarea/VueItextarea'
|
||||
|
||||
export default {
|
||||
name: 'LocationEditor',
|
||||
components: {VueItextarea},
|
||||
props: {
|
||||
locations: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
adding: false,
|
||||
location: {},
|
||||
update: 0
|
||||
}
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-card {
|
||||
margin: 10px 0;
|
||||
box-shadow: unset;
|
||||
}
|
||||
</style>
|
104
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue
Normal file
104
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<a-tabs v-model="current_server_index">
|
||||
<a-tab-pane :tab="'Server '+(k+1)" v-for="(v,k) in ngx_config.servers" :key="k">
|
||||
|
||||
<div class="tab-content">
|
||||
<template v-if="support_ssl&&enabled">
|
||||
<cert-info :domain="name" v-if="name"/>
|
||||
<issue-cert
|
||||
:current_server_directives="current_server_directives"
|
||||
:directives-map="directivesMap"
|
||||
v-model="auto_cert"
|
||||
/>
|
||||
<cert-info :current_server_directives="current_server_directives"
|
||||
:directives-map="directivesMap"
|
||||
v-model="auto_cert"/>
|
||||
</template>
|
||||
|
||||
<a-form-item :label="$gettext('Comments')" v-if="v.comments">
|
||||
<p style="white-space: pre-wrap;">{{ v.comments }}</p>
|
||||
</a-form-item>
|
||||
|
||||
<directive-editor :ngx_directives="v.directives" :key="update"/>
|
||||
|
||||
<location-editor :locations="v.locations"/>
|
||||
</div>
|
||||
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</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() ?? '',
|
||||
}
|
||||
},
|
||||
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()
|
||||
}
|
||||
}
|
||||
},
|
||||
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: {
|
||||
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>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="add-directive-temp" v-if="adding">
|
||||
<a-select v-model="mode" default-value="default" style="min-width: 150px">
|
||||
<a-select-option value="default">
|
||||
{{ $gettext('Single Directive') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="if">
|
||||
if
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<vue-itextarea v-if="mode===If" :default-text-height="100" v-model="directive.params"/>
|
||||
<a-input-group compact v-else>
|
||||
<a-input style="width: 30%" :placeholder="$gettext('Directive')" v-model="directive.directive"/>
|
||||
<a-input style="width: 70%" :placeholder="$gettext('Params')" v-model="directive.params">
|
||||
<a-icon slot="suffix" type="close" style="color: rgba(0,0,0,.45);font-size: 10px;"
|
||||
@click="adding=false"/>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</div>
|
||||
<a-button block v-if="!adding" @click="add">{{ $gettext('Add Directive Below') }}</a-button>
|
||||
<a-button type="primary" v-else block @click="save"
|
||||
:disabled="!directive.directive&&!directive.params">{{ $gettext('Save Directive') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</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>
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<a-form-item :label="$gettext('Directives')">
|
||||
<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-input :addon-before="directive.directive" v-model="directive.params" @click="current_idx=k" v-else>
|
||||
<a-popconfirm slot="suffix" @confirm="remove(k)"
|
||||
:title="$gettext('Are you sure you want to remove this directive?')"
|
||||
:ok-text="$gettext('Yes')"
|
||||
:cancel-text="$gettext('No')">
|
||||
<a-icon type="close"
|
||||
style="color: rgba(0,0,0,.45);font-size: 10px;"
|
||||
/>
|
||||
</a-popconfirm>
|
||||
</a-input>
|
||||
<transition name="slide">
|
||||
<div v-if="current_idx===k" class="extra">
|
||||
<div class="extra-content">
|
||||
<a-form-item :label="$gettext('Comments')">
|
||||
<a-textarea v-model="directive.comments"/>
|
||||
</a-form-item>
|
||||
<directive-add :ngx_directives="ngx_directives" :idx="k" @save="onSave(k)"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<directive-add :ngx_directives="ngx_directives"/>
|
||||
</a-form-item>
|
||||
</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>
|
||||
.extra {
|
||||
background-color: #fafafa;
|
||||
padding: 10px 20px 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.slide-enter-active, .slide-leave-active {
|
||||
transition: max-height .5s ease;
|
||||
}
|
||||
|
||||
.slide-enter, .slide-leave-to {
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
.slide-enter-to, .slide-leave {
|
||||
max-height: 600px;
|
||||
}
|
||||
</style>
|
1
frontend/src/views/domain/ngx_conf/ngx_constant.js
Normal file
1
frontend/src/views/domain/ngx_conf/ngx_constant.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const If = "if"
|
|
@ -1,164 +1,128 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
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{
|
||||
"error": err,
|
||||
"subject_name": key.Subject.CommonName,
|
||||
"issuer_name": key.Issuer.CommonName,
|
||||
"not_after": key.NotAfter,
|
||||
"not_before": key.NotBefore,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err,
|
||||
"subject_name": key.Subject.CommonName,
|
||||
"issuer_name": key.Issuer.CommonName,
|
||||
"not_after": key.NotAfter,
|
||||
"not_before": key.NotBefore,
|
||||
})
|
||||
}
|
||||
|
||||
func IssueCert(c *gin.Context) {
|
||||
domain := c.Param("domain")
|
||||
var upGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
domain := c.Param("domain")
|
||||
var upGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// upgrade http to websocket
|
||||
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
// upgrade http to websocket
|
||||
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func(ws *websocket.Conn) {
|
||||
err := ws.Close()
|
||||
if err != nil {
|
||||
log.Println("defer websocket close err", err)
|
||||
}
|
||||
}(ws)
|
||||
defer func(ws *websocket.Conn) {
|
||||
err := ws.Close()
|
||||
if err != nil {
|
||||
log.Println("defer websocket close err", err)
|
||||
}
|
||||
}(ws)
|
||||
|
||||
for {
|
||||
// read
|
||||
mt, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if string(message) == "go" {
|
||||
var m []byte
|
||||
// read
|
||||
mt, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if settings.ServerSettings.Demo {
|
||||
m, _ = json.Marshal(gin.H{
|
||||
"status": "error",
|
||||
"message": "this feature is not available in demo",
|
||||
})
|
||||
_ = ws.WriteMessage(mt, m)
|
||||
return
|
||||
}
|
||||
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)
|
||||
|
||||
m, err = json.Marshal(gin.H{
|
||||
"status": "error",
|
||||
"message": err.Error(),
|
||||
})
|
||||
err = ws.WriteJSON(gin.H{
|
||||
"status": "error",
|
||||
"message": err.Error(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = ws.WriteMessage(mt, m)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
|
||||
_, err = os.Stat(sslCertificatePath)
|
||||
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
|
||||
_, err = os.Stat(sslCertificatePath)
|
||||
log.Println("[found]", "fullchain.cer")
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
err = ws.WriteJSON(gin.H{
|
||||
"status": "success",
|
||||
"message": "[found] fullchain.cer",
|
||||
})
|
||||
|
||||
log.Println("[found]", "fullchain.cer")
|
||||
m, err = json.Marshal(gin.H{
|
||||
"status": "success",
|
||||
"message": "[found] fullchain.cer",
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
|
||||
_, err = os.Stat(sslCertificateKeyPath)
|
||||
|
||||
err = ws.WriteMessage(mt, m)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
log.Println("[found]", "cert key")
|
||||
err = ws.WriteJSON(gin.H{
|
||||
"status": "success",
|
||||
"message": "[found] Certificate Key",
|
||||
})
|
||||
|
||||
sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
|
||||
_, err = os.Stat(sslCertificateKeyPath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
err = ws.WriteJSON(gin.H{
|
||||
"status": "success",
|
||||
"message": "Issued certificate successfully",
|
||||
"ssl_certificate": sslCertificatePath,
|
||||
"ssl_certificate_key": sslCertificateKeyPath,
|
||||
})
|
||||
|
||||
log.Println("[found]", "cert key")
|
||||
m, err = json.Marshal(gin.H{
|
||||
"status": "success",
|
||||
"message": "[found] cert key",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
err = ws.WriteMessage(mt, m)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
log.Println("申请成功")
|
||||
m, err = json.Marshal(gin.H{
|
||||
"status": "success",
|
||||
"message": "申请成功",
|
||||
"ssl_certificate": sslCertificatePath,
|
||||
"ssl_certificate_key": sslCertificateKeyPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
err = ws.WriteMessage(mt, m)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,267 +1,270 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetDomains(c *gin.Context) {
|
||||
orderBy := c.Query("order_by")
|
||||
sort := c.DefaultQuery("sort", "desc")
|
||||
orderBy := c.Query("order_by")
|
||||
sort := c.DefaultQuery("sort", "desc")
|
||||
|
||||
mySort := map[string]string{
|
||||
"enabled": "bool",
|
||||
"name": "string",
|
||||
"modify": "time",
|
||||
}
|
||||
mySort := map[string]string{
|
||||
"enabled": "bool",
|
||||
"name": "string",
|
||||
"modify": "time",
|
||||
}
|
||||
|
||||
configFiles, err := ioutil.ReadDir(nginx.GetNginxConfPath("sites-available"))
|
||||
configFiles, err := ioutil.ReadDir(nginx.GetNginxConfPath("sites-available"))
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
enabledConfig, err := ioutil.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
|
||||
enabledConfig, err := ioutil.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
enabledConfigMap := make(map[string]bool)
|
||||
for i := range enabledConfig {
|
||||
enabledConfigMap[enabledConfig[i].Name()] = true
|
||||
}
|
||||
enabledConfigMap := make(map[string]bool)
|
||||
for i := range enabledConfig {
|
||||
enabledConfigMap[enabledConfig[i].Name()] = true
|
||||
}
|
||||
|
||||
var configs []gin.H
|
||||
var configs []gin.H
|
||||
|
||||
for i := range configFiles {
|
||||
file := configFiles[i]
|
||||
if !file.IsDir() {
|
||||
configs = append(configs, gin.H{
|
||||
"name": file.Name(),
|
||||
"size": file.Size(),
|
||||
"modify": file.ModTime(),
|
||||
"enabled": enabledConfigMap[file.Name()],
|
||||
})
|
||||
}
|
||||
}
|
||||
for i := range configFiles {
|
||||
file := configFiles[i]
|
||||
if !file.IsDir() {
|
||||
configs = append(configs, gin.H{
|
||||
"name": file.Name(),
|
||||
"size": file.Size(),
|
||||
"modify": file.ModTime(),
|
||||
"enabled": enabledConfigMap[file.Name()],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
||||
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"configs": configs,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"configs": configs,
|
||||
})
|
||||
}
|
||||
|
||||
func GetDomain(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||
name := c.Param("name")
|
||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||
|
||||
enabled := true
|
||||
if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
||||
enabled = false
|
||||
}
|
||||
enabled := true
|
||||
if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
config, err := nginx.ParseNgxConfig(path)
|
||||
config, err := nginx.ParseNgxConfig(path)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = model.FirstCert(name)
|
||||
_, err = model.FirstCert(name)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"enabled": enabled,
|
||||
"name": name,
|
||||
"config": config.BuildConfig(),
|
||||
"tokenized": config,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"enabled": enabled,
|
||||
"name": name,
|
||||
"config": config.BuildConfig(),
|
||||
"tokenized": config,
|
||||
"auto_cert": err == nil,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func EditDomain(c *gin.Context) {
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
request := make(gin.H)
|
||||
err = c.BindJSON(&request)
|
||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
request := make(gin.H)
|
||||
err = c.BindJSON(&request)
|
||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||
|
||||
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
||||
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
||||
// 测试配置文件
|
||||
err = nginx.TestNginxConf()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
||||
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
||||
// Test nginx configuration
|
||||
err = nginx.TestNginxConf()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
output := nginx.ReloadNginx()
|
||||
output := nginx.ReloadNginx()
|
||||
|
||||
if output != "" && strings.Contains(output, "error") {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if output != "" && strings.Contains(output, "error") {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
GetDomain(c)
|
||||
GetDomain(c)
|
||||
}
|
||||
|
||||
func EnableDomain(c *gin.Context) {
|
||||
configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
|
||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
|
||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
|
||||
_, err := os.Stat(configFilePath)
|
||||
_, err := os.Stat(configFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
||||
if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
|
||||
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Test nginx config, if not pass then rollback.
|
||||
err = nginx.TestNginxConf()
|
||||
if err != nil {
|
||||
_ = os.Remove(enabledConfigFilePath)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// Test nginx config, if not pass then rollback.
|
||||
err = nginx.TestNginxConf()
|
||||
if err != nil {
|
||||
_ = os.Remove(enabledConfigFilePath)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
output := nginx.ReloadNginx()
|
||||
output := nginx.ReloadNginx()
|
||||
|
||||
if output != "" && strings.Contains(output, "error") {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
if output != "" && strings.Contains(output, "error") {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func DisableDomain(c *gin.Context) {
|
||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
|
||||
_, err := os.Stat(enabledConfigFilePath)
|
||||
_, err := os.Stat(enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove(enabledConfigFilePath)
|
||||
err = os.Remove(enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// delete auto cert record
|
||||
cert := model.Cert{Domain: c.Param("name")}
|
||||
err = cert.Remove()
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
// delete auto cert record
|
||||
cert := model.Cert{Domain: c.Param("name")}
|
||||
err = cert.Remove()
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
output := nginx.ReloadNginx()
|
||||
output := nginx.ReloadNginx()
|
||||
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteDomain(c *gin.Context) {
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||
enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||
enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
||||
|
||||
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": "site not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": "site not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = os.Stat(enabledPath); err == nil {
|
||||
c.JSON(http.StatusNotAcceptable, gin.H{
|
||||
"message": "site is enabled",
|
||||
})
|
||||
return
|
||||
}
|
||||
if _, err = os.Stat(enabledPath); err == nil {
|
||||
c.JSON(http.StatusNotAcceptable, gin.H{
|
||||
"message": "site is enabled",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cert := model.Cert{Domain: name}
|
||||
_ = cert.Remove()
|
||||
cert := model.Cert{Domain: name}
|
||||
_ = cert.Remove()
|
||||
|
||||
err = os.Remove(availablePath)
|
||||
err = os.Remove(availablePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func AddDomainToAutoCert(c *gin.Context) {
|
||||
domain := c.Param("domain")
|
||||
cert, err := model.FirstOrCreateCert(domain)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, cert)
|
||||
domain := c.Param("domain")
|
||||
cert, err := model.FirstOrCreateCert(domain)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, cert)
|
||||
}
|
||||
|
||||
func RemoveDomainFromAutoCert(c *gin.Context) {
|
||||
cert := model.Cert{
|
||||
Domain: c.Param("domain"),
|
||||
}
|
||||
err := cert.Remove()
|
||||
cert := model.Cert{
|
||||
Domain: c.Param("domain"),
|
||||
}
|
||||
err := cert.Remove()
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, nil)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, nil)
|
||||
}
|
||||
|
|
42
server/api/ngx.go
Normal file
42
server/api/ngx.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func BuildNginxConfig(c *gin.Context) {
|
||||
var ngxConf nginx.NgxConfig
|
||||
if !BindAndValid(c, &ngxConf) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"content": ngxConf.BuildConfig(),
|
||||
})
|
||||
}
|
||||
|
||||
func TokenizeNginxConfig(c *gin.Context) {
|
||||
var json struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
|
||||
if !BindAndValid(c, &json) {
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(json.Content))
|
||||
|
||||
ngxConfig, err := nginx.ParseNgxConfigByScanner("", scanner)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ngxConfig)
|
||||
|
||||
}
|
|
@ -2,34 +2,58 @@ package api
|
|||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/0xJacky/Nginx-UI/server/template"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetTemplate(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
content, err := template.DistFS.ReadFile(name)
|
||||
|
||||
_content := string(content)
|
||||
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
|
||||
content := `proxy_set_header Host $host;
|
||||
proxy_set_header X-Real_IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
||||
proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
|
||||
`
|
||||
content = strings.ReplaceAll(content, "{{ HTTP01PORT }}",
|
||||
settings.ServerSettings.HTTPChallengePort)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
var ngxConfig *nginx.NgxConfig
|
||||
|
||||
ngxConfig = &nginx.NgxConfig{
|
||||
Servers: []*nginx.NgxServer{
|
||||
{
|
||||
Directives: []*nginx.NgxDirective{
|
||||
{
|
||||
Directive: "listen",
|
||||
Params: "80",
|
||||
},
|
||||
{
|
||||
Directive: "listen",
|
||||
Params: "[::]:80",
|
||||
},
|
||||
{
|
||||
Directive: "server_name",
|
||||
},
|
||||
{
|
||||
Directive: "root",
|
||||
},
|
||||
{
|
||||
Directive: "index",
|
||||
},
|
||||
},
|
||||
Locations: []*nginx.NgxLocation{
|
||||
{
|
||||
Path: "/.well-known/acme-challenge",
|
||||
Content: content,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
"template": _content,
|
||||
"message": "ok",
|
||||
"template": ngxConfig.BuildConfig(),
|
||||
"tokenized": ngxConfig,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func authRequired() gin.HandlerFunc {
|
|||
token = string(tmp)
|
||||
if token == "" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "auth fail",
|
||||
"message": "Authorization failed",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
|
@ -45,7 +45,7 @@ func authRequired() gin.HandlerFunc {
|
|||
|
||||
if n < 1 {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "auth fail",
|
||||
"message": "Authorization failed",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
|
|
|
@ -1,92 +1,100 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/0xJacky/Nginx-UI/server/api"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
"bufio"
|
||||
"github.com/0xJacky/Nginx-UI/server/api"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func InitRouter() *gin.Engine {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
|
||||
r.Use(recovery())
|
||||
r.Use(recovery())
|
||||
|
||||
r.Use(cacheJs())
|
||||
r.Use(cacheJs())
|
||||
|
||||
r.Use(static.Serve("/", mustFS("")))
|
||||
r.Use(static.Serve("/", mustFS("")))
|
||||
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
accept := c.Request.Header.Get("Accept")
|
||||
if strings.Contains(accept, "text/html") {
|
||||
file, _ := mustFS("").Open("index.html")
|
||||
defer file.Close()
|
||||
stat, _ := file.Stat()
|
||||
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
|
||||
bufio.NewReader(file), nil)
|
||||
return
|
||||
}
|
||||
})
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
accept := c.Request.Header.Get("Accept")
|
||||
if strings.Contains(accept, "text/html") {
|
||||
file, _ := mustFS("").Open("index.html")
|
||||
defer file.Close()
|
||||
stat, _ := file.Stat()
|
||||
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
|
||||
bufio.NewReader(file), nil)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
g := r.Group("/api")
|
||||
{
|
||||
g := r.Group("/api")
|
||||
{
|
||||
|
||||
g.GET("settings", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"demo": settings.ServerSettings.Demo,
|
||||
})
|
||||
})
|
||||
g.GET("settings", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"demo": settings.ServerSettings.Demo,
|
||||
})
|
||||
})
|
||||
|
||||
g.GET("install", api.InstallLockCheck)
|
||||
g.POST("install", api.InstallNginxUI)
|
||||
g.GET("install", api.InstallLockCheck)
|
||||
g.POST("install", api.InstallNginxUI)
|
||||
|
||||
g.POST("/login", api.Login)
|
||||
g.DELETE("/logout", api.Logout)
|
||||
g.POST("/login", api.Login)
|
||||
g.DELETE("/logout", api.Logout)
|
||||
|
||||
g := g.Group("/", authRequired())
|
||||
{
|
||||
g.GET("/analytic", api.Analytic)
|
||||
g.GET("/analytic/init", api.GetAnalyticInit)
|
||||
g := g.Group("/", authRequired())
|
||||
{
|
||||
g.GET("analytic", api.Analytic)
|
||||
g.GET("analytic/init", api.GetAnalyticInit)
|
||||
|
||||
g.GET("/users", api.GetUsers)
|
||||
g.GET("/user/:id", api.GetUser)
|
||||
g.POST("/user", api.AddUser)
|
||||
g.POST("/user/:id", api.EditUser)
|
||||
g.DELETE("/user/:id", api.DeleteUser)
|
||||
g.GET("users", api.GetUsers)
|
||||
g.GET("user/:id", api.GetUser)
|
||||
g.POST("user", api.AddUser)
|
||||
g.POST("user/:id", api.EditUser)
|
||||
g.DELETE("user/:id", api.DeleteUser)
|
||||
|
||||
g.GET("domains", api.GetDomains)
|
||||
g.GET("domain/:name", api.GetDomain)
|
||||
g.POST("domain/:name", api.EditDomain)
|
||||
g.POST("domain/:name/enable", api.EnableDomain)
|
||||
g.POST("domain/:name/disable", api.DisableDomain)
|
||||
g.DELETE("domain/:name", api.DeleteDomain)
|
||||
g.GET("domains", api.GetDomains)
|
||||
g.GET("domain/:name", api.GetDomain)
|
||||
|
||||
g.GET("configs", api.GetConfigs)
|
||||
g.GET("config/:name", api.GetConfig)
|
||||
g.POST("config", api.AddConfig)
|
||||
g.POST("config/:name", api.EditConfig)
|
||||
// Modify site configuration directly
|
||||
g.POST("domain/:name", api.EditDomain)
|
||||
|
||||
g.GET("backups", api.GetFileBackupList)
|
||||
g.GET("backup/:id", api.GetFileBackup)
|
||||
// Transform NgxConf to nginx configuration
|
||||
g.POST("ngx/build_config", api.BuildNginxConfig)
|
||||
// Tokenized nginx configuration to NgxConf
|
||||
g.POST("ngx/tokenize_config", api.TokenizeNginxConfig)
|
||||
|
||||
g.GET("template/:name", api.GetTemplate)
|
||||
g.POST("domain/:name/enable", api.EnableDomain)
|
||||
g.POST("domain/:name/disable", api.DisableDomain)
|
||||
g.DELETE("domain/:name", api.DeleteDomain)
|
||||
|
||||
g.GET("cert/issue/:domain", api.IssueCert)
|
||||
g.GET("cert/:domain/info", api.CertInfo)
|
||||
g.GET("configs", api.GetConfigs)
|
||||
g.GET("config/:name", api.GetConfig)
|
||||
g.POST("config", api.AddConfig)
|
||||
g.POST("config/:name", api.EditConfig)
|
||||
|
||||
// 添加域名到自动续期列表
|
||||
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
||||
// 从自动续期列表中删除域名
|
||||
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
||||
//g.GET("backups", api.GetFileBackupList)
|
||||
//g.GET("backup/:id", api.GetFileBackup)
|
||||
|
||||
// pty
|
||||
g.GET("pty", api.Pty)
|
||||
}
|
||||
}
|
||||
g.GET("template", api.GetTemplate)
|
||||
|
||||
return r
|
||||
g.GET("cert/issue/:domain", api.IssueCert)
|
||||
g.GET("cert/:domain/info", api.CertInfo)
|
||||
|
||||
// Add domain to auto-renew cert list
|
||||
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
||||
// Delete domain from auto-renew cert list
|
||||
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
||||
|
||||
// pty
|
||||
g.GET("pty", api.Pty)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
server {
|
||||
listen {{ http_listen_port }};
|
||||
listen [::]:{{ http_listen_port }};
|
||||
|
||||
server_name {{ server_name }};
|
||||
|
||||
location /.well-known {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real_IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
||||
proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
server {
|
||||
listen {{ http_listen_port }};
|
||||
listen [::]:{{ http_listen_port }};
|
||||
|
||||
server_name {{ server_name }};
|
||||
|
||||
rewrite ^(.*)$ https://$host$1 permanent;
|
||||
}
|
||||
|
||||
server {
|
||||
listen {{ https_listen_port }} ssl http2;
|
||||
listen [::]:{{ https_listen_port }} ssl http2;
|
||||
|
||||
server_name {{ server_name }};
|
||||
|
||||
ssl_certificate {{ ssl_certificate }};
|
||||
ssl_certificate_key {{ ssl_certificate_key }};
|
||||
|
||||
location /.well-known {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real_IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
||||
proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package template
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed http-conf https-conf
|
||||
var DistFS embed.FS
|
|
@ -1,185 +1,189 @@
|
|||
package tool
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MyUser You'll need a user or account type that implements acme.User
|
||||
type MyUser struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (u *MyUser) GetEmail() string {
|
||||
return u.Email
|
||||
return u.Email
|
||||
}
|
||||
func (u MyUser) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
return u.Registration
|
||||
}
|
||||
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.key
|
||||
return u.key
|
||||
}
|
||||
|
||||
func AutoCert() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Println("[AutoCert] Recover", err)
|
||||
}
|
||||
}()
|
||||
log.Println("[AutoCert] Start")
|
||||
autoCertList := model.GetAutoCertList()
|
||||
for i := range autoCertList {
|
||||
domain := autoCertList[i].Domain
|
||||
key, err := GetCertInfo(domain)
|
||||
if err != nil {
|
||||
log.Println("GetCertInfo Err", err)
|
||||
// 获取证书信息失败,本次跳过
|
||||
continue
|
||||
}
|
||||
// 未到一个月
|
||||
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
||||
continue
|
||||
}
|
||||
// 过一个月了,重新申请证书
|
||||
err = IssueCert(domain)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Println("[AutoCert] Recover", err)
|
||||
}
|
||||
}()
|
||||
log.Println("[AutoCert] Start")
|
||||
autoCertList := model.GetAutoCertList()
|
||||
for i := range autoCertList {
|
||||
domain := autoCertList[i].Domain
|
||||
key, err := GetCertInfo(domain)
|
||||
if err != nil {
|
||||
log.Println("GetCertInfo Err", err)
|
||||
// 获取证书信息失败,本次跳过
|
||||
continue
|
||||
}
|
||||
// 未到一个月
|
||||
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
||||
continue
|
||||
}
|
||||
// 过一个月了,重新申请证书
|
||||
err = IssueCert(domain)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetCertInfo(domain string) (key *x509.Certificate, err error) {
|
||||
|
||||
var response *http.Response
|
||||
var response *http.Response
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
}).DialContext,
|
||||
DisableKeepAlives: true,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
}).DialContext,
|
||||
DisableKeepAlives: true,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
response, err = client.Get("https://" + domain)
|
||||
response, err = client.Get("https://" + domain)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "get cert info error")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "get cert info error")
|
||||
return
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err = Body.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}(response.Body)
|
||||
defer func(Body io.ReadCloser) {
|
||||
err = Body.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}(response.Body)
|
||||
|
||||
key = response.TLS.PeerCertificates[0]
|
||||
key = response.TLS.PeerCertificates[0]
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
func IssueCert(domain string) error {
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert generate key error")
|
||||
}
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert generate key error")
|
||||
}
|
||||
|
||||
myUser := MyUser{
|
||||
Email: settings.ServerSettings.Email,
|
||||
key: privateKey,
|
||||
}
|
||||
myUser := MyUser{
|
||||
Email: settings.ServerSettings.Email,
|
||||
key: privateKey,
|
||||
}
|
||||
|
||||
config := lego.NewConfig(&myUser)
|
||||
config := lego.NewConfig(&myUser)
|
||||
|
||||
if settings.ServerSettings.Demo {
|
||||
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
}
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
if settings.ServerSettings.Demo {
|
||||
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
}
|
||||
|
||||
// A client facilitates communication with the CA server.
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert new client error")
|
||||
}
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
err = client.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer("",
|
||||
settings.ServerSettings.HTTPChallengePort,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert challenge fail")
|
||||
}
|
||||
// A client facilitates communication with the CA server.
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert new client error")
|
||||
}
|
||||
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return errors.Wrap(err, "issue cert register fail")
|
||||
}
|
||||
myUser.Registration = reg
|
||||
err = client.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer("",
|
||||
settings.ServerSettings.HTTPChallengePort,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert challenge fail")
|
||||
}
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{domain},
|
||||
Bundle: true,
|
||||
}
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert fail to obtain")
|
||||
}
|
||||
saveDir := nginx.GetNginxConfPath("ssl/" + domain)
|
||||
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
|
||||
err = os.Mkdir(saveDir, 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert fail to create")
|
||||
}
|
||||
}
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return errors.Wrap(err, "issue cert register fail")
|
||||
}
|
||||
myUser.Registration = reg
|
||||
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||
certificates.Certificate, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return errors.Wrap(err, "issue cert write fullchain.cer fail")
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
||||
certificates.PrivateKey, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return errors.Wrap(err, "issue cert write key fail")
|
||||
}
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{domain},
|
||||
Bundle: true,
|
||||
}
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert fail to obtain")
|
||||
}
|
||||
saveDir := nginx.GetNginxConfPath("ssl/" + domain)
|
||||
if _, err = os.Stat(saveDir); os.IsNotExist(err) {
|
||||
err = os.Mkdir(saveDir, 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "issue cert fail to create")
|
||||
}
|
||||
}
|
||||
|
||||
nginx.ReloadNginx()
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||
certificates.Certificate, 0644)
|
||||
|
||||
return nil
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return errors.Wrap(err, "issue cert write fullchain.cer fail")
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
||||
certificates.PrivateKey, 0644)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return errors.Wrap(err, "issue cert write key fail")
|
||||
}
|
||||
|
||||
nginx.ReloadNginx()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func (c *NgxConfig) BuildConfig() (content string) {
|
|||
}
|
||||
if directive.Directive == If {
|
||||
server += fmt.Sprintf("%s%s\n", comments, fmtCodeWithIndent(directive.Params, 1))
|
||||
} else {
|
||||
} else if directive.Params != "" {
|
||||
server += fmt.Sprintf("%s\t%s;\n", comments, directive.Orig())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,15 +105,9 @@ func parseDirective(scanner *bufio.Scanner) (d NgxDirective) {
|
|||
return
|
||||
}
|
||||
|
||||
func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error open file in ParseNgxConfig")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
func ParseNgxConfigByScanner(filename string, scanner *bufio.Scanner) (c *NgxConfig, err error) {
|
||||
c = NewNgxConfig(filename)
|
||||
|
||||
for scanner.Scan() {
|
||||
d := parseDirective(scanner)
|
||||
paramsScanner := bufio.NewScanner(strings.NewReader(d.Params))
|
||||
|
@ -142,3 +136,15 @@ func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
|
|||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error open file in ParseNgxConfig")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
return ParseNgxConfigByScanner(filename, scanner)
|
||||
}
|
||||
|
|
|
@ -36,8 +36,6 @@ type NgxDirective struct {
|
|||
Comments string `json:"comments"`
|
||||
}
|
||||
|
||||
type NgxDirectives map[string][]NgxDirective
|
||||
|
||||
type NgxLocation struct {
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue