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") }}...
+
+ +
+

+ + {{ $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); + } + } + } + } +} + + + +