mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-17 11:44:38 -05:00
[new status page] wip
This commit is contained in:
parent
ae14ad5a84
commit
50d6e888c2
@ -10,7 +10,9 @@ CREATE TABLE [status_page](
|
|||||||
[published] BOOLEAN NOT NULL DEFAULT 1,
|
[published] BOOLEAN NOT NULL DEFAULT 1,
|
||||||
[search_engine_index] BOOLEAN NOT NULL DEFAULT 1,
|
[search_engine_index] BOOLEAN NOT NULL DEFAULT 1,
|
||||||
[show_tags] BOOLEAN NOT NULL DEFAULT 0,
|
[show_tags] BOOLEAN NOT NULL DEFAULT 0,
|
||||||
[password] VARCHAR
|
[password] VARCHAR,
|
||||||
|
[date_created] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
[date_modified] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX [slug] ON [status_page]([slug]);
|
CREATE UNIQUE INDEX [slug] ON [status_page]([slug]);
|
||||||
|
44
server/model/status_page.js
Normal file
44
server/model/status_page.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
const { R } = require("redbean-node");
|
||||||
|
|
||||||
|
class StatusPage extends BeanModel {
|
||||||
|
|
||||||
|
static async sendStatusPageList(io, socket) {
|
||||||
|
let result = {};
|
||||||
|
|
||||||
|
let list = await R.findAll("status_page", " ORDER BY title ");
|
||||||
|
|
||||||
|
for (let item of list) {
|
||||||
|
result[item.id] = await item.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
io.to(socket.userID).emit("statusPageList", result);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
async toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
slug: this.slug,
|
||||||
|
title: this.title,
|
||||||
|
icon: this.icon,
|
||||||
|
theme: this.theme,
|
||||||
|
published: !!this.published,
|
||||||
|
showTags: !!this.show_tags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async toPublicJSON() {
|
||||||
|
return {
|
||||||
|
slug: this.slug,
|
||||||
|
title: this.title,
|
||||||
|
icon: this.icon,
|
||||||
|
theme: this.theme,
|
||||||
|
published: !!this.published,
|
||||||
|
showTags: !!this.show_tags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = StatusPage;
|
@ -83,33 +83,28 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Status Page Config
|
// Status Page Config
|
||||||
router.get("/api/status-page/config", async (_request, response) => {
|
router.get("/api/status-page/config/:slug", async (request, response) => {
|
||||||
allowDevAllOrigin(response);
|
allowDevAllOrigin(response);
|
||||||
|
let slug = request.params.slug;
|
||||||
|
|
||||||
let config = await getSettings("statusPage");
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
|
slug
|
||||||
|
]);
|
||||||
|
|
||||||
if (! config.statusPageTheme) {
|
if (!statusPage) {
|
||||||
config.statusPageTheme = "light";
|
response.statusCode = 404;
|
||||||
|
response.json({
|
||||||
|
msg: "Not Found"
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! config.statusPagePublished) {
|
response.json(await statusPage.toPublicJSON());
|
||||||
config.statusPagePublished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! config.statusPageTags) {
|
|
||||||
config.statusPageTags = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! config.title) {
|
|
||||||
config.title = "Uptime Kuma";
|
|
||||||
}
|
|
||||||
|
|
||||||
response.json(config);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Status Page - Get the current Incident
|
// Status Page - Get the current Incident
|
||||||
// Can fetch only if published
|
// Can fetch only if published
|
||||||
router.get("/api/status-page/incident", async (_, response) => {
|
router.get("/api/status-page/incident/:slug", async (_, response) => {
|
||||||
allowDevAllOrigin(response);
|
allowDevAllOrigin(response);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -133,7 +128,7 @@ router.get("/api/status-page/incident", async (_, response) => {
|
|||||||
|
|
||||||
// Status Page - Monitor List
|
// Status Page - Monitor List
|
||||||
// Can fetch only if published
|
// Can fetch only if published
|
||||||
router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request, response) => {
|
router.get("/api/status-page/monitor-list/:slug", cache("5 minutes"), async (_request, response) => {
|
||||||
allowDevAllOrigin(response);
|
allowDevAllOrigin(response);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -172,7 +167,7 @@ router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request,
|
|||||||
|
|
||||||
// Status Page Polling Data
|
// Status Page Polling Data
|
||||||
// Can fetch only if published
|
// Can fetch only if published
|
||||||
router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, response) => {
|
router.get("/api/status-page/heartbeat/:slug", cache("5 minutes"), async (_request, response) => {
|
||||||
allowDevAllOrigin(response);
|
allowDevAllOrigin(response);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -132,6 +132,7 @@ const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sen
|
|||||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||||
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
|
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
|
||||||
const TwoFA = require("./2fa");
|
const TwoFA = require("./2fa");
|
||||||
|
const StatusPage = require("./model/status_page");
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
@ -1414,6 +1415,8 @@ async function afterLogin(socket, user) {
|
|||||||
for (let monitorID in monitorList) {
|
for (let monitorID in monitorList) {
|
||||||
await Monitor.sendStats(io, monitorID, user.id);
|
await Monitor.sendStats(io, monitorID, user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await StatusPage.sendStatusPageList(io, socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMonitorJSONList(userID) {
|
async function getMonitorJSONList(userID) {
|
||||||
|
@ -92,6 +92,10 @@ textarea.form-control {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-dark {
|
||||||
|
background-color: #161B22;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 550px) {
|
@media (max-width: 550px) {
|
||||||
.table-shadow-box {
|
.table-shadow-box {
|
||||||
padding: 10px !important;
|
padding: 10px !important;
|
||||||
@ -167,7 +171,7 @@ textarea.form-control {
|
|||||||
background-color: #232f3b;
|
background-color: #232f3b;
|
||||||
}
|
}
|
||||||
|
|
||||||
a,
|
a:not(.btn),
|
||||||
.table,
|
.table,
|
||||||
.nav-link {
|
.nav-link {
|
||||||
color: $dark-font-color;
|
color: $dark-font-color;
|
||||||
|
@ -34,6 +34,8 @@ import {
|
|||||||
faAward,
|
faAward,
|
||||||
faLink,
|
faLink,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
|
faPen,
|
||||||
|
faExternalLinkSquareAlt,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
@ -67,6 +69,8 @@ library.add(
|
|||||||
faAward,
|
faAward,
|
||||||
faLink,
|
faLink,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
|
faPen,
|
||||||
|
faExternalLinkSquareAlt,
|
||||||
);
|
);
|
||||||
|
|
||||||
export { FontAwesomeIcon };
|
export { FontAwesomeIcon };
|
||||||
|
@ -183,7 +183,7 @@ export default {
|
|||||||
"Edit Status Page": "Edit Status Page",
|
"Edit Status Page": "Edit Status Page",
|
||||||
"Go to Dashboard": "Go to Dashboard",
|
"Go to Dashboard": "Go to Dashboard",
|
||||||
"Status Page": "Status Page",
|
"Status Page": "Status Page",
|
||||||
"Status Pages": "Status Page",
|
"Status Pages": "Status Pages",
|
||||||
defaultNotificationName: "My {notification} Alert ({number})",
|
defaultNotificationName: "My {notification} Alert ({number})",
|
||||||
here: "here",
|
here: "here",
|
||||||
Required: "Required",
|
Required: "Required",
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
|
|
||||||
<ul class="nav nav-pills">
|
<ul class="nav nav-pills">
|
||||||
<li class="nav-item me-2">
|
<li class="nav-item me-2">
|
||||||
<a href="/status" class="nav-link status-page">
|
<router-link to="/manage-status-page" class="nav-link">
|
||||||
<font-awesome-icon icon="stream" /> {{ $t("Status Page") }}
|
<font-awesome-icon icon="stream" /> {{ $t("Status Pages") }}
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="$root.loggedIn" class="nav-item me-2">
|
<li v-if="$root.loggedIn" class="nav-item me-2">
|
||||||
<router-link to="/dashboard" class="nav-link">
|
<router-link to="/dashboard" class="nav-link">
|
||||||
|
@ -33,6 +33,7 @@ export default {
|
|||||||
uptimeList: { },
|
uptimeList: { },
|
||||||
tlsInfoList: {},
|
tlsInfoList: {},
|
||||||
notificationList: [],
|
notificationList: [],
|
||||||
|
statusPageList: [],
|
||||||
connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
|
connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -103,6 +104,11 @@ export default {
|
|||||||
this.notificationList = data;
|
this.notificationList = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("statusPageList", (data) => {
|
||||||
|
console.log(data);
|
||||||
|
this.statusPageList = data;
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("heartbeat", (data) => {
|
socket.on("heartbeat", (data) => {
|
||||||
if (! (data.monitorID in this.heartbeatList)) {
|
if (! (data.monitorID in this.heartbeatList)) {
|
||||||
this.heartbeatList[data.monitorID] = [];
|
this.heartbeatList[data.monitorID] = [];
|
||||||
|
104
src/pages/ManageStatusPage.vue
Normal file
104
src/pages/ManageStatusPage.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
|
<div>
|
||||||
|
<h1 class="mb-3">
|
||||||
|
{{ $t("Status Pages") }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<router-link to="/add-status-page" class="btn btn-primary mb-3"><font-awesome-icon icon="plus" /> {{ $t("Add New Status Page") }}</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow-box">
|
||||||
|
<div v-for="statusPage in $root.statusPageList" class="item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="title">{{ statusPage.title }}</div>
|
||||||
|
<div class="slug">/status/{{ statusPage.slug }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 col-xl-5">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a target="_blank" :href="'/status/' + statusPage.slug" class="btn btn-dark">
|
||||||
|
<font-awesome-icon icon="external-link-square-alt" /><br />
|
||||||
|
{{ $t("Manage") }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<router-link to="/" class="btn btn-danger">
|
||||||
|
<font-awesome-icon icon="trash" /><br />
|
||||||
|
{{ $t("Delete") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 13px 15px 10px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all ease-in-out 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $highlight-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #cdf8f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slug {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
//margin-top: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
.item {
|
||||||
|
&:hover {
|
||||||
|
background-color: $dark-bg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $dark-bg2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -167,6 +167,8 @@ footer {
|
|||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
padding: 0.7em 1em;
|
padding: 0.7em 1em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-left-width: 0;
|
||||||
|
transition: all ease-in-out 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item:hover {
|
.menu-item:hover {
|
||||||
|
@ -247,6 +247,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
slug: null,
|
||||||
enableEditMode: false,
|
enableEditMode: false,
|
||||||
enableEditIncidentMode: false,
|
enableEditIncidentMode: false,
|
||||||
hasToken: false,
|
hasToken: false,
|
||||||
@ -296,15 +297,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isPublished() {
|
isPublished() {
|
||||||
return this.config.statusPagePublished;
|
return this.config.published;
|
||||||
},
|
},
|
||||||
|
|
||||||
theme() {
|
theme() {
|
||||||
return this.config.statusPageTheme;
|
return this.config.theme;
|
||||||
},
|
},
|
||||||
|
|
||||||
tagsVisible() {
|
tagsVisible() {
|
||||||
return this.config.statusPageTags
|
return this.config.showTags;
|
||||||
},
|
},
|
||||||
|
|
||||||
logoClass() {
|
logoClass() {
|
||||||
@ -378,8 +379,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Set Theme
|
// Set Theme
|
||||||
"config.statusPageTheme"() {
|
"config.theme"() {
|
||||||
this.$root.statusPageTheme = this.config.statusPageTheme;
|
this.$root.statusPageTheme = this.config.theme;
|
||||||
this.loadedTheme = true;
|
this.loadedTheme = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -409,7 +410,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
axios.get("/api/status-page/config").then((res) => {
|
this.slug = this.$route.params.slug;
|
||||||
|
|
||||||
|
if (!this.slug) {
|
||||||
|
this.slug = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get("/api/status-page/config/" + this.slug).then((res) => {
|
||||||
this.config = res.data;
|
this.config = res.data;
|
||||||
|
|
||||||
if (this.config.logo) {
|
if (this.config.logo) {
|
||||||
@ -417,13 +424,13 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
axios.get("/api/status-page/incident").then((res) => {
|
axios.get("/api/status-page/incident/" + this.slug).then((res) => {
|
||||||
if (res.data.ok) {
|
if (res.data.ok) {
|
||||||
this.incident = res.data.incident;
|
this.incident = res.data.incident;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
axios.get("/api/status-page/monitor-list").then((res) => {
|
axios.get("/api/status-page/monitor-list/" + this.slug).then((res) => {
|
||||||
this.$root.publicGroupList = res.data;
|
this.$root.publicGroupList = res.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -438,7 +445,7 @@ export default {
|
|||||||
updateHeartbeatList() {
|
updateHeartbeatList() {
|
||||||
// If editMode, it will use the data from websocket.
|
// If editMode, it will use the data from websocket.
|
||||||
if (! this.editMode) {
|
if (! this.editMode) {
|
||||||
axios.get("/api/status-page/heartbeat").then((res) => {
|
axios.get("/api/status-page/heartbeat/" + this.slug).then((res) => {
|
||||||
this.$root.heartbeatList = res.data.heartbeatList;
|
this.$root.heartbeatList = res.data.heartbeatList;
|
||||||
this.$root.uptimeList = res.data.uptimeList;
|
this.$root.uptimeList = res.data.uptimeList;
|
||||||
this.loadedData = true;
|
this.loadedData = true;
|
||||||
@ -485,10 +492,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
changeTheme(name) {
|
changeTheme(name) {
|
||||||
this.config.statusPageTheme = name;
|
this.config.theme = name;
|
||||||
},
|
},
|
||||||
changeTagsVisibilty(newState) {
|
changeTagsVisibilty(newState) {
|
||||||
this.config.statusPageTags = newState;
|
this.config.showTags = newState;
|
||||||
|
|
||||||
// On load, the status page will not include tags if it's not enabled for security reasons
|
// On load, the status page will not include tags if it's not enabled for security reasons
|
||||||
// Which means if we enable tags, it won't show in the UI until saved
|
// Which means if we enable tags, it won't show in the UI until saved
|
||||||
@ -501,9 +508,9 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...monitor,
|
...monitor,
|
||||||
tags: newState ? this.$root.monitorList[monitor.id].tags : []
|
tags: newState ? this.$root.monitorList[monitor.id].tags : []
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import MonitorHistory from "./components/settings/MonitorHistory.vue";
|
|||||||
import Security from "./components/settings/Security.vue";
|
import Security from "./components/settings/Security.vue";
|
||||||
import Backup from "./components/settings/Backup.vue";
|
import Backup from "./components/settings/Backup.vue";
|
||||||
import About from "./components/settings/About.vue";
|
import About from "./components/settings/About.vue";
|
||||||
|
import ManageStatusPage from "./pages/ManageStatusPage.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -98,6 +99,10 @@ const routes = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/manage-status-page",
|
||||||
|
component: ManageStatusPage,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -114,6 +119,10 @@ const routes = [
|
|||||||
path: "/status",
|
path: "/status",
|
||||||
component: StatusPage,
|
component: StatusPage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/status/:slug",
|
||||||
|
component: StatusPage,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
|
Loading…
Reference in New Issue
Block a user