Initial commit
22
.github/workflows/docker-image.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: Publish Docker image
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Push to GitHub Packages
|
||||
uses: docker/build-push-action@v1
|
||||
with:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: ${{ github.repository }}/${{ github.event.repository.name }}
|
||||
registry: docker.pkg.github.com
|
||||
tag_with_ref: true
|
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
.env
|
||||
.vscode/
|
||||
dist/
|
||||
node_modules/
|
6
Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
FROM node:lts
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN ls
|
||||
RUN npm install
|
||||
ENTRYPOINT npm start
|
50
client/index.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Pi-helper</title>
|
||||
<meta charset="utf-8">
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="application-name" content="Twigs">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="apple-touch-icon" href="/static/icons/icon-192x192.png">
|
||||
<link rel="stylesheet" href="/static/css/style.css" />
|
||||
<script type="text/javascript" src="/static/js/index.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="monitorChanges()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 404 404" class="spin" id="logo">
|
||||
<g id="group" transform="translate(66.72238 66.72238)">
|
||||
<path id="path"
|
||||
d="M 135.467 61.19 C 94.537 61.19 61.19 94.537 61.19 135.467 C 61.19 147.777 64.21 159.399 69.542 169.636 C 64.167 175.795 61.2 183.696 61.191 191.871 C 61.191 200.851 64.762 209.472 71.112 215.822 C 77.462 222.172 86.083 225.743 95.063 225.743 C 100.983 225.736 106.799 224.178 111.929 221.224 C 117.059 218.269 121.326 214.021 124.303 208.904 C 124.303 208.904 124.304 208.903 124.304 208.903 C 127.946 209.455 131.674 209.743 135.467 209.743 C 176.397 209.743 209.744 176.397 209.744 135.467 C 209.744 94.537 176.397 61.19 135.467 61.19 Z M 135.467 76.748 C 167.99 76.748 194.192 102.943 194.192 135.466 C 194.192 167.989 167.99 194.191 135.467 194.191 C 133.225 194.191 131.014 194.062 128.839 193.819 C 128.839 193.817 128.839 193.814 128.839 193.812 C 128.89 193.165 128.922 192.517 128.936 191.869 C 128.935 182.889 125.364 174.268 119.014 167.919 C 112.664 161.569 104.043 157.999 95.063 157.999 C 90.692 158.007 86.364 158.86 82.318 160.512 C 82.318 160.512 82.318 160.511 82.317 160.511 C 82.315 160.512 82.313 160.513 82.311 160.514 C 78.743 152.923 76.747 144.437 76.747 135.467 C 76.747 102.944 102.942 76.749 135.465 76.749 Z"
|
||||
fill="#000000" stroke="#000000" stroke-width="1.72941" />
|
||||
</g>
|
||||
</svg>
|
||||
<div class="enabled" id="enabled">
|
||||
<p>Status: <span class="status">Enabled</span></p>
|
||||
<button onclick="disable(30)">Disable for 30 seconds</button>
|
||||
<button onclick="disable(60)">Disable for 1 minute</button>
|
||||
<button onclick="disable(300)">Disable for 5 minutes</button>
|
||||
<button onclick="showEnable(false, true)">Disable for custom time</button>
|
||||
<button onclick="disable()">Disable Permanently</button>
|
||||
</div>
|
||||
<div class="disabled" id="disabled">
|
||||
<p>Status: <span class="status">Disabled</span><span class="status" id="duration"></span></p>
|
||||
<button onclick="enable()">Enable</button>
|
||||
</div>
|
||||
<div class="enabled custom" id="disable-custom">
|
||||
<p>Disable for a custom time</p>
|
||||
<input type="number" id="disable-duration" value="10" />
|
||||
<div class="units">
|
||||
<button id="seconds" class="unit selected" onclick="setUnit('seconds')">Seconds</button>
|
||||
<button id="minutes" class="unit" onclick="setUnit('minutes')">Minutes</button>
|
||||
<button id="hours" class="unit" onclick="setUnit('hours')">Hours</button>
|
||||
<input type="hidden" name="unit" value="seconds" id="unit" />
|
||||
</div>
|
||||
<button onclick="disableCustom()">Disable</button>
|
||||
<button class="outline" onclick="showEnable(true)">Cancel</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
99
client/manifest.json
Normal file
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"name": "Pi-helper",
|
||||
"short_name": "Pi-helper",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#000000",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "static/icons/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "static/icons/icon-maskable-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
18
client/package.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "client",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prebuild": "copyfiles -Ve src/** index.html manifest.json **/*.css **/*.png ../dist/public",
|
||||
"build": "tsc --project ./",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.4.1",
|
||||
"eslint": "^8.8.0",
|
||||
"typescript": "^4.5.5"
|
||||
}
|
||||
}
|
187
client/src/index.ts
Normal file
|
@ -0,0 +1,187 @@
|
|||
let socket: WebSocket | null = null;
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/static/js/sw.js')
|
||||
.then(function (registration) {
|
||||
console.log('Registration successful, scope is:', registration.scope);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Service worker registration failed, error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function monitorChanges() {
|
||||
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
socket = new WebSocket(`${protocol}://${location.host}/`)
|
||||
socket.onopen = (e) => {
|
||||
console.log('socket opened', e)
|
||||
}
|
||||
|
||||
socket.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data)
|
||||
console.log('message received', data)
|
||||
switch (data.status) {
|
||||
case 'enabled':
|
||||
if (durationInterval) {
|
||||
clearInterval(durationInterval)
|
||||
}
|
||||
animateLogo(false)
|
||||
showDisable(false)
|
||||
showEnable(true)
|
||||
break
|
||||
case 'disabled':
|
||||
animateLogo(false)
|
||||
showEnable(false, false)
|
||||
showDisable(true, data.until)
|
||||
break
|
||||
default:
|
||||
console.error('Unhandled status', data)
|
||||
}
|
||||
}
|
||||
|
||||
socket.onclose = (e) => {
|
||||
console.log('socket closed', e)
|
||||
setTimeout(monitorChanges, 1000)
|
||||
}
|
||||
|
||||
socket.onerror = (e) => {
|
||||
console.error('socket error', e)
|
||||
}
|
||||
}
|
||||
|
||||
function enable() {
|
||||
if (!socket) return;
|
||||
showDisable(false)
|
||||
animateLogo(true)
|
||||
const command = { action: 'enable' };
|
||||
socket.send(JSON.stringify(command))
|
||||
}
|
||||
|
||||
function disable(duration: number) {
|
||||
if (!socket) return;
|
||||
showEnable(false)
|
||||
animateLogo(true)
|
||||
const command = { action: 'disable', duration: duration };
|
||||
socket.send(JSON.stringify(command))
|
||||
}
|
||||
|
||||
function setUnit(unit: string) {
|
||||
const unitInput = document.getElementById('unit') as HTMLInputElement
|
||||
const units = document.getElementsByClassName('unit')
|
||||
for (let i = 0; i < units.length; i++) {
|
||||
if (units[i].id === unit) {
|
||||
units[i].classList.add('selected')
|
||||
} else {
|
||||
units[i].classList.remove('selected')
|
||||
}
|
||||
}
|
||||
unitInput.value = unit
|
||||
}
|
||||
|
||||
function disableCustom() {
|
||||
|
||||
showEnable(false, false)
|
||||
}
|
||||
|
||||
function animateLogo(animate: boolean) {
|
||||
const logo = document.getElementById('logo') as HTMLElement
|
||||
if (animate) {
|
||||
logo.classList.add('spin')
|
||||
} else {
|
||||
logo.classList.remove('spin')
|
||||
}
|
||||
}
|
||||
|
||||
function showEnable(show: boolean, showCustom?: boolean) {
|
||||
const enableDiv = document.getElementById('enabled') as HTMLElement
|
||||
const disableCustom = document.getElementById('disable-custom') as HTMLElement
|
||||
if (show) {
|
||||
if (disableCustom.style.opacity === '1') {
|
||||
disableCustom.style.opacity = '0'
|
||||
setTimeout(() => {
|
||||
disableCustom.style.maxHeight = '0'
|
||||
enableDiv.style.maxHeight = '100vh'
|
||||
}, 250)
|
||||
setTimeout(() => {
|
||||
enableDiv.style.opacity = '1'
|
||||
}, 500)
|
||||
} else {
|
||||
enableDiv.style.maxHeight = '100vh'
|
||||
setTimeout(() => {
|
||||
enableDiv.style.opacity = '1'
|
||||
}, 250)
|
||||
}
|
||||
} else {
|
||||
enableDiv.style.opacity = '0'
|
||||
setTimeout(() => {
|
||||
enableDiv.style.maxHeight = '0'
|
||||
}, 250)
|
||||
if (showCustom) {
|
||||
setTimeout(() => {
|
||||
disableCustom.style.maxHeight = '100vh'
|
||||
}, 250)
|
||||
setTimeout(() => {
|
||||
disableCustom.style.opacity = '1'
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let durationInterval: any;
|
||||
|
||||
function showDisable(show: boolean, timestamp?: number) {
|
||||
const disableDiv = document.getElementById('disabled') as HTMLElement
|
||||
if (show) {
|
||||
disableDiv.style.maxHeight = '100vh'
|
||||
} else {
|
||||
disableDiv.style.opacity = '0'
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (show) {
|
||||
disableDiv.style.opacity = '1'
|
||||
} else {
|
||||
disableDiv.style.maxHeight = '0'
|
||||
}
|
||||
}, 250)
|
||||
const duration = document.getElementById('duration') as HTMLElement
|
||||
if (!timestamp) {
|
||||
duration.innerText = ''
|
||||
return
|
||||
}
|
||||
const until = new Date(timestamp)
|
||||
function updateDuration() {
|
||||
const now = new Date();
|
||||
const difference = until.getTime() - now.getTime();
|
||||
if (durationInterval && difference <= 0) {
|
||||
duration.innerText = ''
|
||||
clearInterval(durationInterval)
|
||||
if (socket) {
|
||||
const command = { action: 'status' };
|
||||
socket.send(JSON.stringify(command))
|
||||
}
|
||||
return
|
||||
}
|
||||
let seconds = Math.ceil(difference / 1000)
|
||||
console.log(`${until.getTime()} - ${now.getTime()} = ${seconds} seconds`)
|
||||
let hours = 0
|
||||
let minutes = 0
|
||||
let durationText: string = '';
|
||||
if (seconds >= 3600) {
|
||||
hours = Math.floor(seconds / 3600)
|
||||
seconds -= hours * 3600
|
||||
}
|
||||
console.log(`hours: ${hours} seconds: ${seconds}`)
|
||||
if (seconds >= 60) {
|
||||
minutes = Math.floor(seconds / 60)
|
||||
seconds -= minutes * 60
|
||||
}
|
||||
console.log(`minutes: ${minutes} seconds: ${seconds}`)
|
||||
if (hours > 0) {
|
||||
durationText += `${hours.toString().padStart(2, '0')}:`
|
||||
}
|
||||
durationText += `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
|
||||
duration.innerText = ` (${durationText})`
|
||||
}
|
||||
durationInterval = setInterval(updateDuration, 1000)
|
||||
}
|
49
client/src/sw.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
const filesToCache = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/static/css/style.css',
|
||||
'/static/icons/icon-128x128.png',
|
||||
'/static/icons/icon-144x144.png',
|
||||
'/static/icons/icon-152x152.png',
|
||||
'/static/icons/icon-192x192.png',
|
||||
'/static/icons/icon-384x384.png',
|
||||
'/static/icons/icon-512x512.png',
|
||||
'/static/icons/icon-72x72.png',
|
||||
'/static/icons/icon-96x96.png',
|
||||
'/static/icons/icon-maskable-128x128.png',
|
||||
'/static/icons/icon-maskable-144x144.png',
|
||||
'/static/icons/icon-maskable-152x152.png',
|
||||
'/static/icons/icon-maskable-192x192.png',
|
||||
'/static/icons/icon-maskable-384x384.png',
|
||||
'/static/icons/icon-maskable-512x512.png',
|
||||
'/static/icons/icon-maskable-72x72.png',
|
||||
'/static/icons/icon-maskable-96x96.png'
|
||||
];
|
||||
|
||||
const staticCacheName = 'pages-cache-v1';
|
||||
|
||||
// TODO: Import this properly
|
||||
type InstallEvent = any;
|
||||
|
||||
self.addEventListener('install', (event: InstallEvent) => {
|
||||
console.log('Attempting to install service worker and cache static assets');
|
||||
event.waitUntil(
|
||||
caches.open(staticCacheName)
|
||||
.then(cache => {
|
||||
return cache.addAll(filesToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event: InstallEvent) => {
|
||||
event.respondWith(
|
||||
caches.match(event.request).then((response) => {
|
||||
caches.open(staticCacheName).then((cache) => {
|
||||
if (event.request.url.match(/^https?/)) {
|
||||
cache.add(event.request.url);
|
||||
}
|
||||
})
|
||||
return response || fetch(event.request);
|
||||
})
|
||||
);
|
||||
});
|
163
client/static/css/style.css
Normal file
|
@ -0,0 +1,163 @@
|
|||
:root {
|
||||
--color-red-light: #f60d1a;
|
||||
--color-red-dark: #96060c;
|
||||
--color-green-light: #29fc2e;
|
||||
--color-green-dark: #22b225;
|
||||
--color-grey-light: #cbcbcb;
|
||||
--color-grey-medium: #cbcbcb;
|
||||
--color-grey-dark: #333333;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-background: #FFFFFF;
|
||||
--color-foreground: #000000;
|
||||
--color-foreground-enabled: var(--color-green-dark);
|
||||
--color-foreground-disabled: var(--color-red-light);
|
||||
--color-background-enabled: var(--color-green-dark);
|
||||
--color-background-disabled: var(--color-red-light);
|
||||
--color-onbackground-enabled: #FFFFFF;
|
||||
--color-onbackground-disabled: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: #000000;
|
||||
--color-foreground: #F1F1F1;
|
||||
--color-foreground-enabled: var(--color-green-dark);
|
||||
--color-foreground-disabled: var(--color-red-dark);
|
||||
--color-background-enabled: var(--color-green-dark);
|
||||
--color-background-disabled: var(--color-red-dark);
|
||||
--color-onbackground-enabled: white;
|
||||
--color-onbackground-disabled: white;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
body, div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 400px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div, div > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 10em;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: var(--color-foreground);
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 5px;
|
||||
border: 0;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button, p {
|
||||
margin: 5px 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#enabled, #disabled, #disable-custom {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.enabled .status {
|
||||
color: var(--color-foreground-enabled);
|
||||
}
|
||||
|
||||
.enabled button {
|
||||
background-color: var(--color-background-disabled);
|
||||
color: var(--color-onbackground-disabled);
|
||||
}
|
||||
|
||||
.enabled button.outline {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
border: 1px solid var(--color-foreground-disabled);
|
||||
}
|
||||
|
||||
.disabled .status {
|
||||
color: var(--color-red-light);
|
||||
}
|
||||
|
||||
.disabled button {
|
||||
background-color: var(--color-background-enabled);
|
||||
color: var(--color-onbackground-enabled);
|
||||
}
|
||||
|
||||
.disabled button.outline {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
border: 1px solid var(--color-foreground-enabled);
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation-name: spin;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg) }
|
||||
to { transform: rotate(360deg) }
|
||||
}
|
||||
|
||||
.custom * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.units {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.enabled button.unit {
|
||||
background: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.enabled button.unit.selected {
|
||||
background: var(--color-grey-medium);
|
||||
color: var(--color-grey-dark);
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: var(--color-background);
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--color-foreground-disabled);
|
||||
color: var(--color-foreground);
|
||||
font-size: 1em;
|
||||
margin: 5px 0;
|
||||
padding: 10px;
|
||||
}
|
BIN
client/static/icons/icon-128x128.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
client/static/icons/icon-144x144.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
client/static/icons/icon-152x152.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
client/static/icons/icon-192x192.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
client/static/icons/icon-384x384.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
client/static/icons/icon-512x512.png
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
client/static/icons/icon-72x72.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
client/static/icons/icon-96x96.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
client/static/icons/icon-maskable-128x128.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
client/static/icons/icon-maskable-144x144.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
client/static/icons/icon-maskable-152x152.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
client/static/icons/icon-maskable-192x192.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
client/static/icons/icon-maskable-384x384.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
client/static/icons/icon-maskable-512x512.png
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
client/static/icons/icon-maskable-72x72.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
client/static/icons/icon-maskable-96x96.png
Normal file
After Width: | Height: | Size: 13 KiB |
101
client/tsconfig.json
Normal file
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files */
|
||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "../dist/public/static/js", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
||||
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
3558
package-lock.json
generated
Normal file
19
package.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "pihelper",
|
||||
"version": "1.0.0",
|
||||
"description": "A PWA to manage the status of your Pi-hole",
|
||||
"scripts": {
|
||||
"build": "npm run build --workspaces",
|
||||
"prestart": "npm run -w client build",
|
||||
"start": "npm run -w server start"
|
||||
},
|
||||
"author": "William Brawner <me@wbrawner.com>",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"devDependencies": {
|
||||
"concurrently": "^7.0.0"
|
||||
}
|
||||
}
|
130
server/index.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
import express from 'express'
|
||||
import ws from 'ws'
|
||||
import http from 'http'
|
||||
|
||||
const port = process.env.PIHELPER_PORT || 3000;
|
||||
const pihole = process.env.PIHELPER_PIHOLE_HOST || 'http://pi.hole';
|
||||
const apiKey: string = process.env.PIHELPER_API_KEY || '';
|
||||
|
||||
if (apiKey === '') {
|
||||
console.error("PIHELPER_API_KEY not set, aborting")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const app = express();
|
||||
const wss = new ws.Server({ noServer: true });
|
||||
const clients: Array<ws> = []
|
||||
wss.on('connection', socket => {
|
||||
clients.push(socket)
|
||||
socket.on('close', () => {
|
||||
const index = clients.indexOf(socket);
|
||||
if (index > -1) {
|
||||
delete clients[index];
|
||||
}
|
||||
})
|
||||
socket.on('message', message => {
|
||||
const command = JSON.parse(message.toString())
|
||||
switch (command.action) {
|
||||
case 'status':
|
||||
status(status => {
|
||||
clients.forEach(client => {
|
||||
client.send(status)
|
||||
})
|
||||
})
|
||||
break
|
||||
case 'enable':
|
||||
enable(status => {
|
||||
clients.forEach(client => {
|
||||
client.send(status)
|
||||
})
|
||||
})
|
||||
break
|
||||
case 'disable':
|
||||
disable(command.duration, status => {
|
||||
clients.forEach(client => {
|
||||
client.send(status)
|
||||
})
|
||||
})
|
||||
break
|
||||
default:
|
||||
socket.send(`{"error": "Invalid command sent: '${command.action}'"}`)
|
||||
}
|
||||
})
|
||||
status(status => {
|
||||
clients.forEach(client => {
|
||||
client.send(status)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Handle errors better than this
|
||||
function get(url: URL, callback: (data: string) => void, error?: (error?: Error) => void) {
|
||||
const req = http.request(url, res => {
|
||||
res.on('data', data => {
|
||||
callback(data.toString())
|
||||
})
|
||||
})
|
||||
req.on('response', res => {
|
||||
if (res.statusCode != 200 && error) {
|
||||
error()
|
||||
}
|
||||
})
|
||||
req.on('error', e => {
|
||||
console.error(`Failed to send GET request to ${url}`, e)
|
||||
if (error) {
|
||||
error(e)
|
||||
}
|
||||
})
|
||||
req.end()
|
||||
}
|
||||
|
||||
function status(callback: (status: any) => void) {
|
||||
let url = new URL(`${pihole}/admin/api.php`)
|
||||
get(url, data => {
|
||||
let dataObj = JSON.parse(data)
|
||||
if (dataObj.status === 'disabled') {
|
||||
url = new URL(`${pihole}/custom_disable_timer`)
|
||||
get(url, timestamp => {
|
||||
dataObj["until"] = Number.parseInt(timestamp)
|
||||
callback(JSON.stringify(dataObj))
|
||||
}, e => {
|
||||
callback(data)
|
||||
})
|
||||
} else {
|
||||
callback(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function enable(callback: (status: any) => void) {
|
||||
let url = new URL(`${pihole}/admin/api.php`)
|
||||
url.searchParams.append('enable', '')
|
||||
url.searchParams.append('auth', apiKey)
|
||||
get(url, callback)
|
||||
}
|
||||
|
||||
function disable(duration: number, callback: (status: any) => void) {
|
||||
let url = new URL(`${pihole}/admin/api.php`)
|
||||
url.searchParams.append('auth', apiKey)
|
||||
url.searchParams.append('disable', duration.toString())
|
||||
get(url, (data) => {
|
||||
url = new URL(`${pihole}/custom_disable_timer`)
|
||||
get(url, (timestamp) => {
|
||||
let dataObj = JSON.parse(data)
|
||||
dataObj["until"] = Number.parseInt(timestamp)
|
||||
callback(JSON.stringify(dataObj))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
app.use(express.static('./public'))
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
console.info(`Pi-helper listening on port ${port}`)
|
||||
})
|
||||
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
wss.handleUpgrade(req, socket, head, socket => {
|
||||
wss.emit('connection', socket, req)
|
||||
})
|
||||
})
|
21
server/package.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"build": "tsc --project ./",
|
||||
"prestart": "npm run build",
|
||||
"start": "cd ../dist/; node index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/ws": "^8.2.2",
|
||||
"express": "^4.17.2",
|
||||
"typescript": "^4.5.5",
|
||||
"ws": "^8.4.2"
|
||||
}
|
||||
}
|
101
server/tsconfig.json
Normal file
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files */
|
||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "../dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
||||
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|