mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-01 11:22:34 +02:00
Merge eae0595353
into 839ead80cc
This commit is contained in:
commit
8c2a7bd2d7
9 changed files with 1703 additions and 3819 deletions
|
@ -8,5 +8,8 @@
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC"
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"update-language-files": "file:"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
4955
package-lock.json
generated
4955
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -141,6 +141,7 @@
|
||||||
"tcp-ping": "~0.1.1",
|
"tcp-ping": "~0.1.1",
|
||||||
"thirty-two": "~1.0.2",
|
"thirty-two": "~1.0.2",
|
||||||
"tough-cookie": "~4.1.3",
|
"tough-cookie": "~4.1.3",
|
||||||
|
"uptime-kuma": "file:",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -388,7 +388,6 @@ let needSetup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("login", async (data, callback) => {
|
socket.on("login", async (data, callback) => {
|
||||||
const clientIP = await server.getClientIP(socket);
|
const clientIP = await server.getClientIP(socket);
|
||||||
|
|
||||||
|
@ -693,6 +692,16 @@ let needSetup = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("fetchIncidentReports", async () => {
|
||||||
|
try {
|
||||||
|
const incidentReports = await R.findAll("incident");
|
||||||
|
socket.emit("incidentReports", incidentReports);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
socket.emit("incidentReportsError", { error: "Failed to fetch incident reports" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ***************************
|
// ***************************
|
||||||
// Auth Only API
|
// Auth Only API
|
||||||
// ***************************
|
// ***************************
|
||||||
|
@ -754,7 +763,6 @@ let needSetup = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edit a monitor
|
// Edit a monitor
|
||||||
socket.on("editMonitor", async (monitor, callback) => {
|
socket.on("editMonitor", async (monitor, callback) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -357,6 +357,50 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get incident history for a status page
|
||||||
|
*/
|
||||||
|
socket.on("getStatusPageIncidentHistory", async (slug, callback) => {
|
||||||
|
try {
|
||||||
|
const statusPageBean = await R.findOne("status_page", " slug = ? ", [
|
||||||
|
slug
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!statusPageBean) {
|
||||||
|
throw new Error("Status page not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all incidents for this status page, ordered by creation date descending
|
||||||
|
const incidents = await R.find("incident", " status_page_id = ? ORDER BY created_date DESC ", [
|
||||||
|
statusPageBean.id
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Convert to public JSON format
|
||||||
|
const incidentsJSON = incidents.map(incident => {
|
||||||
|
return {
|
||||||
|
id: incident.id,
|
||||||
|
title: incident.title,
|
||||||
|
content: incident.content,
|
||||||
|
style: incident.style,
|
||||||
|
createdDate: incident.created_date,
|
||||||
|
lastUpdatedDate: incident.last_updated_date,
|
||||||
|
pin: incident.pin,
|
||||||
|
active: incident.active
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
incidents: incidentsJSON
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
6
src/languages/en.js
Normal file
6
src/languages/en.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
// Add translations for Incident History
|
||||||
|
"Incident History": "Incident History",
|
||||||
|
"No incident reports found.": "No incident reports found.",
|
||||||
|
"Loading": "Loading",
|
||||||
|
};
|
79
src/pages/ListIncidents.vue
Normal file
79
src/pages/ListIncidents.vue
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>{{ $t("Incident Reports") }}</h1>
|
||||||
|
<div v-if="isLoading">Loading...</div>
|
||||||
|
<div v-else-if="filteredReports.length">
|
||||||
|
<div
|
||||||
|
v-for="report in filteredReports"
|
||||||
|
:key="report._id"
|
||||||
|
class="big-padding"
|
||||||
|
>
|
||||||
|
<h3>{{ datetimeFormat(report._createdDate) }}</h3>
|
||||||
|
<hr />
|
||||||
|
<h4>{{ report._title }}</h4>
|
||||||
|
<p>{{ report._content }}</p>
|
||||||
|
<hr />
|
||||||
|
<br /><br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else>No incident reports found or an error occurred.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
incidentReports: [],
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredReports() {
|
||||||
|
return this.incidentReports
|
||||||
|
.slice() // Create a copy to avoid mutating the original array
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b._createdDate) - new Date(a._createdDate),
|
||||||
|
)
|
||||||
|
.slice(-25); // Get the last 25 sorted reports
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetchIncidentReports();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchIncidentReports() {
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/incident-reports"); // Replace with your API endpoint
|
||||||
|
const data = await response.json();
|
||||||
|
this.incidentReports = data;
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error;
|
||||||
|
console.error("Error fetching incident reports:", error);
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.incident-report-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px; /* Adjust gap between boxes */
|
||||||
|
}
|
||||||
|
|
||||||
|
.incident-report {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
{{ $t("Refresh Interval Description", [config.autoRefreshInterval]) }}
|
{{ $t("Refresh Interval Description", [config.autoRefreshInterval]) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="switch-theme" class="form-label">{{ $t("Theme") }}</label>
|
<label for="switch-theme" class="form-label">{{ $t("Theme") }}</label>
|
||||||
<select id="switch-theme" v-model="config.theme" class="form-select" data-testid="theme-select">
|
<select id="switch-theme" v-model="config.theme" class="form-select" data-testid="theme-select">
|
||||||
|
@ -281,6 +280,41 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- Incident History with improved styling -->
|
||||||
|
<div class="mb-4 incident-history">
|
||||||
|
<h2>{{ $t("Incident History") }}</h2>
|
||||||
|
<div v-if="isLoading">{{ $t("Loading") }}...</div>
|
||||||
|
<div v-else-if="incidentReports.length">
|
||||||
|
<div
|
||||||
|
v-for="report in incidentReports"
|
||||||
|
:key="report.id"
|
||||||
|
class="shadow-box alert mb-4 p-4 incident-report"
|
||||||
|
:class="'bg-' + report.style"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<h4 class="alert-heading">{{ report.title }}</h4>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html-->
|
||||||
|
<div class="content markdown-content" v-html="formatIncidentContent(report.content)"></div>
|
||||||
|
<div class="incident-meta mt-3">
|
||||||
|
<div class="incident-date">
|
||||||
|
<font-awesome-icon icon="calendar-alt" class="me-1" />
|
||||||
|
{{ $t("Date Created") }}: {{ datetimeFormat(report.createdDate) }}
|
||||||
|
<span class="text-muted">({{ dateFromNow(report.createdDate) }})</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="report.lastUpdatedDate" class="incident-updated">
|
||||||
|
<font-awesome-icon icon="clock" class="me-1" />
|
||||||
|
{{ $t("Last Updated") }}: {{ datetimeFormat(report.lastUpdatedDate) }}
|
||||||
|
<span class="text-muted">({{ dateFromNow(report.lastUpdatedDate) }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else class="text-center py-4">
|
||||||
|
<font-awesome-icon icon="info-circle" class="me-2" />
|
||||||
|
{{ $t("No incident reports found.") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<strong v-if="editMode">{{ $t("Description") }}:</strong>
|
<strong v-if="editMode">{{ $t("Description") }}:</strong>
|
||||||
<Editable v-if="enableEditMode" v-model="config.description" :contenteditable="editMode" tag="div" class="mb-4 description" data-testid="description-editable" />
|
<Editable v-if="enableEditMode" v-model="config.description" :contenteditable="editMode" tag="div" class="mb-4 description" data-testid="description-editable" />
|
||||||
|
@ -327,7 +361,25 @@
|
||||||
<!-- 👀 Nothing here, please add a group or a monitor. -->
|
<!-- 👀 Nothing here, please add a group or a monitor. -->
|
||||||
👀 {{ $t("statusPageNothing") }}
|
👀 {{ $t("statusPageNothing") }}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>Incident Reports</h1>
|
||||||
|
<div v-if="isLoading">Loading...</div>
|
||||||
|
<div v-else-if="filteredReports.length">
|
||||||
|
<div
|
||||||
|
v-for="report in filteredReports"
|
||||||
|
:key="report._id"
|
||||||
|
class="big-padding"
|
||||||
|
>
|
||||||
|
<h3>{{ (report._createdDate) }}</h3>
|
||||||
|
<hr />
|
||||||
|
<h4>{{ report._title }}</h4>
|
||||||
|
<p>{{ report._content }}</p>
|
||||||
|
<hr />
|
||||||
|
<br /><br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else>No incident reports found or an error occurred.</p>
|
||||||
|
</div>
|
||||||
<PublicGroupList :edit-mode="enableEditMode" :show-tags="config.showTags" :show-certificate-expiry="config.showCertificateExpiry" />
|
<PublicGroupList :edit-mode="enableEditMode" :show-tags="config.showTags" :show-certificate-expiry="config.showCertificateExpiry" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -379,12 +431,14 @@ import DOMPurify from "dompurify";
|
||||||
import Confirm from "../components/Confirm.vue";
|
import Confirm from "../components/Confirm.vue";
|
||||||
import PublicGroupList from "../components/PublicGroupList.vue";
|
import PublicGroupList from "../components/PublicGroupList.vue";
|
||||||
import MaintenanceTime from "../components/MaintenanceTime.vue";
|
import MaintenanceTime from "../components/MaintenanceTime.vue";
|
||||||
|
import dateTime from "../mixins/datetime.js";
|
||||||
import { getResBaseURL } from "../util-frontend";
|
import { getResBaseURL } from "../util-frontend";
|
||||||
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE } from "../util.ts";
|
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE } from "../util.ts";
|
||||||
import Tag from "../components/Tag.vue";
|
import Tag from "../components/Tag.vue";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
|
|
||||||
const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
|
const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
|
||||||
|
@ -407,7 +461,7 @@ export default {
|
||||||
Tag,
|
Tag,
|
||||||
VueMultiselect
|
VueMultiselect
|
||||||
},
|
},
|
||||||
|
mixins: [ dateTime ],
|
||||||
// Leave Page for vue route change
|
// Leave Page for vue route change
|
||||||
beforeRouteLeave(to, from, next) {
|
beforeRouteLeave(to, from, next) {
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
|
@ -429,7 +483,6 @@ export default {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
slug: null,
|
slug: null,
|
||||||
|
@ -451,10 +504,24 @@ export default {
|
||||||
updateCountdown: null,
|
updateCountdown: null,
|
||||||
updateCountdownText: null,
|
updateCountdownText: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
|
isLoading: false,
|
||||||
|
incidentReports: [],
|
||||||
|
error: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
filteredReports() {
|
||||||
|
for (let reports in this.incidentReports) {
|
||||||
|
this.datetime(reports._createdDate);
|
||||||
|
}
|
||||||
|
return this.incidentReports
|
||||||
|
.slice()
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b._createdDate) - new Date(a._createdDate),
|
||||||
|
)
|
||||||
|
.slice(-25);
|
||||||
|
},
|
||||||
logoURL() {
|
logoURL() {
|
||||||
if (this.imgDataUrl.startsWith("data:")) {
|
if (this.imgDataUrl.startsWith("data:")) {
|
||||||
return this.imgDataUrl;
|
return this.imgDataUrl;
|
||||||
|
@ -462,7 +529,6 @@ export default {
|
||||||
return this.baseURL + this.imgDataUrl;
|
return this.baseURL + this.imgDataUrl;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the monitor is added to public list, which will not be in this list.
|
* If the monitor is added to public list, which will not be in this list.
|
||||||
* @returns {object[]} List of monitors
|
* @returns {object[]} List of monitors
|
||||||
|
@ -731,7 +797,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateHeartbeatList();
|
this.updateHeartbeatList();
|
||||||
|
this.fetchIncidentReports();
|
||||||
// Go to edit page if ?edit present
|
// Go to edit page if ?edit present
|
||||||
// null means ?edit present, but no value
|
// null means ?edit present, but no value
|
||||||
if (this.$route.query.edit || this.$route.query.edit === null) {
|
if (this.$route.query.edit || this.$route.query.edit === null) {
|
||||||
|
@ -797,7 +863,33 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async fetchIncidentReports() {
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
const socket = this.$root.getSocket();
|
||||||
|
|
||||||
|
socket.emit("getStatusPageIncidentHistory", this.slug, (data) => {
|
||||||
|
if (data.ok) {
|
||||||
|
this.incidentReports = data.incidents;
|
||||||
|
} else {
|
||||||
|
this.error = data.msg;
|
||||||
|
console.error("Error fetching incident reports:", data.msg);
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error;
|
||||||
|
console.error("Error fetching incident reports:", error);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatIncidentContent(content) {
|
||||||
|
// Convert markdown to HTML and sanitize
|
||||||
|
if (!content) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return DOMPurify.sanitize(marked(content));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Setup timer to display countdown to refresh
|
* Setup timer to display countdown to refresh
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
@ -1056,216 +1148,157 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
|
/* Import the variables file to access the theme colors */
|
||||||
@import "../assets/vars.scss";
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
.overall-status {
|
/* Remove the scoped attribute from the style tag to allow targeting child components */
|
||||||
font-weight: bold;
|
.incident-history {
|
||||||
font-size: 25px;
|
.incident-report {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-left: 5px solid;
|
||||||
|
|
||||||
.ok {
|
&.bg-info {
|
||||||
color: $primary;
|
border-left-color: #0dcaf0; /* Hardcoded Bootstrap info color */
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
&.bg-warning {
|
||||||
color: $warning;
|
border-left-color: $warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger {
|
&.bg-danger {
|
||||||
color: $danger;
|
border-left-color: $danger;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
&.bg-primary {
|
||||||
font-size: 30px;
|
border-left-color: $primary;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
&.bg-light {
|
||||||
vertical-align: middle;
|
border-left-color: #ccc;
|
||||||
height: 60px;
|
}
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
&.bg-dark {
|
||||||
transition: all ease-in-out 0.1s;
|
border-left-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
&.edit {
|
&.bg-maintenance {
|
||||||
margin-left: 300px;
|
border-left-color: $maintenance;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
.alert-heading {
|
||||||
position: fixed;
|
font-weight: bold;
|
||||||
left: 0;
|
margin-bottom: 1rem;
|
||||||
top: 0;
|
}
|
||||||
width: 300px;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
border-right: 1px solid #ededed;
|
.incident-meta {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
.danger-zone {
|
.incident-date, .incident-updated {
|
||||||
border-top: 1px solid #ededed;
|
margin-bottom: 0.25rem;
|
||||||
padding-top: 15px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-body {
|
|
||||||
padding: 0 10px 10px 10px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
height: calc(100% - 70px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-footer {
|
|
||||||
border-top: 1px solid #ededed;
|
|
||||||
border-right: 1px solid #ededed;
|
|
||||||
padding: 10px;
|
|
||||||
width: 300px;
|
|
||||||
height: 70px;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: white;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description span {
|
|
||||||
min-width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-flex {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-wrapper {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.icon-upload {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-upload {
|
/* Markdown content styles without using ::v-deep */
|
||||||
transition: all $easing-in 0.2s;
|
.markdown-content {
|
||||||
position: absolute;
|
h1, h2, h3, h4, h5, h6 {
|
||||||
bottom: 6px;
|
margin-top: 1rem;
|
||||||
font-size: 20px;
|
margin-bottom: 0.5rem;
|
||||||
left: -14px;
|
|
||||||
background-color: white;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 15px 70px rgba(0, 0, 0, 0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
transition: all $easing-in 0.2s;
|
|
||||||
|
|
||||||
&.edit-mode {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.incident {
|
|
||||||
.content {
|
|
||||||
&[contenteditable="true"] {
|
|
||||||
min-height: 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.date {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.maintenance-bg-info {
|
|
||||||
color: $maintenance;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maintenance-icon {
|
|
||||||
font-size: 35px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .shadow-box {
|
|
||||||
background-color: #0d1117;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-maintenance {
|
|
||||||
color: $maintenance;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overall-status {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
.sidebar {
|
|
||||||
background-color: $dark-header-bg;
|
|
||||||
border-right-color: $dark-border-color;
|
|
||||||
|
|
||||||
.danger-zone {
|
|
||||||
border-top-color: $dark-border-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-footer {
|
p {
|
||||||
border-right-color: $dark-border-color;
|
margin-bottom: 1rem;
|
||||||
border-top-color: $dark-border-color;
|
|
||||||
background-color: $dark-header-bg;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.domain-name-list {
|
ul, ol {
|
||||||
li {
|
margin-bottom: 1rem;
|
||||||
display: flex;
|
padding-left: 2rem;
|
||||||
align-items: center;
|
}
|
||||||
padding: 10px 0 10px 10px;
|
|
||||||
|
|
||||||
.domain-input {
|
code {
|
||||||
flex-grow: 1;
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
background-color: transparent;
|
padding: 0.2rem 0.4rem;
|
||||||
border: none;
|
border-radius: 3px;
|
||||||
color: $dark-font-color;
|
}
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&::placeholder {
|
pre {
|
||||||
color: #1d2634;
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid rgba(0, 0, 0, 0.1);
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin-left: 0;
|
||||||
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-maintenance {
|
/* Dark mode adjustments */
|
||||||
.alert-heading {
|
.dark {
|
||||||
font-weight: bold;
|
.incident-history {
|
||||||
|
.incident-report {
|
||||||
|
&.bg-light {
|
||||||
|
color: $dark-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.incident-meta {
|
||||||
|
border-top-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content {
|
||||||
|
code, pre {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left-color: rgba(255, 255, 255, 0.2);
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
th, td {
|
||||||
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh-info {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import DashboardHome from "./pages/DashboardHome.vue";
|
||||||
import Details from "./pages/Details.vue";
|
import Details from "./pages/Details.vue";
|
||||||
import EditMonitor from "./pages/EditMonitor.vue";
|
import EditMonitor from "./pages/EditMonitor.vue";
|
||||||
import EditMaintenance from "./pages/EditMaintenance.vue";
|
import EditMaintenance from "./pages/EditMaintenance.vue";
|
||||||
|
import ListIncidents from "./pages/ListIncidents.vue";
|
||||||
import List from "./pages/List.vue";
|
import List from "./pages/List.vue";
|
||||||
const Settings = () => import("./pages/Settings.vue");
|
const Settings = () => import("./pages/Settings.vue");
|
||||||
import Setup from "./pages/Setup.vue";
|
import Setup from "./pages/Setup.vue";
|
||||||
|
@ -162,6 +163,10 @@ const routes = [
|
||||||
path: "/maintenance/edit/:id",
|
path: "/maintenance/edit/:id",
|
||||||
component: EditMaintenance,
|
component: EditMaintenance,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/incident-history",
|
||||||
|
component: ListIncidents,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
Loading…
Add table
Reference in a new issue