# Conflicts: # package-lock.json # package.json # server/database.js # src/languages/en.js # src/mixins/socket.jspull/1452/head
commit
04e3394d02
@ -0,0 +1,31 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
CREATE TABLE [status_page](
|
||||||
|
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
[slug] VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
[title] VARCHAR(255) NOT NULL,
|
||||||
|
[description] TEXT,
|
||||||
|
[icon] VARCHAR(255) NOT NULL,
|
||||||
|
[theme] VARCHAR(30) NOT NULL,
|
||||||
|
[published] BOOLEAN NOT NULL DEFAULT 1,
|
||||||
|
[search_engine_index] BOOLEAN NOT NULL DEFAULT 1,
|
||||||
|
[show_tags] BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
[password] VARCHAR,
|
||||||
|
[created_date] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
[modified_date] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX [slug] ON [status_page]([slug]);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE [status_page_cname](
|
||||||
|
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
[status_page_id] INTEGER NOT NULL REFERENCES [status_page]([id]) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
[domain] VARCHAR NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE incident ADD status_page_id INTEGER;
|
||||||
|
ALTER TABLE [group] ADD status_page_id INTEGER;
|
||||||
|
|
||||||
|
COMMIT;
|
@ -0,0 +1,76 @@
|
|||||||
|
const pkg = require("../../package.json");
|
||||||
|
const fs = require("fs");
|
||||||
|
const child_process = require("child_process");
|
||||||
|
const util = require("../../src/util");
|
||||||
|
|
||||||
|
util.polyfill();
|
||||||
|
|
||||||
|
const oldVersion = pkg.version;
|
||||||
|
const version = process.env.VERSION;
|
||||||
|
|
||||||
|
console.log("Beta Version: " + version);
|
||||||
|
|
||||||
|
if (!oldVersion || oldVersion.includes("-beta.")) {
|
||||||
|
console.error("Error: old version should not be a beta version?");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!version || !version.includes("-beta.")) {
|
||||||
|
console.error("invalid version, beta version only");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists = tagExists(version);
|
||||||
|
|
||||||
|
if (! exists) {
|
||||||
|
// Process package.json
|
||||||
|
pkg.version = version;
|
||||||
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
||||||
|
commit(version);
|
||||||
|
tag(version);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log("version tag exists, please delete the tag or use another tag");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit(version) {
|
||||||
|
let msg = "Update to " + version;
|
||||||
|
|
||||||
|
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
|
||||||
|
let stdout = res.stdout.toString().trim();
|
||||||
|
console.log(stdout);
|
||||||
|
|
||||||
|
if (stdout.includes("no changes added to commit")) {
|
||||||
|
throw new Error("commit error");
|
||||||
|
}
|
||||||
|
|
||||||
|
res = child_process.spawnSync("git", ["push", "origin", "master"]);
|
||||||
|
console.log(res.stdout.toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function tag(version) {
|
||||||
|
let res = child_process.spawnSync("git", ["tag", version]);
|
||||||
|
console.log(res.stdout.toString().trim());
|
||||||
|
|
||||||
|
res = child_process.spawnSync("git", ["push", "origin", version]);
|
||||||
|
console.log(res.stdout.toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function tagExists(version) {
|
||||||
|
if (! version) {
|
||||||
|
throw new Error("invalid version");
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = child_process.spawnSync("git", ["tag", "-l", version]);
|
||||||
|
|
||||||
|
return res.stdout.toString().trim() === version;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeDelete(dir) {
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
fs.rmdirSync(dir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
|
||||||
|
const http = require("https"); // or 'https' for https:// URLs
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const platform = process.argv[2];
|
||||||
|
|
||||||
|
if (!platform) {
|
||||||
|
console.error("No platform??");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let arch = null;
|
||||||
|
|
||||||
|
if (platform === "linux/amd64") {
|
||||||
|
arch = "amd64";
|
||||||
|
} else if (platform === "linux/arm64") {
|
||||||
|
arch = "arm64";
|
||||||
|
} else if (platform === "linux/arm/v7") {
|
||||||
|
arch = "arm";
|
||||||
|
} else {
|
||||||
|
console.error("Invalid platform?? " + platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = fs.createWriteStream("cloudflared.deb");
|
||||||
|
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
|
||||||
|
|
||||||
|
function get(url) {
|
||||||
|
http.get(url, function (res) {
|
||||||
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||||
|
console.log("Redirect to " + res.headers.location);
|
||||||
|
get(res.headers.location);
|
||||||
|
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
|
res.pipe(file);
|
||||||
|
|
||||||
|
res.on("end", function () {
|
||||||
|
console.log("Downloaded");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(res.statusCode);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const childProcess = require("child_process");
|
||||||
|
let env = process.env;
|
||||||
|
|
||||||
|
let cmd = process.argv[2];
|
||||||
|
let args = process.argv.slice(3);
|
||||||
|
let replacedArgs = [];
|
||||||
|
|
||||||
|
for (let arg of args) {
|
||||||
|
for (let key in env) {
|
||||||
|
arg = arg.replaceAll(`$${key}`, env[key]);
|
||||||
|
}
|
||||||
|
replacedArgs.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let child = childProcess.spawn(cmd, replacedArgs);
|
||||||
|
child.stdout.pipe(process.stdout);
|
||||||
|
child.stderr.pipe(process.stderr);
|
@ -0,0 +1,6 @@
|
|||||||
|
console.log("Git Push and Publish the release note on github, then press any key to continue");
|
||||||
|
|
||||||
|
process.stdin.setRawMode(true);
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on("data", process.exit.bind(process, 0));
|
||||||
|
|
@ -0,0 +1,48 @@
|
|||||||
|
const child_process = require("child_process");
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const newVersion = process.env.VERSION;
|
||||||
|
|
||||||
|
if (!newVersion) {
|
||||||
|
console.log("Missing version");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWiki(newVersion);
|
||||||
|
|
||||||
|
function updateWiki(newVersion) {
|
||||||
|
const wikiDir = "./tmp/wiki";
|
||||||
|
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
|
||||||
|
|
||||||
|
safeDelete(wikiDir);
|
||||||
|
|
||||||
|
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
|
||||||
|
let content = fs.readFileSync(howToUpdateFilename).toString();
|
||||||
|
|
||||||
|
// Replace the version: https://regex101.com/r/hmj2Bc/1
|
||||||
|
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
|
||||||
|
fs.writeFileSync(howToUpdateFilename, content);
|
||||||
|
|
||||||
|
child_process.spawnSync("git", ["add", "-A"], {
|
||||||
|
cwd: wikiDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], {
|
||||||
|
cwd: wikiDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Pushing to Github");
|
||||||
|
child_process.spawnSync("git", ["push"], {
|
||||||
|
cwd: wikiDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
safeDelete(wikiDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeDelete(dir) {
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
fs.rmdirSync(dir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
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,
|
||||||
|
description: this.description,
|
||||||
|
icon: this.getIcon(),
|
||||||
|
theme: this.theme,
|
||||||
|
published: !!this.published,
|
||||||
|
showTags: !!this.show_tags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async toPublicJSON() {
|
||||||
|
return {
|
||||||
|
slug: this.slug,
|
||||||
|
title: this.title,
|
||||||
|
description: this.description,
|
||||||
|
icon: this.getIcon(),
|
||||||
|
theme: this.theme,
|
||||||
|
published: !!this.published,
|
||||||
|
showTags: !!this.show_tags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async slugToID(slug) {
|
||||||
|
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
||||||
|
slug
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getIcon() {
|
||||||
|
if (!this.icon) {
|
||||||
|
return "/icon.svg";
|
||||||
|
} else {
|
||||||
|
return this.icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = StatusPage;
|
@ -0,0 +1,85 @@
|
|||||||
|
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
|
||||||
|
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
||||||
|
const { io } = require("../server");
|
||||||
|
|
||||||
|
const prefix = "cloudflared_";
|
||||||
|
const cloudflared = new CloudflaredTunnel();
|
||||||
|
|
||||||
|
cloudflared.change = (running, message) => {
|
||||||
|
io.to("cloudflared").emit(prefix + "running", running);
|
||||||
|
io.to("cloudflared").emit(prefix + "message", message);
|
||||||
|
};
|
||||||
|
|
||||||
|
cloudflared.error = (errorMessage) => {
|
||||||
|
io.to("cloudflared").emit(prefix + "errorMessage", errorMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
|
|
||||||
|
socket.on(prefix + "join", async () => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
socket.join("cloudflared");
|
||||||
|
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
|
||||||
|
io.to(socket.userID).emit(prefix + "running", cloudflared.running);
|
||||||
|
io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
|
||||||
|
} catch (error) { }
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on(prefix + "leave", async () => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
socket.leave("cloudflared");
|
||||||
|
} catch (error) { }
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on(prefix + "start", async (token) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
if (token && typeof token === "string") {
|
||||||
|
await setSetting("cloudflaredTunnelToken", token);
|
||||||
|
cloudflared.token = token;
|
||||||
|
} else {
|
||||||
|
cloudflared.token = null;
|
||||||
|
}
|
||||||
|
cloudflared.start();
|
||||||
|
} catch (error) { }
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on(prefix + "stop", async (currentPassword, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
|
cloudflared.stop();
|
||||||
|
} catch (error) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on(prefix + "removeToken", async () => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
await setSetting("cloudflaredTunnelToken", "");
|
||||||
|
} catch (error) { }
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.autoStart = async (token) => {
|
||||||
|
if (!token) {
|
||||||
|
token = await setting("cloudflaredTunnelToken");
|
||||||
|
} else {
|
||||||
|
// Override the current token via args or env var
|
||||||
|
await setSetting("cloudflaredTunnelToken", token);
|
||||||
|
console.log("Use cloudflared token from args or env var");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
console.log("Start cloudflared");
|
||||||
|
cloudflared.token = token;
|
||||||
|
cloudflared.start();
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h4 class="mt-4">Cloudflare Tunnel</h4>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<div>
|
||||||
|
cloudflared:
|
||||||
|
<span v-if="installed === true" class="text-primary">{{ $t("Installed") }}</span>
|
||||||
|
<span v-else-if="installed === false" class="text-danger">{{ $t("Not installed") }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ $t("Status") }}:
|
||||||
|
<span v-if="running" class="text-primary">{{ $t("Running") }}</span>
|
||||||
|
<span v-else-if="!running" class="text-danger">{{ $t("Not running") }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="false">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="errorMessage" class="mt-3">
|
||||||
|
Message:
|
||||||
|
<textarea v-model="errorMessage" class="form-control" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="installed === false">(Download cloudflared from <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/">Cloudflare Website</a>)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- If installed show token input -->
|
||||||
|
<div v-if="installed" class="mb-2">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label" for="cloudflareTunnelToken">
|
||||||
|
Cloudflare Tunnel {{ $t("Token") }}
|
||||||
|
</label>
|
||||||
|
<HiddenInput
|
||||||
|
id="cloudflareTunnelToken"
|
||||||
|
v-model="cloudflareTunnelToken"
|
||||||
|
autocomplete="one-time-code"
|
||||||
|
:readonly="running"
|
||||||
|
/>
|
||||||
|
<div class="form-text">
|
||||||
|
<div v-if="cloudflareTunnelToken" class="mb-3">
|
||||||
|
<span v-if="!running" class="remove-token" @click="removeToken">{{ $t("Remove Token") }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Don't know how to get the token? Please read the guide:<br />
|
||||||
|
<a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel" target="_blank">
|
||||||
|
https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy-with-Cloudflare-Tunnel
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button v-if="!running" class="btn btn-primary" type="submit" @click="start">
|
||||||
|
{{ $t("Start") }} cloudflared
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-if="running" class="btn btn-danger" type="submit" @click="$refs.confirmStop.show();">
|
||||||
|
{{ $t("Stop") }} cloudflared
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Confirm ref="confirmStop" btn-style="btn-danger" :yes-text="$t('Stop') + ' cloudflared'" :no-text="$t('Cancel')" @yes="stop">
|
||||||
|
The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<label for="current-password2" class="form-label">
|
||||||
|
{{ $t("Current Password") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="current-password2"
|
||||||
|
v-model="currentPassword"
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Confirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mt-4">Other Software</h4>
|
||||||
|
<div>
|
||||||
|
For example: nginx, Apache and Traefik. <br />
|
||||||
|
Please read <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy</a>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HiddenInput from "../../components/HiddenInput.vue";
|
||||||
|
import Confirm from "../Confirm.vue";
|
||||||
|
|
||||||
|
const prefix = "cloudflared_";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
HiddenInput,
|
||||||
|
Confirm
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
// See /src/mixins/socket.js
|
||||||
|
return this.$root.cloudflared;
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$root.getSocket().emit(prefix + "join");
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
this.$root.getSocket().emit(prefix + "leave");
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
start() {
|
||||||
|
this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeToken() {
|
||||||
|
this.$root.getSocket().emit(prefix + "removeToken");
|
||||||
|
this.cloudflareTunnelToken = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.remove-token {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="slide-fade" appear>
|
||||||
|
<div>
|
||||||
|
<h1 class="mb-3">
|
||||||
|
{{ $t("Add New Status Page") }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<form @submit.prevent="submit">
|
||||||
|
<div class="shadow-box">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">{{ $t("Name") }}</label>
|
||||||
|
<input id="name" v-model="title" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="slug" class="form-label">{{ $t("Slug") }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span id="basic-addon3" class="input-group-text">/status/</span>
|
||||||
|
<input id="slug" v-model="slug" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
<ul>
|
||||||
|
<li>{{ $t("Accept characters:") }} <mark>a-z</mark> <mark>0-9</mark> <mark>-</mark></li>
|
||||||
|
<li>{{ $t("Start or end with") }} <mark>a-z</mark> <mark>0-9</mark> only</li>
|
||||||
|
<li>{{ $t("No consecutive dashes") }} <mark>--</mark></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 mb-1">
|
||||||
|
<button id="monitor-submit-btn" class="btn btn-primary w-100" type="submit" :disabled="processing">{{ $t("Next") }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: "",
|
||||||
|
slug: "",
|
||||||
|
processing: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submit() {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
this.$root.getSocket().emit("addStatusPage", this.title, this.slug, (res) => {
|
||||||
|
this.processing = false;
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
location.href = "/status/" + this.slug + "?edit";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (res.msg.includes("UNIQUE constraint")) {
|
||||||
|
this.$root.toastError(this.$t("The slug is already taken. Please choose another slug."));
|
||||||
|
} else {
|
||||||
|
this.$root.toastRes(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.shadow-box {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,118 @@
|
|||||||
|
<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("New Status Page") }}</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow-box">
|
||||||
|
<template v-if="$root.statusPageListLoaded">
|
||||||
|
<span v-if="Object.keys($root.statusPageList).length === 0" class="d-flex align-items-center justify-content-center my-3">
|
||||||
|
No status pages
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- use <a> instead of <router-link>, because the heartbeat won't load. -->
|
||||||
|
<a v-for="statusPage in $root.statusPageList" :key="statusPage.slug" :href="'/status/' + statusPage.slug" class="item">
|
||||||
|
<img :src="icon(statusPage.icon)" alt class="logo me-2" />
|
||||||
|
<div class="info">
|
||||||
|
<div class="title">{{ statusPage.title }}</div>
|
||||||
|
<div class="slug">/status/{{ statusPage.slug }}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<div v-else class="d-flex align-items-center justify-content-center my-3 spinner">
|
||||||
|
<font-awesome-icon icon="spinner" size="2x" spin />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { getResBaseURL } from "../util-frontend";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
icon(icon) {
|
||||||
|
if (icon === "/icon.svg") {
|
||||||
|
return icon;
|
||||||
|
} else {
|
||||||
|
return getResBaseURL() + icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all ease-in-out 0.15s;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $highlight-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #cdf8f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
$logo-width: 70px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: $logo-width;
|
||||||
|
|
||||||
|
// Better when the image is loading
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slug {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
.item {
|
||||||
|
&:hover {
|
||||||
|
background-color: $dark-bg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $dark-bg2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- Desktop header -->
|
||||||
|
<header v-if="! $root.isMobile" class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom">
|
||||||
|
<router-link to="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
|
||||||
|
<object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg" />
|
||||||
|
<span class="fs-4 title">Uptime Kuma</span>
|
||||||
|
</router-link>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Mobile header -->
|
||||||
|
<header v-else class="d-flex flex-wrap justify-content-center pt-2 pb-2 mb-3">
|
||||||
|
<router-link to="/dashboard" class="d-flex align-items-center text-dark text-decoration-none">
|
||||||
|
<object class="bi" width="40" height="40" data="/icon.svg" />
|
||||||
|
<span class="fs-4 title ms-2">Uptime Kuma</span>
|
||||||
|
</router-link>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div>
|
||||||
|
<strong>🐻 {{ $t("Page Not Found") }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="guide">
|
||||||
|
Most likely causes:
|
||||||
|
<ul>
|
||||||
|
<li>The resource is no longer available.</li>
|
||||||
|
<li>There might be a typing error in the address.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
What you can try:<br />
|
||||||
|
<ul>
|
||||||
|
<li>Retype the address.</li>
|
||||||
|
<li><a href="#" class="go-back" @click="goBack()">Go back to the previous page.</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
async mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goBack() {
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
|
.go-back {
|
||||||
|
text-decoration: none;
|
||||||
|
color: $primary !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 50px;
|
||||||
|
padding-top: 30px;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide {
|
||||||
|
max-width: 800px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
header {
|
||||||
|
background-color: $dark-header-bg;
|
||||||
|
border-bottom-color: $dark-header-bg !important;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #f0f6fc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-nav {
|
||||||
|
background-color: $dark-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in new issue