mirror of
https://github.com/wahyd4/upptime.git
synced 2026-08-25 21:16:55 +10:00
Merge pull request #14 from koj-co/feature-sapper-site
Feature sapper site
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
/node_modules/
|
||||
/src/node_modules/@sapper/
|
||||
yarn-error.log
|
||||
/cypress/screenshots/
|
||||
/__sapper__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"video": false
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
describe('Sapper template app', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
});
|
||||
|
||||
it('has the correct <h1>', () => {
|
||||
cy.contains('h1', 'Great success!')
|
||||
});
|
||||
|
||||
it('navigates to /about', () => {
|
||||
cy.get('nav a').contains('about').click();
|
||||
cy.url().should('include', '/about');
|
||||
});
|
||||
|
||||
it('navigates to /blog', () => {
|
||||
cy.get('nav a').contains('blog').click();
|
||||
cy.url().should('include', '/blog');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
Generated
+2569
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "koj-status-app",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "sapper dev",
|
||||
"build": "sapper build --legacy",
|
||||
"export": "sapper export --legacy",
|
||||
"start": "node __sapper__/build",
|
||||
"cy:run": "cypress run",
|
||||
"cy:open": "cypress open",
|
||||
"test": "run-p --race dev cy:run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^18.0.3",
|
||||
"compression": "^1.7.1",
|
||||
"polka": "next",
|
||||
"sirv": "^1.0.0",
|
||||
"snarkdown": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sapper": "^0.28.0",
|
||||
"svelte": "^3.17.3",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@rollup/plugin-babel": "^5.0.0",
|
||||
"@rollup/plugin-commonjs": "^14.0.0",
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"@rollup/plugin-replace": "^2.2.0",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-svelte": "^5.0.1",
|
||||
"rollup-plugin-terser": "^6.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import svelte from "rollup-plugin-svelte";
|
||||
import babel from "@rollup/plugin-babel";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import config from "sapper/config/rollup.js";
|
||||
import pkg from "./package.json";
|
||||
|
||||
const mode = process.env.NODE_ENV;
|
||||
const dev = mode === "development";
|
||||
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
|
||||
|
||||
const onwarn = (warning, onwarn) =>
|
||||
(warning.code === "MISSING_EXPORT" && /'preload'/.test(warning.message)) ||
|
||||
(warning.code === "CIRCULAR_DEPENDENCY" &&
|
||||
/[/\\]@sapper[/\\]/.test(warning.message)) ||
|
||||
onwarn(warning);
|
||||
|
||||
export default {
|
||||
client: {
|
||||
input: config.client.input(),
|
||||
output: config.client.output(),
|
||||
plugins: [
|
||||
replace({
|
||||
"process.browser": true,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
}),
|
||||
svelte({
|
||||
dev,
|
||||
hydratable: true,
|
||||
emitCss: true,
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
|
||||
legacy &&
|
||||
babel({
|
||||
extensions: [".js", ".mjs", ".html", ".svelte"],
|
||||
babelHelpers: "runtime",
|
||||
exclude: ["node_modules/@babel/**"],
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
targets: "> 0.25%, not dead",
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
[
|
||||
"@babel/plugin-transform-runtime",
|
||||
{
|
||||
useESModules: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
|
||||
!dev &&
|
||||
terser({
|
||||
module: true,
|
||||
}),
|
||||
],
|
||||
|
||||
preserveEntrySignatures: false,
|
||||
onwarn,
|
||||
},
|
||||
|
||||
server: {
|
||||
input: config.server.input(),
|
||||
output: config.server.output(),
|
||||
plugins: [
|
||||
replace({
|
||||
"process.browser": false,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
}),
|
||||
svelte({
|
||||
generate: "ssr",
|
||||
hydratable: true,
|
||||
dev,
|
||||
}),
|
||||
resolve({
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
external: Object.keys(pkg.dependencies).concat(
|
||||
require("module").builtinModules
|
||||
),
|
||||
|
||||
preserveEntrySignatures: "strict",
|
||||
onwarn,
|
||||
},
|
||||
|
||||
serviceworker: {
|
||||
input: config.serviceworker.input(),
|
||||
output: config.serviceworker.output(),
|
||||
plugins: [
|
||||
resolve(),
|
||||
replace({
|
||||
"process.browser": true,
|
||||
"process.env.NODE_ENV": JSON.stringify(mode),
|
||||
}),
|
||||
commonjs(),
|
||||
!dev && terser(),
|
||||
],
|
||||
|
||||
preserveEntrySignatures: false,
|
||||
onwarn,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import * as sapper from '@sapper/app';
|
||||
|
||||
sapper.start({
|
||||
target: document.querySelector('#sapper')
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
<script>
|
||||
import Loading from "../components/Loading.svelte";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let loading = true;
|
||||
const octokit = new Octokit({
|
||||
userAgent: "KojBot",
|
||||
});
|
||||
const owner = "koj-co";
|
||||
const repo = "status";
|
||||
let incidents = [];
|
||||
|
||||
onMount(async () => {
|
||||
incidents = (
|
||||
await octokit.issues.listForRepo({
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
filter: "all",
|
||||
sort: "created",
|
||||
direction: "desc",
|
||||
labels: "status",
|
||||
})
|
||||
).data;
|
||||
incidents = incidents.map((incident, index) => {
|
||||
incident.showHeading =
|
||||
index === 0 ||
|
||||
new Date(incidents[index - 1].created_at).toLocaleDateString() !==
|
||||
new Date(incident.created_at).toLocaleDateString();
|
||||
return incident;
|
||||
});
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
article {
|
||||
background-color: #ffdce3;
|
||||
}
|
||||
article.good {
|
||||
background-color: #dcffeb;
|
||||
color: #003300;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if !incidents.length && !loading}
|
||||
<article class="good">✅ All systems are operational</article>
|
||||
{/if}
|
||||
|
||||
<section>
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else if incidents.length}
|
||||
{#each incidents as incident}
|
||||
<h2>Active Incidents</h2>
|
||||
<article class="down">
|
||||
<div class="f">
|
||||
<div>
|
||||
<h4>{incident.title.replace('🛑', '').replace('⚠️', '').trim()}</h4>
|
||||
<div>
|
||||
Opened at {new Date(incident.created_at).toLocaleString()} with {incident.comments}
|
||||
posts
|
||||
</div>
|
||||
</div>
|
||||
<div class="f r">
|
||||
<a href={`/${incident.number}`}>Incident report →</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
{/if}
|
||||
</section>
|
||||
@@ -0,0 +1,145 @@
|
||||
<script>
|
||||
import Loading from "../components/Loading.svelte";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { onMount } from "svelte";
|
||||
import snarkdown from "snarkdown";
|
||||
|
||||
export let number;
|
||||
|
||||
let md = snarkdown;
|
||||
let loading = true;
|
||||
let loadingIncident = true;
|
||||
|
||||
const octokit = new Octokit({
|
||||
userAgent: "KojBot",
|
||||
});
|
||||
const owner = "koj-co";
|
||||
const repo = "status";
|
||||
let comments = [];
|
||||
let incident = {};
|
||||
|
||||
onMount(async () => {
|
||||
incident = (
|
||||
await octokit.issues.get({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: number,
|
||||
sort: "created",
|
||||
direction: "desc",
|
||||
})
|
||||
).data;
|
||||
loadingIncident = false;
|
||||
comments = (
|
||||
await octokit.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: number,
|
||||
})
|
||||
).data.reverse();
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
dl {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
dl dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
dl dd {
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
h2 {
|
||||
line-height: 1;
|
||||
}
|
||||
.closed {
|
||||
background-color: #16a085;
|
||||
}
|
||||
.open {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
.closed,
|
||||
.open {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
padding: 0.33rem 0.5rem;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 70%;
|
||||
vertical-align: middle;
|
||||
margin-top: -0.2rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.r {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<title>Incident #{number} Details</title>
|
||||
</svelte:head>
|
||||
|
||||
<h2>
|
||||
{#if loadingIncident}
|
||||
Incident Details
|
||||
{:else}
|
||||
{incident.title}
|
||||
<span class={incident.state}>
|
||||
{incident.state === 'closed' ? 'Fixed' : 'Ongoing'}
|
||||
</span>
|
||||
{/if}
|
||||
</h2>
|
||||
|
||||
<section>
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="f">
|
||||
<dl>
|
||||
<dt>Opened at</dt>
|
||||
<dd>{new Date(incident.created_at).toLocaleString()}</dd>
|
||||
<dt>Closed at</dt>
|
||||
{#if incident.closed_at}
|
||||
<dd>{new Date(incident.closed_at).toLocaleString()}</dd>
|
||||
{/if}
|
||||
</dl>
|
||||
<div class="r">
|
||||
<p>
|
||||
<a href={`https://github.com/koj-co/status/issues/${number}`}>
|
||||
Subscribe to Updates
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href={`https://github.com/koj-co/status/issues/${number}`}>
|
||||
View on GitHub
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{#each comments as comment}
|
||||
<article>
|
||||
<p>
|
||||
{@html md(comment.body)}
|
||||
</p>
|
||||
<div>
|
||||
Posted at
|
||||
<a href={comment.html_url}>
|
||||
{new Date(comment.created_at).toLocaleString()}
|
||||
</a>
|
||||
by
|
||||
<a href={comment.user.html_url}>@{comment.user.login}</a>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<a href="/">← Back to all comments</a>
|
||||
</footer>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script>
|
||||
import Loading from "../components/Loading.svelte";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let loading = true;
|
||||
const octokit = new Octokit({
|
||||
userAgent: "KojBot",
|
||||
});
|
||||
const owner = "koj-co";
|
||||
const repo = "status";
|
||||
let incidents = [];
|
||||
|
||||
onMount(async () => {
|
||||
incidents = (
|
||||
await octokit.issues.listForRepo({
|
||||
owner,
|
||||
repo,
|
||||
state: "closed",
|
||||
filter: "all",
|
||||
sort: "created",
|
||||
direction: "desc",
|
||||
labels: "status",
|
||||
})
|
||||
).data;
|
||||
incidents = incidents.map((incident, index) => {
|
||||
incident.showHeading =
|
||||
index === 0 ||
|
||||
new Date(incidents[index - 1].created_at).toLocaleDateString() !==
|
||||
new Date(incident.created_at).toLocaleDateString();
|
||||
return incident;
|
||||
});
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<h2>Past Incidents</h2>
|
||||
|
||||
<section>
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
{#each incidents as incident}
|
||||
{#if incident.showHeading}
|
||||
<h3>{new Date(incident.created_at).toLocaleDateString()}</h3>
|
||||
{/if}
|
||||
<article class="down">
|
||||
<div class="f">
|
||||
<div>
|
||||
<h4>{incident.title.replace('🛑', '').replace('⚠️', '').trim()}</h4>
|
||||
<div>
|
||||
Resolved in {((new Date(incident.closed_at).getTime() - new Date(incident.created_at).getTime()) / 60000).toFixed(0)}
|
||||
minutes with {incident.comments} posts
|
||||
</div>
|
||||
</div>
|
||||
<div class="f r">
|
||||
<a href={`/${incident.number}`}>Incident report →</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
{/if}
|
||||
</section>
|
||||
@@ -0,0 +1,28 @@
|
||||
<style>
|
||||
.loading {
|
||||
text-align: center;
|
||||
margin: 2.5rem 0;
|
||||
}
|
||||
svg {
|
||||
zoom: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="loading">
|
||||
<svg width="38" height="38" xmlns="http://www.w3.org/2000/svg" stroke="#aaa">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(1 1)" stroke-width="2">
|
||||
<circle stroke-opacity=".5" cx="18" cy="18" r="18" />
|
||||
<path d="M36 18c0-9.94-8.06-18-18-18">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 18 18"
|
||||
to="360 18 18"
|
||||
dur="1s"
|
||||
repeatCount="indefinite" />
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script>
|
||||
export let segment;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
font-weight: 300;
|
||||
padding: 0 1em;
|
||||
background-color: #fff;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
[aria-current] {
|
||||
border-bottom: 2px solid #3498db;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
padding: 1.5rem 2rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: left;
|
||||
height: 3.33rem;
|
||||
margin-top: 0.75rem;
|
||||
margin-right: 2rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<nav>
|
||||
<div class="container">
|
||||
<img
|
||||
class="logo"
|
||||
alt="Koj"
|
||||
src="https://avatars1.githubusercontent.com/u/65495851?s=200&v=4" />
|
||||
<ul>
|
||||
<li>
|
||||
<a aria-current={segment === undefined ? 'page' : undefined} href=".">
|
||||
Status
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a aria-current={segment === 'about' ? 'page' : undefined} href="about">
|
||||
About
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/">GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script context="module">
|
||||
export async function preload(page) {
|
||||
const { number } = page.params;
|
||||
return { number };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import Incident from "../components/Incident.svelte";
|
||||
export let number;
|
||||
</script>
|
||||
|
||||
<Incident {number} />
|
||||
@@ -0,0 +1,41 @@
|
||||
<script>
|
||||
export let status;
|
||||
export let error;
|
||||
|
||||
const dev = process.env.NODE_ENV === "development";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
h1,
|
||||
p {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.8em;
|
||||
font-weight: 700;
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em auto;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
h1 {
|
||||
font-size: 4em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<title>{status}</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1>{status}</h1>
|
||||
|
||||
<p>{error.message}</p>
|
||||
|
||||
{#if dev && error.stack}
|
||||
<pre>{error.stack}</pre>
|
||||
{/if}
|
||||
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import Nav from "../components/Nav.svelte";
|
||||
|
||||
export let segment;
|
||||
</script>
|
||||
|
||||
<Nav {segment} />
|
||||
|
||||
<main class="container">
|
||||
<slot />
|
||||
</main>
|
||||
@@ -0,0 +1,13 @@
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1>About this site</h1>
|
||||
|
||||
<p>
|
||||
This site is built with
|
||||
<a href="/">Svelte</a>
|
||||
using
|
||||
<a href="/">Sapper</a>
|
||||
, and uses the official GitHub API to fetch data from the status repository.
|
||||
</p>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import ActiveIncidents from "../components/ActiveIncidents.svelte";
|
||||
import Incidents from "../components/Incidents.svelte";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Koj Status</title>
|
||||
</svelte:head>
|
||||
|
||||
<ActiveIncidents />
|
||||
<Incidents />
|
||||
@@ -0,0 +1,17 @@
|
||||
import sirv from 'sirv';
|
||||
import polka from 'polka';
|
||||
import compression from 'compression';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT, NODE_ENV } = process.env;
|
||||
const dev = NODE_ENV === 'development';
|
||||
|
||||
polka() // You can also use Express
|
||||
.use(
|
||||
compression({ threshold: 0 }),
|
||||
sirv('static', { dev }),
|
||||
sapper.middleware()
|
||||
)
|
||||
.listen(PORT, err => {
|
||||
if (err) console.log('error', err);
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
import { timestamp, files, shell, routes } from '@sapper/service-worker';
|
||||
|
||||
const ASSETS = `cache${timestamp}`;
|
||||
|
||||
// `shell` is an array of all the files generated by the bundler,
|
||||
// `files` is an array of everything in the `static` directory
|
||||
const to_cache = shell.concat(files);
|
||||
const cached = new Set(to_cache);
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
event.waitUntil(
|
||||
caches
|
||||
.open(ASSETS)
|
||||
.then(cache => cache.addAll(to_cache))
|
||||
.then(() => {
|
||||
self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(
|
||||
caches.keys().then(async keys => {
|
||||
// delete old caches
|
||||
for (const key of keys) {
|
||||
if (key !== ASSETS) await caches.delete(key);
|
||||
}
|
||||
|
||||
self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
|
||||
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// don't try to handle e.g. data: URIs
|
||||
if (!url.protocol.startsWith('http')) return;
|
||||
|
||||
// ignore dev server requests
|
||||
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
|
||||
|
||||
// always serve static files and bundler-generated assets from cache
|
||||
if (url.host === self.location.host && cached.has(url.pathname)) {
|
||||
event.respondWith(caches.match(event.request));
|
||||
return;
|
||||
}
|
||||
|
||||
// for pages, you might want to serve a shell `service-worker-index.html` file,
|
||||
// which Sapper has generated for you. It's not right for every
|
||||
// app, but if it's right for yours then uncomment this section
|
||||
/*
|
||||
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
|
||||
event.respondWith(caches.match('/service-worker-index.html'));
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
if (event.request.cache === 'only-if-cached') return;
|
||||
|
||||
// for everything else, try the network first, falling back to
|
||||
// cache if the user is offline. (If the pages never change, you
|
||||
// might prefer a cache-first approach to a network-first one.)
|
||||
event.respondWith(
|
||||
caches
|
||||
.open(`offline${timestamp}`)
|
||||
.then(async cache => {
|
||||
try {
|
||||
const response = await fetch(event.request);
|
||||
cache.put(event.request, response.clone());
|
||||
return response;
|
||||
} catch(err) {
|
||||
const response = await cache.match(event.request);
|
||||
if (response) return response;
|
||||
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<meta name="theme-color" content="#333333">
|
||||
|
||||
%sapper.base%
|
||||
|
||||
<link rel="stylesheet" href="global.css">
|
||||
<link rel="manifest" href="manifest.json" crossorigin="use-credentials">
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
|
||||
<!-- Sapper creates a <script> tag containing `src/client.js`
|
||||
and anything else it needs to hydrate the app and
|
||||
initialise the router -->
|
||||
%sapper.scripts%
|
||||
|
||||
<!-- Sapper generates a <style> tag containing critical CSS
|
||||
for the current page. CSS for the rest of the app is
|
||||
lazily loaded when it precaches secondary pages -->
|
||||
%sapper.styles%
|
||||
|
||||
<!-- This contains the contents of the <svelte:head> component, if
|
||||
the current page has one -->
|
||||
%sapper.head%
|
||||
</head>
|
||||
<body>
|
||||
<!-- The application will be rendered inside this element,
|
||||
because `src/client.js` references it -->
|
||||
<div id="sapper">%sapper.html%</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
line-height: 1.5;
|
||||
padding-bottom: 2.5rem;
|
||||
color: #333;
|
||||
background-color: #f0f7f7;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0 0 0.5em 0;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: menlo, inconsolata, monospace;
|
||||
font-size: calc(1em - 2px);
|
||||
color: #555;
|
||||
background-color: #f0f0f0;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
@media (min-width: 400px) {
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
article {
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
article + article {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
article + h3 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
h3 {
|
||||
opacity: 0.75;
|
||||
}
|
||||
h4 {
|
||||
font-weight: bold;
|
||||
}
|
||||
article.down {
|
||||
border-left: 0.2rem solid #e74c3c;
|
||||
}
|
||||
article.up {
|
||||
border-left: 0.2rem solid #2ecc71;
|
||||
}
|
||||
|
||||
.f {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
section + h2 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#333333",
|
||||
"name": "TODO",
|
||||
"short_name": "TODO",
|
||||
"display": "minimal-ui",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "logo-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "logo-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user