mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-26 19:36:29 +03:00
Manage domain names
This commit is contained in:
parent
0b9b5102ec
commit
c9fa183712
6 changed files with 157 additions and 12 deletions
|
@ -10,7 +10,7 @@ class StatusPage extends BeanModel {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async loadDomainMappingList() {
|
static async loadDomainMappingList() {
|
||||||
this.domainMappingList = await R.getAssoc(`
|
StatusPage.domainMappingList = await R.getAssoc(`
|
||||||
SELECT domain, slug
|
SELECT domain, slug
|
||||||
FROM status_page, status_page_cname
|
FROM status_page, status_page_cname
|
||||||
WHERE status_page.id = status_page_cname.status_page_id
|
WHERE status_page.id = status_page_cname.status_page_id
|
||||||
|
@ -30,7 +30,46 @@ class StatusPage extends BeanModel {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDomainList() {
|
async updateDomainNameList(domainNameList) {
|
||||||
|
|
||||||
|
if (!Array.isArray(domainNameList)) {
|
||||||
|
throw new Error("Invalid array");
|
||||||
|
}
|
||||||
|
|
||||||
|
let trx = await R.begin();
|
||||||
|
|
||||||
|
await trx.exec("DELETE FROM status_page_cname WHERE status_page_id = ?", [
|
||||||
|
this.id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let domain of domainNameList) {
|
||||||
|
if (typeof domain !== "string") {
|
||||||
|
throw new Error("Invalid domain");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domain.trim() === "") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the domain name is used in another status page, delete it
|
||||||
|
await trx.exec("DELETE FROM status_page_cname WHERE domain = ?", [
|
||||||
|
domain,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mapping = trx.dispense("status_page_cname");
|
||||||
|
mapping.status_page_id = this.id;
|
||||||
|
mapping.domain = domain;
|
||||||
|
await trx.store(mapping);
|
||||||
|
}
|
||||||
|
await trx.commit();
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDomainNameList() {
|
||||||
let domainList = [];
|
let domainList = [];
|
||||||
for (let domain in StatusPage.domainMappingList) {
|
for (let domain in StatusPage.domainMappingList) {
|
||||||
let s = StatusPage.domainMappingList[domain];
|
let s = StatusPage.domainMappingList[domain];
|
||||||
|
@ -52,7 +91,7 @@ class StatusPage extends BeanModel {
|
||||||
theme: this.theme,
|
theme: this.theme,
|
||||||
published: !!this.published,
|
published: !!this.published,
|
||||||
showTags: !!this.show_tags,
|
showTags: !!this.show_tags,
|
||||||
domainList: this.getDomainList(),
|
domainNameList: this.getDomainNameList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,15 +85,35 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("getStatusPage", async (slug, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
|
slug
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!statusPage) {
|
||||||
|
throw new Error("No slug?");
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
config: await statusPage.toJSON(),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Save Status Page
|
// Save Status Page
|
||||||
// imgDataUrl Only Accept PNG!
|
// imgDataUrl Only Accept PNG!
|
||||||
socket.on("saveStatusPage", async (slug, config, imgDataUrl, publicGroupList, callback) => {
|
socket.on("saveStatusPage", async (slug, config, imgDataUrl, publicGroupList, callback) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
checkSlug(config.slug);
|
|
||||||
|
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
apicache.clear();
|
|
||||||
|
|
||||||
// Save Config
|
// Save Config
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
|
@ -104,6 +124,8 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
throw new Error("No slug?");
|
throw new Error("No slug?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkSlug(config.slug);
|
||||||
|
|
||||||
const header = "data:image/png;base64,";
|
const header = "data:image/png;base64,";
|
||||||
|
|
||||||
// Check logo format
|
// Check logo format
|
||||||
|
@ -137,6 +159,9 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
|
|
||||||
await R.store(statusPage);
|
await R.store(statusPage);
|
||||||
|
|
||||||
|
await statusPage.updateDomainNameList(config.domainNameList);
|
||||||
|
await StatusPage.loadDomainMappingList();
|
||||||
|
|
||||||
// Save Public Group List
|
// Save Public Group List
|
||||||
const groupIDList = [];
|
const groupIDList = [];
|
||||||
let groupOrder = 1;
|
let groupOrder = 1;
|
||||||
|
@ -193,6 +218,8 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
await setSetting("entryPage", server.entryPage, "general");
|
await setSetting("entryPage", server.entryPage, "general");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apicache.clear();
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
publicGroupList,
|
publicGroupList,
|
||||||
|
|
|
@ -22,6 +22,18 @@ textarea.form-control {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-group {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
.list-group-item {
|
||||||
|
background-color: $dark-bg;
|
||||||
|
color: $dark-font-color;
|
||||||
|
border-color: $dark-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #ccc;
|
background: #ccc;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
@ -412,6 +424,10 @@ textarea.form-control {
|
||||||
background-color: rgba(239, 239, 239, 0.7);
|
background-color: rgba(239, 239, 239, 0.7);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&.no-bg {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0 solid #eee;
|
outline: 0 solid #eee;
|
||||||
background-color: rgba(245, 245, 245, 0.9);
|
background-color: rgba(245, 245, 245, 0.9);
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {
|
||||||
faExternalLinkSquareAlt,
|
faExternalLinkSquareAlt,
|
||||||
faSpinner,
|
faSpinner,
|
||||||
faUndo,
|
faUndo,
|
||||||
|
faPlusCircle,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
@ -75,6 +76,7 @@ library.add(
|
||||||
faExternalLinkSquareAlt,
|
faExternalLinkSquareAlt,
|
||||||
faSpinner,
|
faSpinner,
|
||||||
faUndo,
|
faUndo,
|
||||||
|
faPlusCircle,
|
||||||
);
|
);
|
||||||
|
|
||||||
export { FontAwesomeIcon };
|
export { FontAwesomeIcon };
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<StatusPage v-if="statusPageSlug" override-slug="statusPageSlug" />
|
<StatusPage v-if="statusPageSlug" :override-slug="statusPageSlug" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export default {
|
||||||
if (res.type === "statusPageMatchedDomain") {
|
if (res.type === "statusPageMatchedDomain") {
|
||||||
this.statusPageSlug = res.statusPageSlug;
|
this.statusPageSlug = res.statusPageSlug;
|
||||||
|
|
||||||
} else if (res.type === "entryPage") { // Dev only
|
} else if (res.type === "entryPage") { // Dev only. For production, the logic is in the server side
|
||||||
const entryPage = res.entryPage;
|
const entryPage = res.entryPage;
|
||||||
|
|
||||||
if (entryPage === "statusPage") {
|
if (entryPage === "statusPage") {
|
||||||
|
|
|
@ -36,9 +36,19 @@
|
||||||
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
|
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Domain Name List -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="cname" class="form-label">Domain Names</label>
|
<label class="form-label">
|
||||||
<textarea id="cname" v-model="config.domanNames" rows="3" class="form-control" :placeholder="domainNamesPlaceholder"></textarea>
|
Domain Names
|
||||||
|
<font-awesome-icon icon="plus-circle" class="btn-add-domain action text-primary" @click="addDomainField" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<ul class="list-group domain-name-list">
|
||||||
|
<li v-for="(domain, index) in config.domainNameList" :key="index" class="list-group-item">
|
||||||
|
<input v-model="config.domainNameList[index]" type="text" class="no-bg domain-input" placeholder="example.com" />
|
||||||
|
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="removeDomain(index)" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="danger-zone">
|
<div class="danger-zone">
|
||||||
|
@ -305,7 +315,6 @@ export default {
|
||||||
loadedData: false,
|
loadedData: false,
|
||||||
baseURL: "",
|
baseURL: "",
|
||||||
clickedEditButton: false,
|
clickedEditButton: false,
|
||||||
domainNamesPlaceholder: "example1.com\nexample2.com\n..."
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -400,6 +409,22 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If connected to the socket and logged in, request private data of this statusPage
|
||||||
|
* @param connected
|
||||||
|
*/
|
||||||
|
"$root.loggedIn"(loggedIn) {
|
||||||
|
if (loggedIn) {
|
||||||
|
this.$root.getSocket().emit("getStatusPage", this.slug, (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
this.config = res.config;
|
||||||
|
} else {
|
||||||
|
toast.error(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selected a monitor and add to the list.
|
* Selected a monitor and add to the list.
|
||||||
*/
|
*/
|
||||||
|
@ -469,6 +494,10 @@ export default {
|
||||||
axios.get("/api/status-page/" + this.slug).then((res) => {
|
axios.get("/api/status-page/" + this.slug).then((res) => {
|
||||||
this.config = res.data.config;
|
this.config = res.data.config;
|
||||||
|
|
||||||
|
if (!this.config.domainNameList) {
|
||||||
|
this.config.domainNameList = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.config.icon) {
|
if (this.config.icon) {
|
||||||
this.imgDataUrl = this.config.icon;
|
this.imgDataUrl = this.config.icon;
|
||||||
}
|
}
|
||||||
|
@ -586,6 +615,10 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addDomainField() {
|
||||||
|
this.config.domainNameList.push("");
|
||||||
|
},
|
||||||
|
|
||||||
discard() {
|
discard() {
|
||||||
location.href = "/status/" + this.slug;
|
location.href = "/status/" + this.slug;
|
||||||
},
|
},
|
||||||
|
@ -668,6 +701,10 @@ export default {
|
||||||
return dayjs.utc(date).fromNow();
|
return dayjs.utc(date).fromNow();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeDomain(index) {
|
||||||
|
this.config.domainNameList.splice(index, 1);
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -733,6 +770,7 @@ h1 {
|
||||||
|
|
||||||
.sidebar-footer {
|
.sidebar-footer {
|
||||||
border-top: 1px solid #ededed;
|
border-top: 1px solid #ededed;
|
||||||
|
border-right: 1px solid #ededed;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
|
@ -740,6 +778,8 @@ h1 {
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,10 +866,31 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-footer {
|
.sidebar-footer {
|
||||||
|
border-right-color: $dark-border-color;
|
||||||
border-top-color: $dark-border-color;
|
border-top-color: $dark-border-color;
|
||||||
background-color: $dark-header-bg;
|
background-color: $dark-header-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.domain-name-list {
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 0 10px 10px;
|
||||||
|
|
||||||
|
.domain-input {
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: $dark-font-color;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #1d2634;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue