Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export const RequiredVersions = {
tts: '2.0.0',
piper: '1.3.0',
sounder: '2.4.0',
button: '1.0.0'
button: '1.0.0',
controlPoint: '1.0.0'
}

export const DOCS_URL = `https://openschoolbell.co.uk`
Expand Down
2 changes: 2 additions & 0 deletions app/lib/settings.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type SettingKey =
| 'ttsSpeed'
| 'enrollUrl'
| 'controlPointKey'
| 'controlPointDefaultZone'
| 'ttsLastSeen'
| 'workerLastSeen'
| 'lockdownEntrySequence'
Expand All @@ -22,6 +23,7 @@ export const DEFAULT_SETTINGS: {[setting in SettingKey]: string} = {
ttsSpeed: '1',
enrollUrl: 'http://controller:3000',
controlPointKey: '',
controlPointDefaultZone: '',
ttsLastSeen: '"1970-01-01T23:00:00.000Z"',
workerLastSeen: '"1970-01-01T23:00:00.000Z"',
lockdownEntrySequence: '[]',
Expand Down
3 changes: 3 additions & 0 deletions app/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ export const en = {
'settings.controlPointKey.label': 'Control Point Key',
'settings.controlPointKey.helper':
'The key used by the Control Point App to communicate with the controller.',
'settings.controlPointDefaultZone.label': 'Control Point Zone',
'settings.controlPointDefaultZone.helper':
'Which zone should the control point user interface default to?',
'settings.password.label': 'Change password',
'settings.password.helper':
'Leave fields empty to keep the current password.',
Expand Down
47 changes: 45 additions & 2 deletions app/routes/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,25 @@ export const loader = async ({request}: LoaderFunctionArgs) => {
.catch(() => resolve('error'))
})

const controlPointLatest = await new Promise<string>(resolve => {
fetch(
'https://api.github.com/repos/Open-School-Bell/control-point/releases?per_page=1',
{
headers: {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
}
)
.then(response => {
response
.json()
.then(data => resolve(data[0].tag_name))
.catch(() => resolve('error'))
})
.catch(() => resolve('error'))
})

const prisma = getPrisma()
const redis = getRedis()

Expand Down Expand Up @@ -160,6 +179,8 @@ export const loader = async ({request}: LoaderFunctionArgs) => {
buttonVersions[id] = version ? version : '0.0.0'
})

const controlPointVersion = await redis.get('osb-control-point-version')

const license = (
await readFile(path.join(process.cwd(), 'LICENSE'))
).toString()
Expand All @@ -174,7 +195,9 @@ export const loader = async ({request}: LoaderFunctionArgs) => {
buttonLatest,
ttsLatest,
controllerLatest,
license
license,
controlPointVersion,
controlPointLatest
}
}

Expand All @@ -194,7 +217,9 @@ const About = () => {
buttonLatest,
ttsLatest,
controllerLatest,
license
license,
controlPointVersion,
controlPointLatest
} = useLoaderData<typeof loader>()
const {t} = useTranslation()

Expand Down Expand Up @@ -284,6 +309,24 @@ const About = () => {
</tr>
)
})}
{controlPointVersion ? (
<tr>
<td>Control Point</td>
<td className="text-center">{controlPointVersion}</td>
<td
className={`text-center ${semver.gt(controlPointLatest, controlPointVersion) ? 'bg-red-300' : ''}`}
>
{controlPointLatest.replace('v', '')}
</td>
<td
className={`text-center ${semver.gt(RequiredVersions.controlPoint, controlPointVersion) ? 'bg-red-300' : ''}`}
>
{RequiredVersions.controlPoint}
</td>
</tr>
) : (
''
)}
</tbody>
</table>

Expand Down
21 changes: 17 additions & 4 deletions app/routes/control-point.config.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import {type LoaderFunctionArgs} from '@remix-run/node'

import {getSetting} from '~/lib/settings.server'
import {getSettings} from '~/lib/settings.server'
import {getPrisma} from '~/lib/prisma.server'
import {getRedis} from '~/lib/redis.server.mjs'

export const loader = async ({request}: LoaderFunctionArgs) => {
const controlPointKey = await getSetting('controlPointKey')
const {controlPointKey, controlPointDefaultZone} = await getSettings([
'controlPointKey',
'controlPointDefaultZone'
])

const redis = getRedis()

if (controlPointKey === '') {
return Response.json({
Expand All @@ -14,13 +20,18 @@ export const loader = async ({request}: LoaderFunctionArgs) => {
}

const authHeader = request.headers.get('Auth')
const versionHeader = request.headers.get('Version')

if (!authHeader) {
return Response.json({result: 'error', error: 'No key provided.'})
}

if (authHeader !== controlPointKey) {
return Response.json({result: 'error', error: 'Invalid Ket provided.'})
return Response.json({result: 'error', error: 'Invalid Key provided.'})
}

if (versionHeader) {
void redis.set(`osb-control-point-version`, versionHeader)
}

const prisma = getPrisma()
Expand All @@ -30,6 +41,8 @@ export const loader = async ({request}: LoaderFunctionArgs) => {
return Response.json({
zones: zones.map(({id, name}) => {
return {id, name}
})
}),
defaultZone:
controlPointDefaultZone !== '' ? controlPointDefaultZone : zones[0].id
})
}
2 changes: 1 addition & 1 deletion app/routes/control-point.trigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const action = async ({request}: ActionFunctionArgs) => {
}

if (authHeader !== controlPointKey) {
return Response.json({result: 'error', error: 'Invalid Ket provided.'})
return Response.json({result: 'error', error: 'Invalid Key provided.'})
}

const data = (await request.json()) as {pin: string; zone: string}
Expand Down
48 changes: 40 additions & 8 deletions app/routes/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
type MetaFunction,
redirect
} from '@remix-run/node'
import {useLoaderData} from '@remix-run/react'
import {Form, useLoaderData} from '@remix-run/react'
import {invariant} from '@arcath/utils'

import {getSettings, setSetting} from '~/lib/settings.server'
Expand All @@ -14,6 +14,7 @@ import {Page, FormElement} from '~/lib/ui'
import {useTranslation} from '~/lib/i18n'
import {translate} from '~/lib/i18n.shared'
import {getRootI18n} from '~/lib/i18n.meta'
import {getPrisma} from '~/lib/prisma.server'

export const meta: MetaFunction = ({matches}) => {
const {messages} = getRootI18n(matches)
Expand All @@ -27,16 +28,27 @@ export const loader = async ({request}: LoaderFunctionArgs) => {
return redirect('/login')
}

const {ttsSpeed, enrollUrl, controlPointKey} = await getSettings([
'ttsSpeed',
'enrollUrl',
'controlPointKey'
])
const prisma = getPrisma()

const {ttsSpeed, enrollUrl, controlPointKey, controlPointDefaultZone} =
await getSettings([
'ttsSpeed',
'enrollUrl',
'controlPointKey',
'controlPointDefaultZone'
])

const zones = await prisma.zone.findMany({
select: {id: true, name: true},
orderBy: {name: 'asc'}
})

return {
ttsSpeed,
enrollUrl,
controlPointKey
controlPointKey,
controlPointDefaultZone,
zones
}
}

Expand All @@ -52,14 +64,19 @@ export const action = async ({request}: ActionFunctionArgs) => {
| undefined)
? (formData.get('controlPointKey') as string | undefined)
: ''
const controlPointDefaultZone = formData.get('controlPointDefaultZone') as
| string
| undefined

invariant(enrollUrl)
invariant(ttsSpeed)
invariant(controlPointKey)
invariant(controlPointDefaultZone)

await setSetting('enrollUrl', enrollUrl)
await setSetting('ttsSpeed', ttsSpeed)
await setSetting('controlPointKey', controlPointKey)
await setSetting('controlPointDefaultZone', controlPointDefaultZone)

if (password && checkPassword && password === checkPassword) {
await setSetting('password', password)
Expand All @@ -69,7 +86,8 @@ export const action = async ({request}: ActionFunctionArgs) => {
}

const Settings = () => {
const {ttsSpeed, enrollUrl, controlPointKey} = useLoaderData<typeof loader>()
const {ttsSpeed, enrollUrl, controlPointKey, controlPointDefaultZone, zones} =
useLoaderData<typeof loader>()
const {t} = useTranslation()

return (
Expand Down Expand Up @@ -108,6 +126,20 @@ const Settings = () => {
defaultValue={controlPointKey}
/>
</FormElement>
<FormElement
label={t('settings.controlPointDefaultZone.label')}
helperText={t('settings.controlPointDefaultZone.helper')}
>
<select
className={INPUT_CLASSES}
name="controlPointDefaultZone"
defaultValue={controlPointDefaultZone}
>
{zones.map(({id, name}) => {
return <option value={id}>{name}</option>
})}
</select>
</FormElement>
<FormElement
label={t('settings.password.label')}
helperText={t('settings.password.helper')}
Expand Down
Loading