mirror of
https://github.com/rajnandan1/kener.git
synced 2026-01-06 09:30:21 -06:00
fixed api: reopen issue if updated, added footer optional, responsive share menu, update sitemap
This commit is contained in:
@@ -27,4 +27,10 @@ nav:
|
||||
hero:
|
||||
title: Kener is a Open-Source Status Page System
|
||||
subtitle: Let your users know what's going on.
|
||||
footerHTML: |
|
||||
Made using
|
||||
<a href="https://github.com/rajnandan1/kener" target="_blank" rel="noreferrer" class="font-medium underline underline-offset-4">
|
||||
Kener
|
||||
</a>
|
||||
an open source status page system built with Svelte and TailwindCSS.
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kener",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kener",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
|
||||
@@ -51,7 +51,7 @@ const GetAllGHLabels = async function (owner, repo) {
|
||||
console.log(GhnotconfireguredMsg);
|
||||
return [];
|
||||
}
|
||||
const options = getAxiosOptions(`https://api.github.com/repos/${owner}/${repo}/labels`);
|
||||
const options = getAxiosOptions(`https://api.github.com/repos/${owner}/${repo}/labels?per_page=1000`);
|
||||
|
||||
let labels = [];
|
||||
try {
|
||||
@@ -231,7 +231,7 @@ async function CreateIssue(githubConfig, issueTitle, issueBody, issueLabels) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function UpdateIssue(githubConfig, incidentNumber, issueTitle, issueBody, issueLabels) {
|
||||
async function UpdateIssue(githubConfig, incidentNumber, issueTitle, issueBody, issueLabels, state = "open") {
|
||||
if (githubConfig.owner === undefined || githubConfig.repo === undefined || GH_TOKEN === undefined) {
|
||||
console.log(GhnotconfireguredMsg);
|
||||
return null;
|
||||
@@ -242,6 +242,7 @@ async function UpdateIssue(githubConfig, incidentNumber, issueTitle, issueBody,
|
||||
title: issueTitle,
|
||||
body: issueBody,
|
||||
labels: issueLabels,
|
||||
state: state,
|
||||
};
|
||||
const response = await axios.request(patchAxiosOptions(url, payload));
|
||||
return response.data;
|
||||
@@ -285,7 +286,7 @@ async function AddComment(githubConfig, incidentNumber, commentBody) {
|
||||
}
|
||||
}
|
||||
//update issue labels
|
||||
async function UpdateIssueLabels(githubConfig, incidentNumber, issueLabels, body) {
|
||||
async function UpdateIssueLabels(githubConfig, incidentNumber, issueLabels, body, state = "open") {
|
||||
if (githubConfig.owner === undefined || githubConfig.repo === undefined || GH_TOKEN === undefined) {
|
||||
console.log(GhnotconfireguredMsg);
|
||||
return null;
|
||||
@@ -295,8 +296,9 @@ async function UpdateIssueLabels(githubConfig, incidentNumber, issueLabels, body
|
||||
const payload = {
|
||||
labels: issueLabels,
|
||||
body: body,
|
||||
state: state,
|
||||
};
|
||||
const response = await axios.request(postAxiosOptions(url, payload));
|
||||
const response = await axios.request(patchAxiosOptions(url, payload));
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.log(error.response.data);
|
||||
|
||||
@@ -23,6 +23,16 @@ if(site.siteURL !== undefined && site.siteURL !== null && site.siteURL !== ""){
|
||||
</url>`;
|
||||
})
|
||||
.join("\n")}
|
||||
${monitors
|
||||
.map((monitor) => {
|
||||
return `<url>
|
||||
<loc>${site.siteURL}/monitor-${encodeURIComponent(monitor.folderName)}</loc>
|
||||
<lastmod>${new Date().toISOString()}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>`;
|
||||
})
|
||||
.join("\n")}
|
||||
</urlset>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
export let state = "open";
|
||||
export let monitor;
|
||||
let blinker = "bg-transparent"
|
||||
let incidentPriority = ""
|
||||
let incidentPriority = "";
|
||||
let incidentDuration = 0;
|
||||
if(incident.labels.includes("incident-down")){
|
||||
blinker = "bg-red-500";
|
||||
incidentPriority = "DOWN"
|
||||
@@ -26,17 +27,14 @@
|
||||
let incidentCreatedAt = incident.incident_start_time;
|
||||
let incidentMessage = "";
|
||||
if(!!incidentClosedAt && !!incidentCreatedAt){
|
||||
//diff between closed_at and created_at
|
||||
let diff = moment(incidentClosedAt * 1000).add(1, "minutes").diff(moment(incidentCreatedAt * 1000), 'minutes');
|
||||
//incidentDuration between closed_at and created_at
|
||||
incidentDuration = moment(incidentClosedAt * 1000).add(1, "minutes").diff(moment(incidentCreatedAt * 1000), 'minutes');
|
||||
|
||||
if(diff > 0) {
|
||||
incidentMessage = `. Was <span class="text-${StatusObj[incidentPriority]}">${incidentPriority}</span> for ${diff} minutes`;
|
||||
}
|
||||
|
||||
|
||||
} else if(!!incidentCreatedAt){
|
||||
//diff between now and created_at
|
||||
let diff = moment().diff(moment(incidentCreatedAt * 1000), 'minutes');
|
||||
incidentMessage = `. Has been <span class="text-${StatusObj[incidentPriority]}">${incidentPriority}</span> for ${diff} minutes`;
|
||||
//incidentDuration between now and created_at
|
||||
incidentDuration = moment().diff(moment(incidentCreatedAt * 1000), 'minutes');
|
||||
}
|
||||
|
||||
//find a replace /\[start_datetime:(\d+)\]/ empty in incident.body
|
||||
@@ -49,7 +47,7 @@
|
||||
<div class="col-span-3">
|
||||
<Card.Root>
|
||||
|
||||
<Card.Header>
|
||||
<Card.Header class="pb-1">
|
||||
<Card.Title class="relative">
|
||||
{#if variant.includes("monitor")}
|
||||
<div class="pb-4">
|
||||
@@ -107,7 +105,16 @@
|
||||
</Card.Title>
|
||||
<Card.Description>
|
||||
{moment(incidentCreatedAt * 1000).format("MMMM Do YYYY, h:mm:ss a")}
|
||||
{@html incidentMessage}
|
||||
{#if incidentPriority != "" && incidentDuration > 0}
|
||||
<p class="leading-10">
|
||||
<Badge class="text-[rgba(0,0,0,.6)] text-xs bg-{StatusObj[incidentPriority]}">
|
||||
{incidentPriority} for {incidentDuration} minute{incidentDuration > 1 ? "s" : ""}
|
||||
</Badge>
|
||||
</p>
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
<p class="mt-2">
|
||||
{#if incident.labels.includes("identified")}
|
||||
<span class="bg-yellow-100 text-yellow-800 mt-1 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-yellow-900 dark:text-yellow-300">Identified</span>
|
||||
@@ -115,6 +122,9 @@
|
||||
{#if incident.labels.includes("resolved")}
|
||||
<span class="bg-green-100 text-green-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-green-900 dark:text-green-300">Resolved</span>
|
||||
{/if}
|
||||
{#if incident.labels.includes("maintenance")}
|
||||
<span class="bg-blue-100 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">Maintenance</span>
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { Badge } from "$lib/components/ui/badge";
|
||||
import * as HoverCard from "$lib/components/ui/hover-card";
|
||||
import * as Popover from "$lib/components/ui/popover";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import { onMount } from "svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
@@ -135,28 +135,33 @@
|
||||
<div class="scroll-m-20 text-2xl font-semibold tracking-tight">
|
||||
{#if monitor.image}
|
||||
<img src="{monitor.image}" class="w-6 h-6 inline" alt="{monitor.name}" srcset="" />
|
||||
{/if} {monitor.name} {#if monitor.description}
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger>
|
||||
{/if}
|
||||
<span>
|
||||
{monitor.name}
|
||||
</span>
|
||||
<br>
|
||||
{#if monitor.description}
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<span class=" pt-0 pl-1 menu-monitor pr-0 pb-0 {buttonVariants({ variant: 'link' })}">
|
||||
<Info size="{16}" />
|
||||
<Info size="{12}" class="text-muted-foreground" />
|
||||
</span>
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Content class="text-sm">
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="text-sm">
|
||||
<h2 class="mb-2 text-lg font-semibold">{monitor.name}</h2>
|
||||
<span class="text-muted-foreground text-sm">
|
||||
{@html monitor.description}
|
||||
</span>
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Root>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
{/if}
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger >
|
||||
<Popover.Root>
|
||||
<Popover.Trigger >
|
||||
<span class=" pt-0 pl-1 pb-0 menu-monitor pr-0 {buttonVariants({ variant: 'link' })}">
|
||||
<Share2 size="{16}" />
|
||||
<Share2 size="{12}" class="text-muted-foreground" />
|
||||
</span>
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Content class=" pl-1 pr-1 pb-1 w-[375px]">
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class=" pl-1 pr-1 pb-1 w-[375px]">
|
||||
<h2 class="mb-1 text-lg font-semibold px-2">Share</h2>
|
||||
<p class="pl-2 mb-2 text-muted-foreground text-sm">
|
||||
Share this monitor using a link with others.
|
||||
@@ -255,12 +260,12 @@
|
||||
</span>
|
||||
{/if}
|
||||
</Button>
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Root>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="">
|
||||
<div class="grid grid-cols-2 gap-0">
|
||||
<div class="col-span-1 -mt-2">
|
||||
<a href="/incident/{monitor.folderName}#past_incident" class="pt-0 pl-0 pb-0 text-indigo-500 text-left {buttonVariants({ variant: 'link' })}">
|
||||
@@ -336,11 +341,5 @@
|
||||
.daygrid90::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
.monitor .menu-monitor{
|
||||
/* cursor: copy; */
|
||||
visibility: hidden;
|
||||
}
|
||||
.monitor:hover .menu-monitor{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
</style>
|
||||
14
src/lib/components/ui/popover/index.ts
Normal file
14
src/lib/components/ui/popover/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||
import Content from "./popover-content.svelte";
|
||||
const Root = PopoverPrimitive.Root;
|
||||
const Trigger = PopoverPrimitive.Trigger;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
//
|
||||
Root as Popover,
|
||||
Content as PopoverContent,
|
||||
Trigger as PopoverTrigger
|
||||
};
|
||||
22
src/lib/components/ui/popover/popover-content.svelte
Normal file
22
src/lib/components/ui/popover/popover-content.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils";
|
||||
|
||||
type $$Props = PopoverPrimitive.ContentProps;
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<PopoverPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</PopoverPrimitive.Content>
|
||||
@@ -36,11 +36,11 @@ function getDayData(day0, startTime, endTime) {
|
||||
|
||||
if (dayData.DEGRADED > 0) {
|
||||
cssClass = StatusObj.DEGRADED;
|
||||
message = "Degraded for " + dayData.DEGRADED + " minutes";
|
||||
message = "Degraded for " + dayData.DEGRADED + " minute" + (dayData.DEGRADED > 1 ? "s" : "");
|
||||
}
|
||||
if (dayData.DOWN > 0) {
|
||||
cssClass = StatusObj.DOWN;
|
||||
message = "Down for " + dayData.DOWN + " minutes";
|
||||
message = "Down for " + dayData.DOWN + " minute" + (dayData.DOWN > 1 ? "s" : "");
|
||||
}
|
||||
if(dayData.DEGRADED + dayData.DOWN + dayData.UP > 0){
|
||||
dayData.message = message;
|
||||
@@ -74,7 +74,7 @@ const FetchData = async function (monitor, localTz) {
|
||||
index: (i - midnight) / 60,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
let day0 = JSON.parse(fs.readFileSync(monitor.path0Day, "utf8"));
|
||||
|
||||
for (const timestamp in day0) {
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
import "../app.postcss";
|
||||
import "../kener.css";
|
||||
import Nav from "$lib/components/nav.svelte";
|
||||
import { onMount } from "svelte";
|
||||
export let data;
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
onMount(() => {
|
||||
let localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
import { onMount } from "svelte";
|
||||
export let data;
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(";").shift();
|
||||
}
|
||||
onMount(() => {
|
||||
let localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
if (localTz != data.localTz) {
|
||||
document.cookie = "localTz=" + localTz + ";max-age=" + 60 * 60 * 24 * 365 * 30;
|
||||
location.reload();
|
||||
@@ -18,13 +18,24 @@
|
||||
});
|
||||
</script>
|
||||
{#if data.showNav}
|
||||
<Nav {data} />
|
||||
<Nav {data} />
|
||||
{/if}
|
||||
<svelte:head>
|
||||
<title>{data.site.title}</title>
|
||||
{#each Object.entries(data.site.metaTags) as [key, value]}
|
||||
<meta name={key} content={value} />
|
||||
{/each}
|
||||
<title>{data.site.title}</title>
|
||||
{#each Object.entries(data.site.metaTags) as [key, value]}
|
||||
<meta name="{key}" content="{value}" />
|
||||
{/each}
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
|
||||
<slot />
|
||||
{#if data.showNav && !!data.site.footerHTML}
|
||||
<footer class="py-6 z-10 md:px-8 md:py-0">
|
||||
<div class="container relative flex flex-col pl-0 items-center justify-center max-w-[890px] gap-4 md:h-24 md:flex-row">
|
||||
<div class="flex flex-col items-center gap-4 px-8 md:flex-row md:gap-2 md:px-0">
|
||||
<p class="text-center text-sm leading-loose text-muted-foreground md:text-left">
|
||||
{@html data.site.footerHTML}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{/if}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</section>
|
||||
{/if} {#if data.site.categories}
|
||||
<section class="mx-auto backdrop-blur-[2px] mb-8 w-full max-w-[890px]">
|
||||
<h2 class="text-xl mb-2 mt-2 font-semibold">
|
||||
<h2 class="text-xl px-2 mb-2 mt-2 font-semibold">
|
||||
Other Monitors
|
||||
</h2>
|
||||
{#each data.site.categories as category}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="mx-auto flex-1 mt-8 flex-col mb-4 flex w-full" id="active_incident">
|
||||
<section class="mx-auto flex-1 mt-8 flex-col mb-4 flex w-full" >
|
||||
<div class="container">
|
||||
<h1 class="mb-4 text-2xl font-bold leading-none">
|
||||
<Badge variant="outline"> Active Incidents </Badge>
|
||||
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<Separator class="container mb-4 w-[400px]" />
|
||||
<section class="mx-auto flex-1 mt-8 flex-col mb-4 flex w-full" id="active_incident">
|
||||
<section class="mx-auto flex-1 mt-8 flex-col mb-4 flex w-full" >
|
||||
<div class="container">
|
||||
<h1 class="mb-4 text-2xl font-bold leading-none">
|
||||
<Badge variant="outline"> Recent Incidents </Badge>
|
||||
|
||||
Reference in New Issue
Block a user