[new status page] wip

pull/863/head
Louis Lam 3 years ago
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]);

@ -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"
if (! config.statusPagePublished) { });
config.statusPagePublished = true; return;
}
if (! config.statusPageTags) {
config.statusPageTags = false;
}
if (! config.title) {
config.title = "Uptime Kuma";
} }
response.json(config); response.json(await statusPage.toPublicJSON());
}); });
// 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] = [];

@ -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…
Cancel
Save