diff --git a/extra/update-language-files/package.json b/extra/update-language-files/package.json
index c7295175a..a27339884 100644
--- a/extra/update-language-files/package.json
+++ b/extra/update-language-files/package.json
@@ -8,5 +8,8 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
- "license": "ISC"
+ "license": "ISC",
+ "dependencies": {
+ "update-language-files": "file:"
+ }
}
diff --git a/package-lock.json b/package-lock.json
index 7148c8604..ed0ad5034 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -80,6 +80,7 @@
"tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2",
"tough-cookie": "~4.1.3",
+ "uptime-kuma": "file:",
"ws": "^8.13.0"
},
"devDependencies": {
@@ -15129,6 +15130,10 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/uptime-kuma": {
+ "resolved": "",
+ "link": true
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
diff --git a/package.json b/package.json
index a74e4677e..e1f812aef 100644
--- a/package.json
+++ b/package.json
@@ -145,6 +145,7 @@
"tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2",
"tough-cookie": "~4.1.3",
+ "uptime-kuma": "file:",
"ws": "^8.13.0"
},
"devDependencies": {
diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js
index 0804da15d..6926fb9cf 100644
--- a/server/socket-handlers/status-page-socket-handler.js
+++ b/server/socket-handlers/status-page-socket-handler.js
@@ -348,6 +348,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
+ });
+ }
+ });
};
/**
diff --git a/src/languages/en.js b/src/languages/en.js
new file mode 100644
index 000000000..b7196d705
--- /dev/null
+++ b/src/languages/en.js
@@ -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"
+}
\ No newline at end of file
diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue
index eb8c9c5cb..16853586d 100644
--- a/src/pages/StatusPage.vue
+++ b/src/pages/StatusPage.vue
@@ -280,6 +280,41 @@
+
+
+
{{ $t("Incident History") }}
+
{{ $t("Loading") }}...
+
+
+
{{ report.title }}
+
+
+
+
+
+
+
+ {{ $t("No incident reports found.") }}
+
+
+
{{ $t("Description") }}:
@@ -829,20 +864,31 @@ export default {
}
},
async fetchIncidentReports() {
- const socket = io();
this.isLoading = true;
- socket.emit("fetchIncidentReports");
-
- socket.on("incidentReports", (data) => {
- this.incidentReports = data;
- this.isLoading = false;
- });
-
- socket.on("incidentReportsError", (error) => {
+ 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);
+ 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
@@ -1314,4 +1360,154 @@ footer {
opacity: 0.7;
}
+.incident-history {
+ .incident-report {
+ transition: all 0.3s ease;
+ border-left: 5px solid;
+
+ &.bg-info {
+ border-left-color: $info;
+ }
+
+ &.bg-warning {
+ border-left-color: $warning;
+ }
+
+ &.bg-danger {
+ border-left-color: $danger;
+ }
+
+ &.bg-primary {
+ border-left-color: $primary;
+ }
+
+ &.bg-light {
+ border-left-color: #ccc;
+ }
+
+ &.bg-dark {
+ border-left-color: #333;
+ }
+
+ &.bg-maintenance {
+ border-left-color: $maintenance;
+ }
+
+ .alert-heading {
+ font-weight: bold;
+ margin-bottom: 1rem;
+ }
+
+ .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;
+
+ .incident-date, .incident-updated {
+ margin-bottom: 0.25rem;
+ }
+ }
+ }
+
+ /* Ensure markdown content is properly styled */
+ :deep(.markdown-content) {
+ h1, h2, h3, h4, h5, h6 {
+ margin-top: 1rem;
+ margin-bottom: 0.5rem;
+ }
+
+ p {
+ margin-bottom: 1rem;
+ }
+
+ ul, ol {
+ margin-bottom: 1rem;
+ padding-left: 2rem;
+ }
+
+ code {
+ background-color: rgba(0, 0, 0, 0.05);
+ padding: 0.2rem 0.4rem;
+ border-radius: 3px;
+ }
+
+ pre {
+ 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);
+ }
+ }
+ }
+
+ /* Dark mode adjustments */
+ .dark & {
+ .incident-report {
+ &.bg-light {
+ color: $dark-bg;
+ }
+
+ .incident-meta {
+ border-top-color: rgba(255, 255, 255, 0.1);
+ }
+ }
+
+ :deep(.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);
+ }
+ }
+ }
+ }
+}
+
+
+
+