mirror of
https://github.com/rajnandan1/kener.git
synced 2026-08-07 07:14:56 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee831f96bb | |||
| 5542790145 | |||
| 3b1091a34f | |||
| eebe6541c3 | |||
| e308089cfa | |||
| 5132d6894e | |||
| 006d1e1fa0 | |||
| 4c6dc24355 | |||
| acf227a947 | |||
| 87408d22c4 | |||
| ea6754775d | |||
| 3210f4d406 | |||
| 16ac97a9c9 | |||
| 8857a51dde | |||
| fc2abe7f16 | |||
| f04a930e13 | |||
| 1de1e2d6ba | |||
| 0208e4baf9 | |||
| 21af2a6d02 | |||
| 2fbe301a51 | |||
| 6d5382982c | |||
| a20b4ee570 | |||
| 909ff9e070 | |||
| 7d3e265ece | |||
| 9462a0331a | |||
| 4e3d1838c0 | |||
| 1288b463f1 | |||
| 1aed6d0703 | |||
| 69f1e69140 | |||
| ab4ecd29c6 | |||
| 1d76c8a3d3 | |||
| b1c2cedbac |
@@ -79,7 +79,7 @@ npm run dev
|
||||
|
||||
Official Docker images for **Kener** are available on [Docker Hub](https://hub.docker.com/r/rajnandan1/kener). Multiple versions are maintained to support different use cases.
|
||||
|
||||
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.13"><img src="https://img.shields.io/badge/Latest_Stable_Release-3.2.13-blue" alt="Kener latest stable version: 3.2.13" /></a>
|
||||
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.16"><img src="https://img.shields.io/badge/Latest_Stable_Release-3.2.16-blue" alt="Kener latest stable version: 3.2.16" /></a>
|
||||
|
||||
#### Available Tags
|
||||
|
||||
@@ -93,38 +93,38 @@ Official Docker images for **Kener** are available on [Docker Hub](https://hub.d
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=latest" target="_blank"><code>latest</code></td>
|
||||
<td>Latest stable release (aka 3.2.13)</td>
|
||||
<td>Latest stable release (aka 3.2.16)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.13" target="_blank"><code>3.2.13</code></a></td>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.16" target="_blank"><code>3.2.16</code></a></td>
|
||||
<td>Specific release version</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2" target="_blank"><code>3.2</code></a></td>
|
||||
<td>Major-minor version tag pointing to the latest patch (3.2.13) release within that minor version (3.2.x)</td>
|
||||
<td>Major-minor version tag pointing to the latest patch (3.2.16) release within that minor version (3.2.x)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3" target="_blank"><code>3</code></a></td>
|
||||
<td>Major version tag pointing to the latest stable (3.2.13) release within that major version (3.x.x)</td>
|
||||
<td>Major version tag pointing to the latest stable (3.2.16) release within that major version (3.x.x)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" colspan="2" style="color:#0D597F;text-align:left;">Alpine Linux 3.21 w/ Node.js v23.7.0 <strong><em>(smallest image size)</em></strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=alpine" target="_blank"><code>alpine</code></td>
|
||||
<td>Latest stable release (aka 3.2.13)</td>
|
||||
<td>Latest stable release (aka 3.2.16)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.13-alpine" target="_blank"><code>3.2.13-alpine</code></a></td>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.16-alpine" target="_blank"><code>3.2.16-alpine</code></a></td>
|
||||
<td>Specific release version</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2-alpine" target="_blank"><code>3.2-alpine</code></a></td>
|
||||
<td>Major-minor version tag pointing to the latest patch (3.2.13) release within that minor version (3.2.x)</td>
|
||||
<td>Major-minor version tag pointing to the latest patch (3.2.16) release within that minor version (3.2.x)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3-alpine" target="_blank"><code>3-alpine</code></a></td>
|
||||
<td>Major version tag pointing to the latest stable (3.2.13) release within that major version (3.x.x)</td>
|
||||
<td>Major version tag pointing to the latest stable (3.2.16) release within that major version (3.x.x)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
+3
-3
@@ -38,7 +38,7 @@ services:
|
||||
# Only use below section if you would like to utilize PostgreSQL instead of Kener's default SQLite database. (Don't forget to set `DATABASE_URL` in `kener` service to be: `DATABASE_URL=postgresql://db_user:db_password@localhost:5432/kener_db`)
|
||||
postgres:
|
||||
image: postgres:alpine
|
||||
name: kener_db
|
||||
container_name: postgres
|
||||
environment:
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: some_super_random_secure_password # Best to define this in `.env` or via Docker Secret!!
|
||||
@@ -48,7 +48,7 @@ services:
|
||||
# Only use below section if you would like to utilize MySQL instead of Kener's default SQLite database. (Don't forget to set `DATABASE_URL` in `kener` service to be: `DATABASE_URL=mysql://db_user:db_password@localhost:3306/kener_db`)
|
||||
mysql:
|
||||
image: mariadb:11
|
||||
name: kener_db
|
||||
container_name: mysql
|
||||
environment:
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: some_super_random_secure_password # Best to define this in `.env` or via Docker Secret!!
|
||||
@@ -58,4 +58,4 @@ services:
|
||||
|
||||
volumes:
|
||||
data:
|
||||
name: kener_db
|
||||
name: kener_db
|
||||
|
||||
@@ -7,6 +7,13 @@ description: Changelogs for Kener
|
||||
|
||||
Here are the changelogs for Kener. Changelogs are only published when there are new features or breaking changes.
|
||||
|
||||
## v3.2.16
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- **Hidden Categories:** You can now create hidden categories that will not show up for users that are not logged in. All the monitors will also not show up. Some one however can directly get to see them if they visit the monitor link directly
|
||||
- **Configure data rage:** Now you can configure the data range in home page between 1 and 180 days. You can set different ranges for mobile and desktop devices.
|
||||
|
||||
## v3.2.14
|
||||
|
||||
### ✨ Features
|
||||
|
||||
+12
-31
@@ -12,29 +12,24 @@ There are two ways to embed your monitor in your website
|
||||
You can embed your monitor in your website using javascript. We recommend using this method as it takes care of the height of the embedded monitor.
|
||||
|
||||
```html
|
||||
<script
|
||||
async
|
||||
src="http://[hostname]/embed/monitor-[tag]/js?theme=light&monitor=http://[hostname]/embed/monitor-[tag]"
|
||||
></script>
|
||||
<script async src="http://[hostname]/embed/monitor-[tag]/js?theme=light&monitor=http://[hostname]/embed/monitor-[tag]"></script>
|
||||
```
|
||||
|
||||
Here is an example
|
||||
|
||||
```html
|
||||
<script
|
||||
async
|
||||
src="https://kener.ing/embed/monitor-earth/js?theme=light&monitor=https://kener.ing/embed/monitor-earth"
|
||||
></script>
|
||||
<script async src="https://kener.ing/embed/monitor-earth/js?theme=light&monitor=https://kener.ing/embed/monitor-earth"></script>
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
You can pass the following parameters to the embed code
|
||||
|
||||
- `theme`: You can pass `light` or `dark` theme
|
||||
- `monitor`: The monitor url
|
||||
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol. Example: `ff0000`
|
||||
- `locale`: The locale of the monitor. You can pass the code of the locale you have enabled in your kener settings. Example: `en`, `fr` etc
|
||||
- `theme`: You can pass `light` or `dark` theme
|
||||
- `monitor`: The monitor url
|
||||
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol. Example: `ff0000`
|
||||
- `locale`: The locale of the monitor. You can pass the code of the locale you have enabled in your kener settings. Example: `en`, `fr` etc
|
||||
- `tz`: Timezone. Example `Asia/Calcutta`, `Asia/Tokyo`
|
||||
|
||||
Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
|
||||
|
||||
@@ -49,27 +44,13 @@ Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
|
||||
This is the simplest way to embed your monitor in your website. You can use the following code to embed your monitor in your website.
|
||||
|
||||
```html
|
||||
<iframe
|
||||
src="http://[hostname]/embed/monitor-[tag]?theme=light"
|
||||
width="100%"
|
||||
height="200"
|
||||
allowfullscreen="allowfullscreen"
|
||||
allowpaymentrequest
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
<iframe src="http://[hostname]/embed/monitor-[tag]?theme=light" width="100%" height="200" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
|
||||
```
|
||||
|
||||
Here is an example
|
||||
|
||||
```html
|
||||
<iframe
|
||||
src="https://kener.ing/embed/monitor-earth?theme=light"
|
||||
width="100%"
|
||||
height="100"
|
||||
allowfullscreen="allowfullscreen"
|
||||
allowpaymentrequest
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
<iframe src="https://kener.ing/embed/monitor-earth?theme=light" width="100%" height="100" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
|
||||
```
|
||||
|
||||
Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
|
||||
@@ -78,9 +59,9 @@ Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
|
||||
|
||||
You can pass the following parameters to the embed code
|
||||
|
||||
- `theme`: You can pass `light` or `dark` theme
|
||||
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol. Example: `ff0000`
|
||||
- `locale`: The locale of the monitor. You can pass the code of the locale you have enabled in your kener settings. Example: `en`, `fr` etc
|
||||
- `theme`: You can pass `light` or `dark` theme
|
||||
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol. Example: `ff0000`
|
||||
- `locale`: The locale of the monitor. You can pass the code of the locale you have enabled in your kener settings. Example: `en`, `fr` etc
|
||||
|
||||
### Demo
|
||||
|
||||
|
||||
+27
-1
@@ -29,17 +29,25 @@ Copy and update the translation file in the `locales` folder. The translation fi
|
||||
```json
|
||||
{
|
||||
"%status for %duration": "%status for %duration",
|
||||
"120 Days": "120 Days",
|
||||
"14 Days": "14 Days",
|
||||
"150 Days": "150 Days",
|
||||
"180 Days": "180 Days",
|
||||
"30 Days": "30 Days",
|
||||
"60 Days": "60 Days",
|
||||
"7 Days": "7 Days",
|
||||
"90 Days": "90 Days",
|
||||
"Availability per Component": "Availability per Component",
|
||||
"All Systems are Degraded": "All Systems are Degraded",
|
||||
"All Systems are Down": "All Systems are Down",
|
||||
"All Systems are Operational": "All Systems are Operational",
|
||||
"Back": "Back",
|
||||
"Badge Copied": "Badge Copied",
|
||||
"Badge": "Badge",
|
||||
"Browse Events": "Browse Events",
|
||||
"Change Email": "Change Email",
|
||||
"Code Copied": "Code Copied",
|
||||
"Confirm Login": "Confirm Login",
|
||||
"Copy Code": "Copy Code",
|
||||
"Copy Link": "Copy Link",
|
||||
"DEGRADED": "DEGRADED",
|
||||
@@ -48,6 +56,9 @@ Copy and update the translation file in the `locales` folder. The translation fi
|
||||
"Days": "Days",
|
||||
"Embed this monitor using <script> or <iframe> in your app.": "Embed this monitor using <script> or <iframe> in your app.",
|
||||
"Embed": "Embed",
|
||||
"Enter the code": "Enter the code",
|
||||
"Enter your email": "Enter your email",
|
||||
"Enter your email to log in. If you are not subscribed, you will be prompted to subscribe.": "Enter your email to log in. If you are not subscribed, you will be prompted to subscribe.",
|
||||
"Get SVG badge for this monitor": "Get SVG badge for this monitor",
|
||||
"Get a LIVE Status for this monitor": "Get a LIVE Status for this monitor",
|
||||
"IDENTIFIED": "IDENTIFIED",
|
||||
@@ -57,10 +68,12 @@ Copy and update the translation file in the `locales` folder. The translation fi
|
||||
"Lasted for about %lastedFor": "Lasted for about %lastedFor",
|
||||
"Light": "Light",
|
||||
"Link Copied": "Link Copied",
|
||||
"Login": "Login",
|
||||
"MAINTENANCE": "MAINTENANCE",
|
||||
"MONITORING": "MONITORING",
|
||||
"Maintenance Completed": "Maintenance Completed",
|
||||
"Maintenance in Progress": "Maintenance in Progress",
|
||||
"Manage Subscription": "Manage Subscription",
|
||||
"Mode": "Mode",
|
||||
"No Data": "No Data",
|
||||
"No Incident in %date": "No Incident in %date",
|
||||
@@ -69,11 +82,15 @@ Copy and update the translation file in the `locales` folder. The translation fi
|
||||
"No Updates Yet": "No Updates Yet",
|
||||
"Ongoing Incidents": "Ongoing Incidents",
|
||||
"Pinging": "Pinging",
|
||||
"Please select specific monitors to receive updates from.": "Please select specific monitors to receive updates from.",
|
||||
"RESOLVED": "RESOLVED",
|
||||
"Recent Incidents": "Recent Incidents",
|
||||
"Recent Maintenances": "Recent Maintenances",
|
||||
"Share this monitor using a link with others": "Share this monitor using a link with others",
|
||||
"Share": "Share",
|
||||
"Some Systems are not working as expected": "Some Systems are not working as expected",
|
||||
"Some Systems Degraded": "Some Systems Degraded",
|
||||
"Some Systems Down": "Some Systems Down",
|
||||
"Standard": "Standard",
|
||||
"Started %startedAt, lasted for %lastedFor": "Started %startedAt, lasted for %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Started %startedAt, still ongoing",
|
||||
@@ -82,14 +99,23 @@ Copy and update the translation file in the `locales` folder. The translation fi
|
||||
"Starts %startedAt": "Starts %startedAt",
|
||||
"Status OK": "Status OK",
|
||||
"Status": "Status",
|
||||
"Subscribe": "Subscribe",
|
||||
"Subscribe to Updates": "Subscribe to Updates",
|
||||
"Subscribe to all monitors": "Subscribe to all monitors",
|
||||
"Switch to your timezone": "Switch to your timezone",
|
||||
"Theme": "Theme",
|
||||
"There are no upcoming maintenances": "There are no upcoming maintenances",
|
||||
"Timezone set to %tz": "Timezone set to %tz",
|
||||
"Today": "Today",
|
||||
"UP": "UP",
|
||||
"Update Subscription": "Update Subscription",
|
||||
"Upcoming Maintenance": "Upcoming Maintenance",
|
||||
"Upcoming Maintenances": "Upcoming Maintenances",
|
||||
"Updates": "Updates",
|
||||
"Uptime": "Uptime"
|
||||
"Uptime": "Uptime",
|
||||
"View in detail": "View in detail",
|
||||
"We have sent a code to your email. Please enter it below to confirm your login": "We have sent a code to your email. Please enter it below to confirm your login",
|
||||
"You are logged in as %email": "You are logged in as %email"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
<body>
|
||||
<script
|
||||
async
|
||||
src="http://localhost:3000/embed/monitor-bitbuckettcp/js?bgc=212226&locale=en&theme=dark&monitor=http://localhost:3000/embed/monitor-bitbuckettcp"
|
||||
src="http://localhost:3000/embed/monitor-earth/js?theme=light&monitor=http://localhost:3000/embed/monitor-earth&locale=fr&tz=Asia/Tokyo"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Generated
+9
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kener",
|
||||
"version": "3.2.14",
|
||||
"version": "3.2.15",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kener",
|
||||
"version": "3.2.14",
|
||||
"version": "3.2.15",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10",
|
||||
@@ -41,6 +41,7 @@
|
||||
"knex": "^3.1.0",
|
||||
"lucide-svelte": "^0.483.0",
|
||||
"marked": "^11.1.1",
|
||||
"mobile-detect": "^1.4.5",
|
||||
"mode-watcher": "^0.4.1",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.43",
|
||||
@@ -5604,6 +5605,12 @@
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mobile-detect": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/mobile-detect/-/mobile-detect-1.4.5.tgz",
|
||||
"integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mode-watcher": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-0.4.1.tgz",
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kener",
|
||||
"version": "3.2.14",
|
||||
"version": "3.2.17",
|
||||
"private": false,
|
||||
"license": "MIT",
|
||||
"description": "Kener is a modern, open-source status page application built with Node.js. It provides real-time monitoring, uptime tracking, incident management, and beautiful dashboards. Perfect for DevOps teams, SaaS providers, and businesses needing reliable service status communication with minimal setup.",
|
||||
@@ -99,6 +99,7 @@
|
||||
"knex": "^3.1.0",
|
||||
"lucide-svelte": "^0.483.0",
|
||||
"marked": "^11.1.1",
|
||||
"mobile-detect": "^1.4.5",
|
||||
"mode-watcher": "^0.4.1",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.43",
|
||||
|
||||
+2
-1
@@ -882,7 +882,8 @@ p code:not([class^="language-"]) {
|
||||
margin-bottom: 1.3em;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:disabled + .peer {
|
||||
input[type="checkbox"]:disabled + .peer,
|
||||
label:has(input[type="checkbox"]:disabled) {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
|
||||
import Loader from "lucide-svelte/icons/loader";
|
||||
import LaptopMinimal from "lucide-svelte/icons/laptop-minimal";
|
||||
import SmartPhone from "lucide-svelte/icons/smartphone";
|
||||
import X from "lucide-svelte/icons/x";
|
||||
import Plus from "lucide-svelte/icons/plus";
|
||||
import Info from "lucide-svelte/icons/info";
|
||||
@@ -42,6 +44,18 @@
|
||||
let homeIncidentStartTimeWithin = 30;
|
||||
let incidentGroupView = "EXPAND_FIRST";
|
||||
let kenerTheme = "default";
|
||||
let homeDataMaxDays = {
|
||||
desktop: {
|
||||
maxDays: 90,
|
||||
selectableDays: [1, 7, 14, 30, 60, 90]
|
||||
},
|
||||
mobile: {
|
||||
maxDays: 90,
|
||||
selectableDays: [1, 7, 14, 30, 60, 90]
|
||||
}
|
||||
};
|
||||
|
||||
let selectableDaysList = [1, 7, 14, 30, 60, 90, 120, 150, 180];
|
||||
|
||||
let availableThemes = [
|
||||
{ name: "Default", value: "default" },
|
||||
@@ -93,6 +107,9 @@
|
||||
if (data.siteData.kenerTheme) {
|
||||
kenerTheme = data.siteData.kenerTheme;
|
||||
}
|
||||
if (data.siteData.homeDataMaxDays) {
|
||||
homeDataMaxDays = data.siteData.homeDataMaxDays;
|
||||
}
|
||||
if (data.siteData.tzToggle) {
|
||||
tzToggle = data.siteData.tzToggle;
|
||||
}
|
||||
@@ -121,7 +138,6 @@
|
||||
let resp = await storeSiteData({
|
||||
hero: JSON.stringify(hero)
|
||||
});
|
||||
//print data
|
||||
let data = await resp.json();
|
||||
formStateHero = "idle";
|
||||
if (data.error) {
|
||||
@@ -259,7 +275,8 @@
|
||||
...categories,
|
||||
{
|
||||
name: "",
|
||||
description: ""
|
||||
description: "",
|
||||
isHidden: false
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -287,6 +304,23 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let dataErrorMessage = "";
|
||||
let formStateData = "idle";
|
||||
async function formSubmitData() {
|
||||
dataErrorMessage = "";
|
||||
formStateData = "loading";
|
||||
let resp = await storeSiteData({
|
||||
homeDataMaxDays: JSON.stringify(homeDataMaxDays)
|
||||
});
|
||||
//print data
|
||||
let data = await resp.json();
|
||||
formStateData = "idle";
|
||||
if (data.error) {
|
||||
dataErrorMessage = data.error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root class="mt-4">
|
||||
@@ -379,6 +413,173 @@
|
||||
</form>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<Card.Root class="mt-4">
|
||||
<Card.Header class="border-b">
|
||||
<Card.Title>Data</Card.Title>
|
||||
<Card.Description>
|
||||
Configure the data shown on the homepage. This includes the time range for incidents.
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form class="mx-auto mt-4 flex flex-col space-y-4" on:submit|preventDefault={formSubmitData}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<Label class="text-lg">
|
||||
<LaptopMinimal class="mr-2 inline h-4 w-4" />
|
||||
Desktop
|
||||
</Label>
|
||||
<div class="border-b pb-4">
|
||||
<Label for="desktopmaxdata" class="text-sm font-medium">Select Data Range</Label>
|
||||
<Select.Root
|
||||
portal={null}
|
||||
onSelectedChange={(e) => {
|
||||
homeDataMaxDays.desktop.maxDays = e.value;
|
||||
//uncheck all checkboxes greater than maxDays
|
||||
homeDataMaxDays.desktop.selectableDays = homeDataMaxDays.desktop.selectableDays.filter(
|
||||
(day) => day <= e.value
|
||||
);
|
||||
//push all days less than maxDays
|
||||
selectableDaysList.forEach((day) => {
|
||||
if (day <= e.value && !homeDataMaxDays.desktop.selectableDays.includes(day)) {
|
||||
homeDataMaxDays.desktop.selectableDays.push(day);
|
||||
}
|
||||
});
|
||||
}}
|
||||
selected={{
|
||||
value: homeDataMaxDays.desktop.maxDays,
|
||||
label: homeDataMaxDays.desktop.maxDays + " Days"
|
||||
}}
|
||||
>
|
||||
<Select.Trigger class=" mt-2 w-[200px]" id="desktopmaxdata">
|
||||
<Select.Value bind:value={homeDataMaxDays.desktop.maxDays} placeholder="Select Range" />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Group>
|
||||
<Select.Label>Max Days</Select.Label>
|
||||
{#each selectableDaysList as day}
|
||||
<Select.Item value={day} label={day + " Days"} class="text-sm font-medium">
|
||||
{day} Days
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="border-b pb-4">
|
||||
<Label for="desktopdropdown" class="text-sm font-medium">Select Dropdown Range</Label>
|
||||
<div class="mt-2 flex flex-row flex-wrap gap-x-2">
|
||||
{#each selectableDaysList as day}
|
||||
<label>
|
||||
<input
|
||||
on:change={(e) => {
|
||||
if (e.target.checked) {
|
||||
homeDataMaxDays.desktop.selectableDays.push(day);
|
||||
} else {
|
||||
homeDataMaxDays.desktop.selectableDays = homeDataMaxDays.desktop.selectableDays.filter(
|
||||
(d) => d !== day
|
||||
);
|
||||
}
|
||||
}}
|
||||
type="checkbox"
|
||||
disabled={day >= homeDataMaxDays.desktop.maxDays}
|
||||
checked={homeDataMaxDays.desktop.selectableDays.includes(day)}
|
||||
/>
|
||||
{day} Days
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<Label class="text-lg">
|
||||
<SmartPhone class="mr-2 inline h-4 w-4" />
|
||||
Mobile
|
||||
</Label>
|
||||
<div class="border-b pb-4">
|
||||
<Label for="mobilemaxdata" class="text-sm font-medium">Select Data Range</Label>
|
||||
<Select.Root
|
||||
portal={null}
|
||||
onSelectedChange={(e) => {
|
||||
homeDataMaxDays.mobile.maxDays = e.value;
|
||||
//uncheck all checkboxes greater than maxDays
|
||||
homeDataMaxDays.mobile.selectableDays = homeDataMaxDays.mobile.selectableDays.filter(
|
||||
(day) => day <= e.value
|
||||
);
|
||||
//push all days less than maxDays
|
||||
selectableDaysList.forEach((day) => {
|
||||
if (day <= e.value && !homeDataMaxDays.mobile.selectableDays.includes(day)) {
|
||||
homeDataMaxDays.mobile.selectableDays.push(day);
|
||||
}
|
||||
});
|
||||
}}
|
||||
selected={{
|
||||
value: homeDataMaxDays.mobile.maxDays,
|
||||
label: homeDataMaxDays.mobile.maxDays + " Days"
|
||||
}}
|
||||
>
|
||||
<Select.Trigger class=" mt-2 w-[200px]" id="mobilemaxdata">
|
||||
<Select.Value bind:value={homeDataMaxDays.mobile.maxDays} placeholder="Select Range" />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Group>
|
||||
<Select.Label>Max Days</Select.Label>
|
||||
{#each selectableDaysList as day}
|
||||
<Select.Item value={day} label={day + " Days"} class="text-sm font-medium">
|
||||
{day} Days
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="border-b pb-4">
|
||||
<Label for="mobiledropdown" class="text-sm font-medium">Select Dropdown Range</Label>
|
||||
<div class="mt-2 flex flex-row flex-wrap gap-x-2">
|
||||
{#each selectableDaysList as day}
|
||||
<label>
|
||||
<input
|
||||
on:change={(e) => {
|
||||
if (e.target.checked) {
|
||||
homeDataMaxDays.mobile.selectableDays.push(day);
|
||||
} else {
|
||||
homeDataMaxDays.mobile.selectableDays = homeDataMaxDays.mobile.selectableDays.filter(
|
||||
(d) => d !== day
|
||||
);
|
||||
}
|
||||
}}
|
||||
type="checkbox"
|
||||
disabled={day >= homeDataMaxDays.mobile.maxDays}
|
||||
checked={homeDataMaxDays.mobile.selectableDays.includes(day)}
|
||||
/>
|
||||
{day} Days
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<span class="text-xs font-medium text-muted-foreground">
|
||||
Select the maximum number of days to show data for. This will be used to filter the data shown on the
|
||||
homepage. The dropdown will show all the days that are less than or equal to the selected max days. The max
|
||||
value in the dropdown will be the entire value of data range it cannot be changed
|
||||
</span>
|
||||
</p>
|
||||
{#if !!dataErrorMessage}
|
||||
<p class="text-sm font-medium text-destructive">{dataErrorMessage}</p>
|
||||
{/if}
|
||||
<div class="flex w-full justify-end gap-x-2">
|
||||
<Button type="submit" disabled={formStateData === "loading"}>
|
||||
Save Data Settings
|
||||
{#if formStateData === "loading"}
|
||||
<Loader class="ml-2 inline h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<Card.Root class="mt-4">
|
||||
<Card.Header class="border-b">
|
||||
<Card.Title>Incidents</Card.Title>
|
||||
@@ -652,8 +853,8 @@
|
||||
<Card.Content>
|
||||
<form class="mx-auto mt-4 space-y-4" on:submit|preventDefault={formSubmitCategories}>
|
||||
{#each categories as cat, i}
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div class="col-span-1">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-1">
|
||||
<Label for="{i}catkey" class="block w-full">Name</Label>
|
||||
<Input
|
||||
bind:value={cat.name}
|
||||
@@ -664,7 +865,7 @@
|
||||
disabled={i === 0}
|
||||
/>
|
||||
</div>
|
||||
<div class="relative col-span-3 pr-8">
|
||||
<div class="flex-1">
|
||||
<Label for="{i}catdsc" class="block w-full">Description</Label>
|
||||
<Input
|
||||
bind:value={cat.description}
|
||||
@@ -674,9 +875,27 @@
|
||||
placeholder="Category Description"
|
||||
disabled={i === 0}
|
||||
/>
|
||||
</div>
|
||||
<div class="relative flex flex-col items-center gap-y-2">
|
||||
<Label for="{i}vis" class="block w-full">Visibility</Label>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger disabled={i === 0} id="{i}vis">
|
||||
<Button variant="outline" class="mr-2" disabled={cat.name === "Home"}>
|
||||
{cat.isHidden ? "Private" : "Public"}
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item on:click={() => (cat.isHidden = true)}>Private</DropdownMenu.Item>
|
||||
<DropdownMenu.Item on:click={() => (cat.isHidden = false)}>Public</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="absolute right-0 top-8 ml-2 h-6 w-6 p-1 "
|
||||
class="mt-4 h-6 w-6 p-1"
|
||||
disabled={i === 0}
|
||||
on:click={() => (categories = categories.filter((_, index) => index !== i))}
|
||||
>
|
||||
|
||||
@@ -275,10 +275,16 @@
|
||||
body: JSON.stringify({ action: "testTrigger", data: { trigger_id, status } })
|
||||
});
|
||||
let resp = await data.json();
|
||||
//check status code, if not 200 then show error
|
||||
if (resp.error) {
|
||||
triggers[i].testLoaders = "error";
|
||||
alert(resp.error);
|
||||
} else {
|
||||
triggers[i].testLoaders = "success";
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error: " + error);
|
||||
} finally {
|
||||
triggers[i].testLoaders = "success";
|
||||
setTimeout(() => {
|
||||
triggers[i].testLoaders = "idle";
|
||||
}, 3000);
|
||||
@@ -426,6 +432,9 @@
|
||||
{#if trigger.testLoaders == "success"}
|
||||
<Check class="mr-1 inline h-3 w-3 text-green-500 " />
|
||||
{/if}
|
||||
{#if trigger.testLoaders == "error"}
|
||||
<X class="mr-1 inline h-3 w-3 text-red-500 " />
|
||||
{/if}
|
||||
<span class="h-4 text-xs font-medium"> Test Trigger </span>
|
||||
</Button>
|
||||
{#if $page.data.user.role != "member"}
|
||||
@@ -493,7 +502,7 @@
|
||||
newTrigger.trigger_type = "webhook";
|
||||
}}
|
||||
>
|
||||
<img src="{base}/webhooks.svg" title="webhook" class="mr-4 w-6" />
|
||||
<img src="{base}/webhooks.svg" title="webhook" class="mr-4 w-6" alt="Icon that represents webhooks" />
|
||||
Webhooks
|
||||
</Button>
|
||||
<Button
|
||||
@@ -503,7 +512,7 @@
|
||||
newTrigger.trigger_type = "discord";
|
||||
}}
|
||||
>
|
||||
<img src="{base}/discord.svg" title="discord" class="mr-4 w-6" />
|
||||
<img src="{base}/discord.svg" title="discord" class="mr-4 w-6" alt="Discord logo" />
|
||||
Discord
|
||||
</Button>
|
||||
<Button
|
||||
@@ -513,7 +522,7 @@
|
||||
newTrigger.trigger_type = "slack";
|
||||
}}
|
||||
>
|
||||
<img src="{base}/slack.svg" title="slack" class="mr-4 w-6" />
|
||||
<img src="{base}/slack.svg" title="slack" class="mr-4 w-6" alt="Slack logo" />
|
||||
Slack
|
||||
</Button>
|
||||
|
||||
@@ -524,7 +533,7 @@
|
||||
newTrigger.trigger_type = "email";
|
||||
}}
|
||||
>
|
||||
<img src="{base}/email.png" title="email" class="mr-4 w-6" />
|
||||
<img src="{base}/email.png" title="email" class="mr-4 w-6" alt="Icon that represents emails" />
|
||||
Email
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,15 @@
|
||||
let uptime90Day = monitor.pageData.uptime90Day;
|
||||
let incidents = {};
|
||||
let dayIncidentsFull = [];
|
||||
let homeDataMaxDays = monitor.pageData.homeDataMaxDays;
|
||||
|
||||
let dimension = {
|
||||
x1: 6,
|
||||
x2: 4
|
||||
};
|
||||
|
||||
dimension.x1 = ($page.data.isMobile ? 346 : 546) / homeDataMaxDays.maxDays;
|
||||
dimension.x2 = (4 / 6) * dimension.x1;
|
||||
|
||||
function loadIncidents() {
|
||||
axios
|
||||
@@ -91,39 +100,37 @@
|
||||
});
|
||||
}, 1000 * 0.2);
|
||||
}
|
||||
let uptimesRollers = [
|
||||
{
|
||||
text: `${l(lang, "90 Days")}`,
|
||||
startTs: monitor.pageData.startOfTheDay - 86400 * 89,
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp,
|
||||
value: uptime90Day
|
||||
},
|
||||
{
|
||||
text: `${l(lang, "60 Days")}`,
|
||||
startTs: monitor.pageData.startOfTheDay - 86400 * 59,
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp
|
||||
},
|
||||
{
|
||||
text: `${l(lang, "30 Days")}`,
|
||||
startTs: monitor.pageData.startOfTheDay - 86400 * 29,
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp
|
||||
},
|
||||
{
|
||||
text: `${l(lang, "14 Days")}`,
|
||||
startTs: monitor.pageData.startOfTheDay - 86400 * 13,
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp
|
||||
},
|
||||
{
|
||||
text: `${l(lang, "7 Days")}`,
|
||||
startTs: monitor.pageData.startOfTheDay - 86400 * 6,
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp
|
||||
},
|
||||
{
|
||||
text: l(lang, "Today"),
|
||||
startTs: monitor.pageData.startOfTheDay,
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp
|
||||
|
||||
function returnUptimeRollers() {
|
||||
let rollers = homeDataMaxDays.selectableDays;
|
||||
//sort descending
|
||||
rollers.sort((a, b) => b - a);
|
||||
let ret = [];
|
||||
for (let i = 0; i < rollers.length; i++) {
|
||||
let roller = rollers[i];
|
||||
if (roller == 1) {
|
||||
ret.push({
|
||||
text: `${l(lang, "Today")}`,
|
||||
startTs: monitor.pageData.startOfTheDay,
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp
|
||||
});
|
||||
} else {
|
||||
ret.push({
|
||||
text: `${roller} ${l(lang, "Days")}`,
|
||||
startTs: monitor.pageData.startOfTheDay - 86400 * (roller - 1),
|
||||
endTs: monitor.pageData.maxDateTodayTimestamp
|
||||
});
|
||||
}
|
||||
//if last index
|
||||
if (i == 0) {
|
||||
ret[i].value = uptime90Day;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let uptimesRollers = returnUptimeRollers();
|
||||
|
||||
//start of the week moment
|
||||
let rolledAt = 0;
|
||||
@@ -140,7 +147,10 @@
|
||||
uptimesRollers[newRolledAt].loading = true;
|
||||
|
||||
let resp = await axios.post(`${base}/api/today/aggregated`, {
|
||||
monitor: monitor,
|
||||
monitor: {
|
||||
tag: monitor.tag,
|
||||
include_degraded_in_downtime: monitor.include_degraded_in_downtime
|
||||
},
|
||||
startTs: uptimesRollers[newRolledAt].startTs,
|
||||
endTs: uptimesRollers[newRolledAt].endTs
|
||||
});
|
||||
@@ -201,7 +211,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="monitor relative grid w-full grid-cols-12 gap-2 pb-2 pt-0 md:w-[655px]">
|
||||
<div class="monitor relative grid w-full max-w-[655px] grid-cols-12 gap-2 pb-2 pt-0">
|
||||
{#if !!!embed}
|
||||
<div class="col-span-12 md:w-[546px]">
|
||||
<div class="pt-0">
|
||||
@@ -302,9 +312,9 @@
|
||||
showDailyDataModal = false;
|
||||
}}
|
||||
>
|
||||
<div class="daygrid90 flex min-h-[60px] justify-start overflow-x-auto overflow-y-hidden py-1">
|
||||
<div class="daygrid90 flex min-h-[60px] justify-between overflow-x-auto overflow-y-hidden py-1">
|
||||
{#each Object.entries(_90Day) as [ts, bar]}
|
||||
<a
|
||||
<button
|
||||
data-ts={ts}
|
||||
use:hoverAction
|
||||
on:hover={(e) => {
|
||||
@@ -313,24 +323,27 @@
|
||||
on:click={(e) => {
|
||||
dailyDataGetter(e, bar, incidents[ts]);
|
||||
}}
|
||||
href="#"
|
||||
class="oneline h-[34px] w-[6px]
|
||||
class="oneline h-[34px]
|
||||
{bar.border ? 'opacity-100' : 'opacity-20'} pb-1"
|
||||
style="width: {dimension.x1}px"
|
||||
>
|
||||
<div
|
||||
class="oneline-in h-[30px] bg-{bar.cssClass} mx-auto w-[4px] rounded-{monitor.pageData.barRoundness.toUpperCase() ==
|
||||
class="oneline-in h-[30px] bg-{bar.cssClass} mx-auto rounded-{monitor.pageData.barRoundness.toUpperCase() ==
|
||||
'SHARP'
|
||||
? 'none'
|
||||
: 'sm'}"
|
||||
style="width: {dimension.x2}px"
|
||||
></div>
|
||||
<!-- incident dot -->
|
||||
{#if !!incidents[ts]}
|
||||
<div
|
||||
class="bg-api-{incidents[
|
||||
ts
|
||||
].monitor_impact.toLowerCase()} comein absolute -bottom-[3px] left-[1px] h-[4px] w-[4px] rounded-full"
|
||||
].monitor_impact.toLowerCase()} comein absolute -bottom-[3px] h-[4px] w-[4px] rounded-full"
|
||||
style="left: {dimension.x1 / 2 - 2}px"
|
||||
></div>
|
||||
{/if}
|
||||
</a>
|
||||
</button>
|
||||
{#if bar.showDetails}
|
||||
<div class="show-hover absolute text-sm">
|
||||
<div class="text-{bar.textClass} text-xs font-semibold">
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
export let monitor;
|
||||
export let lang;
|
||||
export let selectedLang = "en";
|
||||
let theme = "light";
|
||||
let copiedLink = false;
|
||||
let embedType = "js";
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"%status for %duration": "%status für %duration",
|
||||
"120 Days": "120 Tage",
|
||||
"14 Days": "14 Tage",
|
||||
"150 Days": "150 Tage",
|
||||
"180 Days": "180 Tage",
|
||||
"30 Days": "30 Tage",
|
||||
"60 Days": "60 Tage",
|
||||
"7 Days": "7 Tage",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"60 Days": "60 Dage",
|
||||
"7 Days": "7 Dage",
|
||||
"90 Days": "90 Dage",
|
||||
"120 Days": "120 Dage",
|
||||
"150 Days": "150 Dage",
|
||||
"180 Days": "180 Dage",
|
||||
"Availability per Component": "Tilgængelighed pr. Komponent",
|
||||
"All Systems are Degraded": "Alle Systemer er Forringet",
|
||||
"All Systems are Down": "Alle Systemer er Nede",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"%status for %duration": "%status for %duration",
|
||||
"120 Days": "120 Days",
|
||||
"14 Days": "14 Days",
|
||||
"150 Days": "150 Days",
|
||||
"180 Days": "180 Days",
|
||||
"30 Days": "30 Days",
|
||||
"60 Days": "60 Days",
|
||||
"7 Days": "7 Days",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"60 Days": "60 jours",
|
||||
"7 Days": "7 jours",
|
||||
"90 Days": "90 jours",
|
||||
"120 Days": "120 jours",
|
||||
"150 Days": "150 jours",
|
||||
"180 Days": "180 jours",
|
||||
"Availability per Component": "Disponibilité par composant",
|
||||
"All Systems are Degraded": "Tous les systèmes sont dégradés",
|
||||
"All Systems are Down": "Tous les systèmes sont hors ligne",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"60 Days": "60 दिन",
|
||||
"7 Days": "7 दिन",
|
||||
"90 Days": "90 दिन",
|
||||
"120 Days": "120 दिन",
|
||||
"150 Days": "150 दिन",
|
||||
"180 Days": "180 दिन",
|
||||
"Availability per Component": "प्रत्येक घटक की उपलब्धता",
|
||||
"All Systems are Degraded": "सभी सिस्टम क्षीण हैं",
|
||||
"All Systems are Down": "सभी सिस्टम डाउन हैं",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"%status for %duration": "%duration の間 %status",
|
||||
"120 Days": "120日間",
|
||||
"14 Days": "14日間",
|
||||
"150 Days": "150日間",
|
||||
"180 Days": "180日間",
|
||||
"30 Days": "30日間",
|
||||
"60 Days": "60日間",
|
||||
"7 Days": "7日間",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"%status for %duration": "%duration 동안 %status 상태였음",
|
||||
"120 Days": "120 일",
|
||||
"14 Days": "14 일",
|
||||
"30 Days": "30 ",
|
||||
"150 Days": "150 일",
|
||||
"180 Days": "180 일",
|
||||
"30 Days": "30 일",
|
||||
"60 Days": "60 일",
|
||||
"7 Days": "7 일",
|
||||
"90 Days": "90 일",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"60 Days": "60 dager",
|
||||
"7 Days": "7 dager",
|
||||
"90 Days": "90 dager",
|
||||
"120 Days": "120 dager",
|
||||
"150 Days": "150 dager",
|
||||
"180 Days": "180 dager",
|
||||
"Availability per Component": "Tilgjengelighet per komponent",
|
||||
"All Systems are Degraded": "Alle systemer har nedsatt ytelse",
|
||||
"All Systems are Down": "Alle systemer er nede",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"%status for %duration": "%status voor %duration",
|
||||
"120 Days": "120 dagen",
|
||||
"14 Days": "14 dagen",
|
||||
"150 Days": "150 dagen",
|
||||
"180 Days": "180 dagen",
|
||||
"30 Days": "30 dagen",
|
||||
"60 Days": "60 dagen",
|
||||
"7 Days": "7 dagen",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"%status for %duration": "%status przez %duration",
|
||||
"120 Days": "120 dni",
|
||||
"14 Days": "14 dni",
|
||||
"150 Days": "150 dni",
|
||||
"180 Days": "180 dni",
|
||||
"30 Days": "30 dni",
|
||||
"60 Days": "60 dni",
|
||||
"7 Days": "7 dni",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"%status for %duration": "%status por %duration",
|
||||
"120 Days": "120 Dias",
|
||||
"14 Days": "14 Dias",
|
||||
"150 Days": "150 Dias",
|
||||
"180 Days": "180 Dias",
|
||||
"30 Days": "30 Dias",
|
||||
"60 Days": "60 Dias",
|
||||
"7 Days": "7 Dias",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"60 Days": "60 Дней",
|
||||
"7 Days": "7 Дней",
|
||||
"90 Days": "90 Дней",
|
||||
"120 Days": "120 Дней",
|
||||
"150 Days": "150 Дней",
|
||||
"180 Days": "180 Дней",
|
||||
"Availability per Component": "Доступность по компонентам",
|
||||
"All Systems are Degraded": "Все системы работают с ухудшенной производительностью",
|
||||
"All Systems are Down": "Все системы недоступны",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"60 Days": "60 Gün",
|
||||
"7 Days": "7 Gün",
|
||||
"90 Days": "90 Gün",
|
||||
"120 Days": "120 Gün",
|
||||
"150 Days": "150 Gün",
|
||||
"180 Days": "180 Gün",
|
||||
"Availability per Component": "Bileşen Kullanılabilirliği",
|
||||
"All Systems are Degraded": "Tüm Sistemler Bozuk Durumda",
|
||||
"All Systems are Down": "Tüm Sistemler Çalışmıyor",
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"60 Days": "60 ngày",
|
||||
"7 Days": "7 ngày",
|
||||
"90 Days": "90 ngày",
|
||||
"120 Days": "120 ngày",
|
||||
"150 Days": "150 ngày",
|
||||
"180 Days": "180 ngày",
|
||||
"Availability per Component": "Tính khả dụng theo thành phần",
|
||||
"All Systems are Degraded": "Tất cả hệ thống đang suy giảm hiệu suất",
|
||||
"All Systems are Down": "Tất cả hệ thống đang ngừng hoạt động",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"%status for %duration": "%duration 内的 %status",
|
||||
"120 Days": "120天",
|
||||
"14 Days": "14天",
|
||||
"150 Days": "150天",
|
||||
"180 Days": "180天",
|
||||
"30 Days": "30天",
|
||||
"60 Days": "60天",
|
||||
"7 Days": "7天",
|
||||
|
||||
@@ -26,6 +26,11 @@ export const siteDataKeys = [
|
||||
isValid: IsValidURL,
|
||||
data_type: "string",
|
||||
},
|
||||
{
|
||||
key: "homeDataMaxDays",
|
||||
isValid: IsValidJSONString,
|
||||
data_type: "object",
|
||||
},
|
||||
{
|
||||
key: "home",
|
||||
isValid: (value) => typeof value === "string" && value.trim().length > 0,
|
||||
|
||||
@@ -110,6 +110,8 @@ const Minuter = async (monitor) => {
|
||||
realTimeData[startOfMinute] = await serviceClient.execute();
|
||||
} else if (monitor.monitor_type === "HEARTBEAT") {
|
||||
realTimeData[startOfMinute] = await serviceClient.execute();
|
||||
} else if (monitor.monitor_type === "GAMEDIG") {
|
||||
realTimeData[startOfMinute] = await serviceClient.execute();
|
||||
}
|
||||
|
||||
manualData = await manualIncident(monitor);
|
||||
|
||||
@@ -97,9 +97,19 @@ const seedSiteData = {
|
||||
cssSrc: "",
|
||||
family: "",
|
||||
},
|
||||
categories: [{ name: "Home", description: "Monitors for Home Page" }],
|
||||
categories: [{ name: "Home", description: "Monitors for Home Page", isHidden: false }],
|
||||
homeIncidentCount: 5,
|
||||
homeIncidentStartTimeWithin: 30,
|
||||
homeDataMaxDays: {
|
||||
desktop: {
|
||||
maxDays: 90,
|
||||
selectableDays: [1, 7, 14, 30, 60, 90],
|
||||
},
|
||||
mobile: {
|
||||
maxDays: 90,
|
||||
selectableDays: [1, 7, 14, 30, 60, 90],
|
||||
},
|
||||
},
|
||||
kenerTheme: "default",
|
||||
showSiteStatus: "NO",
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ class Discord {
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending webhook", error);
|
||||
return error;
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ class Email {
|
||||
return await transporter.sendMail(mailOptions);
|
||||
} catch (error) {
|
||||
console.error("Error sending email via SMTP", error);
|
||||
return error;
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class Slack {
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending webhook", error);
|
||||
return error;
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +93,12 @@ class Webhook {
|
||||
body: this.transformData(data),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error from webhook`);
|
||||
throw new Error(`Error from webhook: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending webhook", error);
|
||||
return error;
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,13 +78,16 @@ function getCountOfSimilarStatuesEnd(arr, statusType) {
|
||||
return count;
|
||||
}
|
||||
|
||||
const FetchData = async function (site, monitor, localTz, selectedLang, lang) {
|
||||
const FetchData = async function (site, monitor, localTz, selectedLang, lang, isMobile) {
|
||||
const secondsInDay = 24 * 60 * 60;
|
||||
//get offset from utc in minutes
|
||||
const nowUTC = GetMinuteStartNowTimestampUTC();
|
||||
const midnightUTC = GetDayStartTimestampUTC(nowUTC);
|
||||
|
||||
let deviceType = isMobile ? "mobile" : "desktop";
|
||||
let homeDataMaxDays = site.homeDataMaxDays[deviceType];
|
||||
|
||||
const midnightTz = BeginningOfDay({ timeZone: localTz });
|
||||
const midnight90DaysAgoTz = midnightTz - 90 * 24 * 60 * 60;
|
||||
const midnight90DaysAgoTz = midnightTz - (homeDataMaxDays.maxDays - 1) * 24 * 60 * 60;
|
||||
const NO_DATA = "No Data";
|
||||
let offsetInMinutes = parseInt((GetDayStartTimestampUTC(nowUTC) - midnightTz) / 60);
|
||||
const maxDateTodayTimestampTz = BeginningOfMinute({ timeZone: localTz });
|
||||
@@ -198,6 +201,7 @@ const FetchData = async function (site, monitor, localTz, selectedLang, lang) {
|
||||
midnight90DaysAgo: midnight90DaysAgoTz,
|
||||
maxDateTodayTimestamp: maxDateTodayTimestampTz,
|
||||
startOfTheDay: midnightTz,
|
||||
homeDataMaxDays,
|
||||
};
|
||||
};
|
||||
export { FetchData };
|
||||
|
||||
@@ -2,61 +2,63 @@
|
||||
import i18n from "$lib/i18n/server";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import { base } from "$app/paths";
|
||||
import {
|
||||
GetAllSiteData,
|
||||
IsSetupComplete,
|
||||
IsLoggedInSession
|
||||
} from "$lib/server/controllers/controller.js";
|
||||
import MobileDetect from "mobile-detect";
|
||||
import { GetAllSiteData, IsSetupComplete, IsLoggedInSession } from "$lib/server/controllers/controller.js";
|
||||
|
||||
export async function load({ params, route, url, cookies, request }) {
|
||||
let isSetupComplete = await IsSetupComplete();
|
||||
if (!isSetupComplete) {
|
||||
throw redirect(302, base + "/manage/setup");
|
||||
}
|
||||
const userAgent = request.headers.get("user-agent");
|
||||
const md = new MobileDetect(userAgent);
|
||||
const isMobile = !!md.mobile();
|
||||
let isSetupComplete = await IsSetupComplete();
|
||||
if (!isSetupComplete) {
|
||||
throw redirect(302, base + "/manage/setup");
|
||||
}
|
||||
|
||||
if (process.env.KENER_SECRET_KEY === undefined) {
|
||||
throw redirect(302, base + "/manage/setup");
|
||||
}
|
||||
if (process.env.KENER_SECRET_KEY === undefined) {
|
||||
throw redirect(302, base + "/manage/setup");
|
||||
}
|
||||
const query = url.searchParams;
|
||||
let isLoggedIn = await IsLoggedInSession(cookies);
|
||||
|
||||
let isLoggedIn = await IsLoggedInSession(cookies);
|
||||
let site = await GetAllSiteData();
|
||||
const headers = request.headers;
|
||||
let localTz = "UTC";
|
||||
|
||||
let site = await GetAllSiteData();
|
||||
const headers = request.headers;
|
||||
const userAgent = headers.get("user-agent");
|
||||
let localTz = "UTC";
|
||||
const localTzCookie = cookies.get("localTz");
|
||||
if (!!localTzCookie) {
|
||||
localTz = localTzCookie;
|
||||
}
|
||||
let showNav = true;
|
||||
if (url.pathname.startsWith("/embed")) {
|
||||
showNav = false;
|
||||
}
|
||||
// if the user agent is lighthouse, then we are running a lighthouse test
|
||||
//if bot also set localTz to -1 to avoid reload
|
||||
let isBot = false;
|
||||
if (userAgent?.includes("Chrome-Lighthouse") || userAgent?.includes("bot")) {
|
||||
isBot = true;
|
||||
}
|
||||
const query = url.searchParams;
|
||||
//load all files from lib locales folder
|
||||
let selectedLang = query.get("locale") ? query.get("locale") : "en";
|
||||
const localTzQuery = query.get("tz");
|
||||
if (!!localTzQuery) {
|
||||
localTz = localTzQuery;
|
||||
}
|
||||
|
||||
let embed = false;
|
||||
if (route.id.endsWith("monitor-[tag]")) {
|
||||
embed = true;
|
||||
}
|
||||
let showNav = true;
|
||||
if (url.pathname.startsWith("/embed")) {
|
||||
showNav = false;
|
||||
}
|
||||
// if the user agent is lighthouse, then we are running a lighthouse test
|
||||
//if bot also set localTz to -1 to avoid reload
|
||||
let isBot = false;
|
||||
if (userAgent?.includes("Chrome-Lighthouse") || userAgent?.includes("bot")) {
|
||||
isBot = true;
|
||||
}
|
||||
|
||||
const bgc = query.get("bgc") ? "#" + query.get("bgc") : "";
|
||||
return {
|
||||
site: site,
|
||||
localTz: localTz,
|
||||
showNav,
|
||||
isBot,
|
||||
lang: i18n(String(selectedLang)),
|
||||
selectedLang: selectedLang,
|
||||
embed,
|
||||
bgc,
|
||||
isLoggedIn: !!isLoggedIn.user
|
||||
};
|
||||
//load all files from lib locales folder
|
||||
let selectedLang = query.get("locale") ? query.get("locale") : "en";
|
||||
|
||||
let embed = false;
|
||||
if (route.id.endsWith("monitor-[tag]")) {
|
||||
embed = true;
|
||||
}
|
||||
|
||||
const bgc = query.get("bgc") ? "#" + query.get("bgc") : "";
|
||||
return {
|
||||
site: site,
|
||||
localTz: localTz,
|
||||
showNav,
|
||||
isBot,
|
||||
lang: i18n(String(selectedLang)),
|
||||
selectedLang: selectedLang,
|
||||
embed,
|
||||
bgc,
|
||||
isLoggedIn: !!isLoggedIn.user,
|
||||
isMobile: isMobile,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,24 +29,8 @@
|
||||
} else {
|
||||
defaultLocaleValue = allLocales.find((locale) => locale.code === defaultLocaleKey).name;
|
||||
}
|
||||
/**
|
||||
* @param {string} locale
|
||||
*/
|
||||
function setLanguage(locale) {
|
||||
document.cookie = `localLang=${locale};max-age=${60 * 60 * 24 * 365 * 30}`;
|
||||
if (locale === defaultLocaleKey) return;
|
||||
defaultLocaleValue = allLocales[locale];
|
||||
}
|
||||
|
||||
let Analytics;
|
||||
onMount(async () => {
|
||||
let localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
if (localTz != data.localTz) {
|
||||
if (data.isBot === false) {
|
||||
document.cookie = "localTz=" + localTz + ";max-age=" + 60 * 60 * 24 * 365 * 30;
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
if (defaultTheme != "none") {
|
||||
setMode(defaultTheme);
|
||||
}
|
||||
|
||||
@@ -3,33 +3,34 @@ import { FetchData } from "$lib/server/page";
|
||||
import { GetMonitors } from "$lib/server/controllers/controller.js";
|
||||
|
||||
export async function load({ params, route, url, parent }) {
|
||||
let monitors = await GetMonitors({ status: "ACTIVE" });
|
||||
const parentData = await parent();
|
||||
const siteData = parentData.site;
|
||||
const monitorsActive = [];
|
||||
const query = url.searchParams;
|
||||
const theme = query.get("theme");
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
//only return monitors that have category as home or category is not present
|
||||
if (monitors[i].tag !== params.tag) {
|
||||
continue;
|
||||
}
|
||||
delete monitors[i].api;
|
||||
delete monitors[i].default_status;
|
||||
monitors[i].embed = true;
|
||||
let data = await FetchData(
|
||||
siteData,
|
||||
monitors[i],
|
||||
parentData.localTz,
|
||||
parentData.selectedLang,
|
||||
parentData.lang
|
||||
);
|
||||
monitors[i].pageData = data;
|
||||
monitorsActive.push(monitors[i]);
|
||||
}
|
||||
return {
|
||||
monitors: monitorsActive,
|
||||
theme,
|
||||
openIncidents: []
|
||||
};
|
||||
let monitors = await GetMonitors({ status: "ACTIVE" });
|
||||
const parentData = await parent();
|
||||
const siteData = parentData.site;
|
||||
const monitorsActive = [];
|
||||
const query = url.searchParams;
|
||||
const theme = query.get("theme");
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
//only return monitors that have category as home or category is not present
|
||||
if (monitors[i].tag !== params.tag) {
|
||||
continue;
|
||||
}
|
||||
delete monitors[i].api;
|
||||
delete monitors[i].default_status;
|
||||
monitors[i].embed = true;
|
||||
let data = await FetchData(
|
||||
siteData,
|
||||
monitors[i],
|
||||
parentData.localTz,
|
||||
parentData.selectedLang,
|
||||
parentData.lang,
|
||||
parentData.isMobile,
|
||||
);
|
||||
monitors[i].pageData = data;
|
||||
monitorsActive.push(monitors[i]);
|
||||
}
|
||||
return {
|
||||
monitors: monitorsActive,
|
||||
theme,
|
||||
openIncidents: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
// @ts-nocheck
|
||||
// @ts-ignore
|
||||
export async function GET({ url, params }) {
|
||||
const { tag } = params;
|
||||
const query = url.searchParams;
|
||||
const uriEmbedded = query.get("monitor");
|
||||
const theme = query.get("theme") || "light";
|
||||
const bgc = query.get("bgc") || "transparent";
|
||||
const locale = query.get("locale") || "en";
|
||||
const uriOriginal = uriEmbedded;
|
||||
const { tag } = params;
|
||||
const query = url.searchParams;
|
||||
const uriEmbedded = query.get("monitor");
|
||||
const theme = query.get("theme") || "light";
|
||||
const bgc = query.get("bgc") || "transparent";
|
||||
const locale = query.get("locale") || "en";
|
||||
const tz = query.get("tz") || "NONE";
|
||||
const uriOriginal = uriEmbedded;
|
||||
|
||||
const currentSlug = tag;
|
||||
const js = `
|
||||
const currentSlug = tag;
|
||||
const js = `
|
||||
(function () {
|
||||
//find user timezone
|
||||
let localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
let tz = "${tz}";
|
||||
if(tz === "NONE") {
|
||||
if(!!localTz){
|
||||
tz = localTz;
|
||||
} else {
|
||||
tz = "UTC";
|
||||
}
|
||||
}
|
||||
|
||||
var createEmbedFrame = function () {
|
||||
var uid = "KENER_" + ~~(new Date().getTime() / 86400000);
|
||||
var uriOriginal = "${uriOriginal}";
|
||||
var uriOriginalNoProtocol = uriOriginal.split("//").pop();
|
||||
var uriEmbedded = "${uriEmbedded}?theme=${theme}&bgc=${bgc}&locale=${locale}";
|
||||
var uriEmbedded = "${uriEmbedded}?theme=${theme}&bgc=${bgc}&locale=${locale}&tz=" + tz;
|
||||
var currentSlug = "${currentSlug}";
|
||||
var target = document.querySelector("script[src*='" + uriOriginalNoProtocol + "']");
|
||||
var iframe = document.createElement("iframe");
|
||||
@@ -66,9 +78,9 @@ export async function GET({ url, params }) {
|
||||
}).call(this);
|
||||
|
||||
`;
|
||||
return new Response(js, {
|
||||
headers: {
|
||||
"Content-Type": "application/javascript"
|
||||
}
|
||||
});
|
||||
return new Response(js, {
|
||||
headers: {
|
||||
"Content-Type": "application/javascript",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import i18n from "$lib/i18n/server";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import MobileDetect from "mobile-detect";
|
||||
|
||||
import { base } from "$app/paths";
|
||||
import {
|
||||
GetAllSiteData,
|
||||
@@ -11,6 +13,10 @@ import {
|
||||
} from "$lib/server/controllers/controller.js";
|
||||
|
||||
export async function load({ params, route, url, cookies, request }) {
|
||||
const userAgent = request.headers.get("user-agent");
|
||||
const md = new MobileDetect(userAgent);
|
||||
const isMobile = !!md.mobile();
|
||||
|
||||
let isSetupComplete = await IsSetupComplete();
|
||||
if (!isSetupComplete) {
|
||||
throw redirect(302, base + "/manage/setup");
|
||||
@@ -24,7 +30,6 @@ export async function load({ params, route, url, cookies, request }) {
|
||||
|
||||
let site = await GetAllSiteData();
|
||||
const headers = request.headers;
|
||||
const userAgent = headers.get("user-agent");
|
||||
let localTz = "UTC";
|
||||
const localTzCookie = cookies.get("localTz");
|
||||
if (!!localTzCookie) {
|
||||
@@ -50,5 +55,6 @@ export async function load({ params, route, url, cookies, request }) {
|
||||
selectedLang: selectedLang,
|
||||
isLoggedIn: !!isLoggedIn.user,
|
||||
canSendEmail: IsEmailSetup(),
|
||||
isMobile: isMobile,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FetchData } from "$lib/server/page";
|
||||
import { GetMonitors, GetIncidentsOpenHome, SystemDataMessage } from "$lib/server/controllers/controller.js";
|
||||
import { SortMonitor } from "$lib/clientTools.js";
|
||||
import moment from "moment";
|
||||
import { redirect, error } from "@sveltejs/kit";
|
||||
|
||||
function removeTags(str) {
|
||||
if (str === null || str === "") return false;
|
||||
@@ -106,7 +107,14 @@ export async function load({ parent, url }) {
|
||||
monitors = SortMonitor(siteData.monitorSort, monitors);
|
||||
const monitorsActive = [];
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
let data = await FetchData(siteData, monitors[i], parentData.localTz, parentData.selectedLang, parentData.lang);
|
||||
let data = await FetchData(
|
||||
siteData,
|
||||
monitors[i],
|
||||
parentData.localTz,
|
||||
parentData.selectedLang,
|
||||
parentData.lang,
|
||||
parentData.isMobile,
|
||||
);
|
||||
monitors[i].pageData = data;
|
||||
|
||||
monitors[i].activeIncidents = [];
|
||||
@@ -150,6 +158,9 @@ export async function load({ parent, url }) {
|
||||
let allCategories = siteData.categories;
|
||||
let selectedCategory = allCategories.find((category) => category.name === query.get("category"));
|
||||
if (selectedCategory) {
|
||||
if (!!selectedCategory.isHidden && !parentData.isLoggedIn) {
|
||||
throw error(404, "Category not found");
|
||||
}
|
||||
pageTitle = selectedCategory.name + " - " + pageTitle;
|
||||
pageDescription = selectedCategory.description;
|
||||
canonical = canonical + "?category=" + query.get("category");
|
||||
@@ -159,6 +170,8 @@ export async function load({ parent, url }) {
|
||||
subtitle: selectedCategory.description,
|
||||
image: selectedCategory.image,
|
||||
};
|
||||
} else {
|
||||
throw error(404, "Category not found");
|
||||
}
|
||||
}
|
||||
if (isCategoryPage || isMonitorPage || isGroupPage) {
|
||||
@@ -176,7 +189,6 @@ export async function load({ parent, url }) {
|
||||
return isPresent;
|
||||
});
|
||||
}
|
||||
|
||||
allOpenIncidents = allOpenIncidents.map((incident) => {
|
||||
let incidentMonitors = incident.monitors;
|
||||
let monitorTags = incidentMonitors.map((monitor) => monitor.monitor_tag);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { scale } from "svelte/transition";
|
||||
import { format } from "date-fns";
|
||||
import GMI from "$lib/components/gmi.svelte";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
export let data;
|
||||
let shareMenusToggle = false;
|
||||
@@ -242,10 +243,10 @@
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
class="section-monitors z-20 mx-auto mb-8 flex w-full flex-1 flex-col items-start justify-center backdrop-blur-[2px] md:w-[655px]"
|
||||
class="section-monitors z-20 mx-auto mb-8 flex w-full max-w-[655px] flex-1 flex-col items-center justify-center"
|
||||
>
|
||||
<Card.Root class="monitor-root">
|
||||
<Card.Content class="monitors-card p-0">
|
||||
<Card.Root class="monitor-root w-full">
|
||||
<Card.Content class="monitors-card p-0 ">
|
||||
{#each data.monitors as monitor}
|
||||
<Monitor
|
||||
on:show_shareMenu={showShareMenu}
|
||||
@@ -261,9 +262,9 @@
|
||||
{/if}
|
||||
{#if data.site.categories && data.pageType == "home"}
|
||||
<section
|
||||
class="section-categories relative z-10 mx-auto mb-8 w-full max-w-[890px] flex-1 flex-col items-start backdrop-blur-[2px] md:w-[655px]"
|
||||
class="section-categories relative z-10 mx-auto mb-8 w-full max-w-[655px] flex-1 flex-col items-start backdrop-blur-[2px] md:w-[655px]"
|
||||
>
|
||||
{#each data.site.categories.filter((e) => e.name != "Home") as category}
|
||||
{#each data.site.categories.filter((category) => category.name != "Home" && (!!!category.isHidden || $page.data.isLoggedIn)) as category}
|
||||
<a href={`?category=${category.name}`} rel="external">
|
||||
<Card.Root class="mb-4 hover:bg-secondary">
|
||||
<Card.Header class="bounce-right relative w-full cursor-pointer px-4 ">
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import { onMount } from "svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import ExternalLink from "lucide-svelte/icons/external-link";
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<div class="min-h-[70vh]">
|
||||
|
||||
@@ -233,6 +233,10 @@ export async function POST({ request, cookies }) {
|
||||
],
|
||||
};
|
||||
resp = await notificationClient.send(testObj);
|
||||
//check if resp is error
|
||||
if (resp.error) {
|
||||
throw new Error(resp.error);
|
||||
}
|
||||
} else if (action == "testMonitor") {
|
||||
let monitorID = data.monitor_id;
|
||||
let monitors = await GetMonitorsParsed({ id: monitorID });
|
||||
|
||||
@@ -845,38 +845,38 @@
|
||||
<div
|
||||
class="bg-hover state-{newComment.state} mt-2 grid grid-cols-4 overflow-hidden rounded-md border text-xs font-medium"
|
||||
>
|
||||
<div
|
||||
<button
|
||||
class="col-span-1 cursor-pointer px-2 py-2 text-center hover:underline"
|
||||
on:click={() => {
|
||||
setCommentState("INVESTIGATING");
|
||||
}}
|
||||
>
|
||||
INVESTIGATING
|
||||
</div>
|
||||
<div
|
||||
</button>
|
||||
<button
|
||||
class="col-span-1 cursor-pointer px-2 py-2 text-center hover:underline"
|
||||
on:click={() => {
|
||||
setCommentState("IDENTIFIED");
|
||||
}}
|
||||
>
|
||||
IDENTIFIED
|
||||
</div>
|
||||
<div
|
||||
</button>
|
||||
<button
|
||||
class="col-span-1 cursor-pointer px-2 py-2 text-center hover:underline"
|
||||
on:click={() => {
|
||||
setCommentState("MONITORING");
|
||||
}}
|
||||
>
|
||||
MONITORING
|
||||
</div>
|
||||
<div
|
||||
</button>
|
||||
<button
|
||||
class="col-span-1 cursor-pointer px-2 py-2 text-center hover:underline"
|
||||
on:click={() => {
|
||||
setCommentState("RESOLVED");
|
||||
}}
|
||||
>
|
||||
RESOLVED
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mt-4 flex w-full gap-4">
|
||||
|
||||
Reference in New Issue
Block a user