Skip to content

Commit 7d73d8f

Browse files
authored
Version 1.1.0 (#5)
* feat: inbound webhooks * feat: store some form values in local storage * chore: update deps * feat: add trigger system * feat: add lockdown triggers * feat more triggers and outbound webhooks * fix: lint issues * feat: docs link in page * chore: update deps * feat: docs links on webhooks * feat: add log viewer
1 parent 52d828a commit 7d73d8f

36 files changed

+1646
-489
lines changed

app/lib/constants.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
export const VERSION = '1.0.2'
1+
export const VERSION = '1.1.0'
22

33
export const RequiredVersions = {
44
controller: VERSION,
55
tts: '1.0.1',
66
piper: '1.2.0',
77
sounder: '1.1.1'
88
}
9+
10+
export const DOCS_URL = `https://openschoolbell.co.uk`
11+
12+
export const EVENT_TYPES = [
13+
'action',
14+
'newAction',
15+
'deleteAction',
16+
'ignore',
17+
'lockdownEnd',
18+
'lockdownStart',
19+
'login'
20+
] as const

app/lib/hooks/use-local-storage.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {useState, useEffect} from 'react'
2+
3+
export const useLocalStorage = <ValueType extends string>(
4+
key: string,
5+
initial: ValueType
6+
): [ValueType, (newValue: ValueType) => void] => {
7+
const set = (newValue: ValueType) => {
8+
localStorage.setItem(key, newValue)
9+
}
10+
11+
if (typeof window === 'undefined') {
12+
return [initial, set]
13+
}
14+
15+
const value = localStorage.getItem(key) as ValueType | null
16+
17+
return [value ? value : initial, set]
18+
}
19+
20+
export const useStatefulLocalStorage = <ValueType extends string>(
21+
key: string,
22+
initial: ValueType
23+
) => {
24+
const [lsValue, setLS] = useLocalStorage(key, initial)
25+
26+
const [value, set] = useState(lsValue)
27+
28+
useEffect(() => {
29+
setLS(value)
30+
}, [value, setLS])
31+
32+
return [value, set] as const
33+
}

app/lib/lockdown.server.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {asyncForEach} from '@arcath/utils'
33
import {getSetting, setSetting} from './settings.server'
44
import {getPrisma} from './prisma.server'
55
import {addJob} from './queues.server'
6+
import {trigger} from './trigger'
67

78
export const startLockdown = async () => {
89
await setSetting('lockdownMode', '1')
@@ -11,6 +12,8 @@ export const startLockdown = async () => {
1112

1213
const sounders = await prisma.sounder.findMany()
1314

15+
await trigger('🔐 Lockdown Start', 'lockdownStart')
16+
1417
return asyncForEach(sounders, async ({ip, key}) => {
1518
await addJob('lockdown', {ip, key})
1619
})
@@ -23,6 +26,8 @@ export const endLockdown = async () => {
2326

2427
const sounders = await prisma.sounder.findMany()
2528

29+
await trigger('🔐 Lockdown End', 'lockdownEnd')
30+
2631
return asyncForEach(sounders, async ({ip, key}) => {
2732
await addJob('lockdown', {ip, key})
2833
})

app/lib/queues.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type Jobs = {
1414
times: number
1515
}
1616
lockdown: {ip: string; key: string}
17+
outboundWebhook: {target: string; key: string}
1718
}
1819

1920
export type JobName = keyof Jobs

app/lib/trigger.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {getPrisma} from './prisma.server'
2+
import {addJob} from './queues.server'
3+
4+
import {type EVENT_TYPES} from './constants'
5+
6+
type EventType = (typeof EVENT_TYPES)[number]
7+
8+
export const trigger = async (logMessage: string, event: EventType) => {
9+
const prisma = getPrisma()
10+
11+
await prisma.log.create({data: {message: logMessage}})
12+
13+
const outboundWebhooks = await prisma.outboundWebhook.findMany({
14+
where: {event}
15+
})
16+
17+
outboundWebhooks.forEach(({target, key}) => {
18+
void addJob('outboundWebhook', {target, key})
19+
})
20+
}

app/lib/ui.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {NavLink} from '@remix-run/react'
22

3+
import {docsLink} from './utils'
4+
35
export const SidebarLink: React.FC<{
46
to: string
57
children: React.ReactNode
@@ -24,12 +26,22 @@ export const Page: React.FC<{
2426
children: React.ReactNode
2527
title: string
2628
wide?: boolean
27-
}> = ({title, wide, children}) => {
29+
helpLink?: string
30+
}> = ({title, wide, children, helpLink}) => {
2831
return (
2932
<div className={`grid ${wide ? 'grid-cols-wide' : 'grid-cols-narrow'}`}>
3033
<div className="col-start-2">
3134
<h1 className="font-light mb-2 pb-2 border-b-stone-200 border-b">
3235
{title}
36+
{helpLink ? (
37+
<span className="float-right text-base pt-3">
38+
<a href={docsLink(helpLink)} target="_blank">
39+
📖 Docs
40+
</a>
41+
</span>
42+
) : (
43+
''
44+
)}
3345
</h1>
3446
{children}
3547
</div>

app/lib/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {DOCS_URL} from './constants'
2+
13
export const makeKey = () => {
24
let result = ''
35
const characters =
@@ -17,3 +19,7 @@ export const INPUT_CLASSES =
1719
export const pageTitle = (...parts: string[]) => {
1820
return ['Open School Bell', ...parts].join(' / ')
1921
}
22+
23+
export const docsLink = (path: string) => {
24+
return `${DOCS_URL}${path}`
25+
}

app/root.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import LogoutIcon from '@heroicons/react/24/outline/ArrowRightStartOnRectangleIc
1414
import ComputerIcon from '@heroicons/react/24/outline/ComputerDesktopIcon'
1515
import LockClosedIcon from '@heroicons/react/24/outline/LockClosedIcon'
1616
import MusicIcon from '@heroicons/react/24/outline/MusicalNoteIcon'
17+
import CodeIcon from '@heroicons/react/24/outline/CodeBracketIcon'
18+
import LogIcon from '@heroicons/react/24/outline/ClipboardDocumentCheckIcon'
1719

1820
import './tailwind.css'
1921

@@ -76,6 +78,9 @@ const App = () => {
7678
<SidebarLink to="/actions">
7779
<ArrowIcon className="w-6 mr-2" /> <span>Actions</span>
7880
</SidebarLink>
81+
<SidebarLink to="/webhooks">
82+
<CodeIcon className="w-6 mr-2" /> <span>Webhooks</span>
83+
</SidebarLink>
7984
<SidebarLink to="/zones">
8085
<Square3StackIcon className="w-6 mr-2" /> <span>Zones</span>
8186
</SidebarLink>
@@ -89,6 +94,9 @@ const App = () => {
8994
<SidebarLink to="/about">
9095
<InfoIcon className="w-6 mr-2" /> <span>About</span>
9196
</SidebarLink>
97+
<SidebarLink to="/log">
98+
<LogIcon className="w-6 mr-2" /> <span>Log</span>
99+
</SidebarLink>
92100
<SidebarLink to="/backup">
93101
<BoxIcon className="w-6 mr-2" /> <span>Backup</span>
94102
</SidebarLink>

app/routes/about.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import {
44
redirect
55
} from '@remix-run/node'
66
import {useLoaderData} from '@remix-run/react'
7-
import {asyncForEach} from '@arcath/utils'
7+
import {asyncForEach, nl2br} from '@arcath/utils'
88
import semver from 'semver'
99
import fs from 'fs'
1010
import path from 'path'
11-
import {nl2br} from '@arcath/utils'
1211

1312
import {pageTitle} from '~/lib/utils'
1413
import {Page} from '~/lib/ui'

app/routes/actions.$action.delete.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import {type ActionFunctionArgs, redirect} from '@remix-run/node'
22

33
import {getPrisma} from '~/lib/prisma.server'
4+
import {trigger} from '~/lib/trigger'
45

56
export const action = async ({params}: ActionFunctionArgs) => {
67
const prisma = getPrisma()
78

9+
const action = await prisma.action.findFirstOrThrow({
10+
where: {id: params.action}
11+
})
12+
13+
await trigger(`Deleted action: ${action.name}`, 'deleteAction')
14+
815
await prisma.action.delete({where: {id: params.action}})
916

1017
return redirect('/actions')

0 commit comments

Comments
 (0)