fetch door data from api; improved error handling
This commit is contained in:
parent
7935c54a60
commit
5fed0859af
3 changed files with 243 additions and 170 deletions
|
|
@ -24,6 +24,11 @@ export const ui = {
|
||||||
"button.open": "Open",
|
"button.open": "Open",
|
||||||
"button.close": "Close",
|
"button.close": "Close",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
|
"loggedOut.title": "Signed out",
|
||||||
|
"loggedOut.description": `Your Session is expired and you were logged out.
|
||||||
|
Log in again: <a class='underline' href='/auth/login'>Login</a>`,
|
||||||
|
"serverError.title": "Server Error",
|
||||||
|
"serverError.description": `Please try again later.`,
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
"unauthorized.title": "Nicht berechtigt",
|
"unauthorized.title": "Nicht berechtigt",
|
||||||
|
|
@ -43,5 +48,10 @@ export const ui = {
|
||||||
"button.open": "Öffnen",
|
"button.open": "Öffnen",
|
||||||
"button.close": "Schließen",
|
"button.close": "Schließen",
|
||||||
"login": "Anmelden",
|
"login": "Anmelden",
|
||||||
|
"loggedOut.title": "Abgemeldet",
|
||||||
|
"loggedOut.description": `Deine Sitzung ist abgelaufen und du wurdest abgemeldet.
|
||||||
|
Melde dich hier erneut an: <a class='underline' href='/auth/login'>Anmelden</a>`,
|
||||||
|
"serverError.title": "Serverfehler",
|
||||||
|
"serverError.description": `Bitte versuche es später erneut.`,
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|
@ -15,6 +15,7 @@ const t = useTranslations(lang)
|
||||||
<link rel="icon" href="/favicon.ico"/>
|
<link rel="icon" href="/favicon.ico"/>
|
||||||
<meta name="generator" content={Astro.generator}/>
|
<meta name="generator" content={Astro.generator}/>
|
||||||
<title>dooris</title>
|
<title>dooris</title>
|
||||||
|
<slot name="head"/>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen">
|
<body class="min-h-screen">
|
||||||
<header class="p-2">
|
<header class="p-2">
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,224 @@ const lang = getLangFromUrl(Astro.url)
|
||||||
const t = useTranslations(lang)
|
const t = useTranslations(lang)
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout username="">
|
<Layout>
|
||||||
|
<Fragment slot="head">
|
||||||
|
<script>
|
||||||
|
import {Fetcher} from "openapi-typescript-fetch"
|
||||||
|
import {type paths} from "../../api/schema"
|
||||||
|
|
||||||
|
const fetcher = Fetcher.for<paths>()
|
||||||
|
|
||||||
|
const list: HTMLDivElement = document.querySelector("#list")!
|
||||||
|
const template: HTMLTemplateElement = document.querySelector("#template-door")!
|
||||||
|
|
||||||
|
type DoorType = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
state: "unlocked" | "locked" | "unknown" | "unlocking" | "locking";
|
||||||
|
batteryLow: boolean;
|
||||||
|
unreachable: boolean;
|
||||||
|
jammed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthType = {
|
||||||
|
username: string;
|
||||||
|
authorized: boolean;
|
||||||
|
authenticated: boolean;
|
||||||
|
until: Date | null;
|
||||||
|
recentLogout: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth: AuthType = {
|
||||||
|
username: "user",
|
||||||
|
authorized: true,
|
||||||
|
authenticated: false,
|
||||||
|
until: null,
|
||||||
|
recentLogout: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiError: {
|
||||||
|
current: "serverError" | null;
|
||||||
|
} = {
|
||||||
|
current: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const doors: Array<DoorType> = []
|
||||||
|
|
||||||
|
function loadAuthFromLocalStorage() {
|
||||||
|
const localAuth = JSON.parse(localStorage.getItem("auth") ?? "")
|
||||||
|
auth.recentLogout = localAuth.recentLogout // set recentLogout true, if user was logged in before
|
||||||
|
auth.authenticated = localAuth.authenticated
|
||||||
|
auth.authorized = localAuth.authorized
|
||||||
|
auth.until = localAuth.until ? new Date(localAuth.until) : null
|
||||||
|
auth.username = localAuth.username
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkUser() {
|
||||||
|
const getUserInfo = fetcher.path("/api/user-info/").method("get").create()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {status, data: userInfo} = await getUserInfo({})
|
||||||
|
|
||||||
|
apiError.current = null
|
||||||
|
auth.authenticated = userInfo.is_logged_in
|
||||||
|
auth.authorized = true
|
||||||
|
auth.until = userInfo.guaranteed_session_until ? new Date(userInfo.guaranteed_session_until) : null
|
||||||
|
auth.username = userInfo.user_info?.username ?? ""
|
||||||
|
auth.recentLogout = false
|
||||||
|
} catch (e) {
|
||||||
|
// check which operation threw the exception
|
||||||
|
if (e instanceof getUserInfo.Error) {
|
||||||
|
const error = e.getActualType()
|
||||||
|
|
||||||
|
if (error.status === 401) {
|
||||||
|
auth.recentLogout = auth.authenticated // set recentLogout true, if user was logged in before
|
||||||
|
auth.authenticated = false
|
||||||
|
auth.authorized = false
|
||||||
|
auth.until = null
|
||||||
|
auth.username = ""
|
||||||
|
} else if (error.status >= 500 && error.status < 600) {
|
||||||
|
apiError.current = "serverError"
|
||||||
|
} else {
|
||||||
|
console.error("unknown error:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
localStorage.setItem("auth", JSON.stringify(auth))
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDoors() {
|
||||||
|
const getDoors = fetcher.path("/api/locks/").method("get").create()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {status, data: doorInfo} = await getDoors({})
|
||||||
|
|
||||||
|
apiError.current = null
|
||||||
|
while (doors.length) {
|
||||||
|
doors.pop()
|
||||||
|
}
|
||||||
|
doorInfo.forEach(door => {
|
||||||
|
let state: DoorType["state"] = "unknown"
|
||||||
|
|
||||||
|
switch (door.status.activity_state) {
|
||||||
|
case "locking":
|
||||||
|
case "unlocking":
|
||||||
|
case "unknown":
|
||||||
|
state = door.status.activity_state
|
||||||
|
break
|
||||||
|
case "stable":
|
||||||
|
state = door.status.lock_state
|
||||||
|
}
|
||||||
|
|
||||||
|
doors.push({
|
||||||
|
id: door.name, // TODO: replace by actual ID
|
||||||
|
label: door.name,
|
||||||
|
state: state,
|
||||||
|
batteryLow: door.status.is_low_battery,
|
||||||
|
unreachable: door.status.is_unreachable,
|
||||||
|
jammed: door.status.is_error_jammed,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// check which operation threw the exception
|
||||||
|
if (e instanceof getDoors.Error) {
|
||||||
|
const error = e.getActualType()
|
||||||
|
|
||||||
|
if (error.status === 401) {
|
||||||
|
console.log("unauthorized")
|
||||||
|
} else if (error.status >= 500 && error.status < 600) {
|
||||||
|
apiError.current = "serverError"
|
||||||
|
} else {
|
||||||
|
console.error("unknown error:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDoorInfo(doorElement: HTMLDivElement, door: DoorType) {
|
||||||
|
const labelElement: HTMLDivElement = doorElement.querySelector("[data-label]")!
|
||||||
|
const buttonElements: Array<HTMLButtonElement> = Array.from(doorElement.querySelectorAll("button"))
|
||||||
|
|
||||||
|
doorElement.dataset.id = door.id
|
||||||
|
doorElement.dataset.state = door.state
|
||||||
|
|
||||||
|
if (auth.authenticated && auth.authorized) {
|
||||||
|
doorElement.dataset.active = ""
|
||||||
|
} else {
|
||||||
|
delete doorElement.dataset.active
|
||||||
|
}
|
||||||
|
|
||||||
|
if (door.batteryLow) {
|
||||||
|
doorElement.dataset.battery = ""
|
||||||
|
} else {
|
||||||
|
delete doorElement.dataset.battery
|
||||||
|
}
|
||||||
|
|
||||||
|
if (door.unreachable) {
|
||||||
|
doorElement.dataset.unreachable = ""
|
||||||
|
} else {
|
||||||
|
delete doorElement.dataset.unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
if (door.jammed) {
|
||||||
|
doorElement.dataset.jammed = ""
|
||||||
|
} else {
|
||||||
|
delete doorElement.dataset.jammed
|
||||||
|
}
|
||||||
|
|
||||||
|
labelElement.innerHTML = door.label
|
||||||
|
|
||||||
|
buttonElements.forEach(button => {
|
||||||
|
button.disabled = ["unlocking", "locking"].includes(door.state) || apiError.current !== null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
for (const door of doors) {
|
||||||
|
let doorElement: HTMLDivElement | null = list.querySelector(`[data-id='${door.id}']`)
|
||||||
|
if (doorElement) {
|
||||||
|
setDoorInfo(doorElement, door)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetElement = document.importNode(template.content, true)
|
||||||
|
doorElement = targetElement.querySelector("[data-state]")!
|
||||||
|
setDoorInfo(doorElement, door)
|
||||||
|
list.appendChild(targetElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
const alertUnauthenticated: HTMLDivElement = document.querySelector("#alert-unauthenticated")!
|
||||||
|
const alertUnauthorized: HTMLDivElement = document.querySelector("#alert-unauthorized")!
|
||||||
|
const alertLoggedout: HTMLDivElement = document.querySelector("#alert-loggedout")!
|
||||||
|
const alertServerError: HTMLDivElement = document.querySelector("#alert-serverError")!
|
||||||
|
const alertDivider: HTMLDivElement = document.querySelector("#alert-divider")!
|
||||||
|
const badgeUsername: HTMLDivElement = document.querySelector("#badge-username")!
|
||||||
|
const buttonLogin: HTMLDivElement = document.querySelector("#button-login")!
|
||||||
|
const usernameElement: HTMLSpanElement = badgeUsername.querySelector("#username")!
|
||||||
|
|
||||||
|
alertUnauthenticated.classList.toggle("hidden", auth.authenticated)
|
||||||
|
alertUnauthorized.classList.toggle("hidden", !auth.authenticated || auth.authorized)
|
||||||
|
alertLoggedout.classList.toggle("hidden", !auth.recentLogout)
|
||||||
|
alertServerError.classList.toggle("hidden", apiError.current !== "serverError")
|
||||||
|
alertDivider.classList.toggle("hidden", auth.authenticated && auth.authorized && !auth.recentLogout && apiError.current === null)
|
||||||
|
badgeUsername.classList.toggle("hidden", !auth.authenticated)
|
||||||
|
usernameElement.innerHTML = auth.username
|
||||||
|
buttonLogin.classList.toggle("hidden", auth.authenticated)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAuthFromLocalStorage()
|
||||||
|
fetchDoors()
|
||||||
|
checkUser()
|
||||||
|
|
||||||
|
document.addEventListener("loadeddata", () => {
|
||||||
|
refresh()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</Fragment>
|
||||||
<div class="grid place-items-center grid-cols-1 gap-4 max-w-xl mx-auto" id="list">
|
<div class="grid place-items-center grid-cols-1 gap-4 max-w-xl mx-auto" id="list">
|
||||||
<Alert
|
<Alert
|
||||||
classList="hidden"
|
classList="hidden"
|
||||||
|
|
@ -42,177 +259,22 @@ const t = useTranslations(lang)
|
||||||
<Lock class="size-7"/>
|
<Lock class="size-7"/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
<Alert
|
||||||
|
classList="hidden"
|
||||||
|
id="alert-loggedout"
|
||||||
|
color="warning"
|
||||||
|
title={t("loggedOut.title")}
|
||||||
|
description={t("loggedOut.description")}
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
classList="hidden"
|
||||||
|
id="alert-serverError"
|
||||||
|
color="error"
|
||||||
|
title={t("serverError.title")}
|
||||||
|
description={t("serverError.description")}
|
||||||
|
/>
|
||||||
<div class="divider w-full my-0" id="alert-divider"></div>
|
<div class="divider w-full my-0" id="alert-divider"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DoorTemplate state="unknown"/>
|
<DoorTemplate state="unknown"/>
|
||||||
|
|
||||||
<script>
|
|
||||||
import {Fetcher} from "openapi-typescript-fetch"
|
|
||||||
import {type paths} from "../../api/schema"
|
|
||||||
|
|
||||||
const fetcher = Fetcher.for<paths>()
|
|
||||||
|
|
||||||
const list: HTMLDivElement = document.querySelector("#list")!
|
|
||||||
const template: HTMLTemplateElement = document.querySelector("#template-door")!
|
|
||||||
|
|
||||||
type DoorType = {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
state: "unlocked" | "locked" | "unknown" | "unlocking" | "locking";
|
|
||||||
batteryLow: boolean;
|
|
||||||
unreachable: boolean;
|
|
||||||
jammed: boolean;
|
|
||||||
}
|
|
||||||
type AuthType = {
|
|
||||||
username: string;
|
|
||||||
authorized: boolean;
|
|
||||||
authenticated: boolean;
|
|
||||||
until: Date | null;
|
|
||||||
}
|
|
||||||
const auth: AuthType = {
|
|
||||||
username: "user",
|
|
||||||
authorized: true,
|
|
||||||
authenticated: false,
|
|
||||||
until: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
const doors: Array<DoorType> = [
|
|
||||||
{
|
|
||||||
id: "abcdef",
|
|
||||||
label: "Hauptraum",
|
|
||||||
state: "unlocked",
|
|
||||||
batteryLow: false,
|
|
||||||
unreachable: false,
|
|
||||||
jammed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "12345",
|
|
||||||
label: "Werkstatt",
|
|
||||||
state: "locked",
|
|
||||||
batteryLow: true,
|
|
||||||
unreachable: false,
|
|
||||||
jammed: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
async function checkUser() {
|
|
||||||
const getUserInfo = fetcher.path("/api/user-info/").method("get").create()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const {status, data: userInfo} = await getUserInfo({})
|
|
||||||
|
|
||||||
auth.authenticated = userInfo.is_logged_in
|
|
||||||
auth.authorized = true
|
|
||||||
auth.until = userInfo.guaranteed_session_until ? new Date(userInfo.guaranteed_session_until) : null
|
|
||||||
auth.username = userInfo.user_info?.username ?? ''
|
|
||||||
} catch (e) {
|
|
||||||
// check which operation threw the exception
|
|
||||||
if (e instanceof getUserInfo.Error) {
|
|
||||||
const error = e.getActualType()
|
|
||||||
|
|
||||||
if (error.status === 401) {
|
|
||||||
auth.authenticated = false
|
|
||||||
auth.authorized = false
|
|
||||||
auth.until = null
|
|
||||||
auth.username = ''
|
|
||||||
} else {
|
|
||||||
console.log('unknown error')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDoorInfo(doorElement: HTMLDivElement, door: DoorType) {
|
|
||||||
const labelElement: HTMLDivElement = doorElement.querySelector("[data-label]")!
|
|
||||||
const buttonElements: Array<HTMLButtonElement> = Array.from(doorElement.querySelectorAll("button"))
|
|
||||||
|
|
||||||
doorElement.dataset.id = door.id
|
|
||||||
doorElement.dataset.state = door.state
|
|
||||||
|
|
||||||
if (auth.authenticated && auth.authorized) {
|
|
||||||
doorElement.dataset.active = ""
|
|
||||||
} else {
|
|
||||||
delete doorElement.dataset.active
|
|
||||||
}
|
|
||||||
|
|
||||||
if (door.batteryLow) {
|
|
||||||
doorElement.dataset.battery = ""
|
|
||||||
} else {
|
|
||||||
delete doorElement.dataset.battery
|
|
||||||
}
|
|
||||||
|
|
||||||
if (door.unreachable) {
|
|
||||||
doorElement.dataset.unreachable = ""
|
|
||||||
} else {
|
|
||||||
delete doorElement.dataset.unreachable
|
|
||||||
}
|
|
||||||
|
|
||||||
if (door.jammed) {
|
|
||||||
doorElement.dataset.jammed = ""
|
|
||||||
} else {
|
|
||||||
delete doorElement.dataset.jammed
|
|
||||||
}
|
|
||||||
|
|
||||||
labelElement.innerHTML = door.label
|
|
||||||
|
|
||||||
buttonElements.forEach(button => {
|
|
||||||
button.disabled = ["unlocking", "locking"].includes(door.state)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function refresh() {
|
|
||||||
for (const door of doors) {
|
|
||||||
let doorElement: HTMLDivElement | null = list.querySelector(`[data-id='${door.id}']`)
|
|
||||||
if (doorElement) {
|
|
||||||
setDoorInfo(doorElement, door)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetElement = document.importNode(template.content, true)
|
|
||||||
doorElement = targetElement.querySelector("[data-state]")!
|
|
||||||
setDoorInfo(doorElement, door)
|
|
||||||
list.appendChild(targetElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
const alertUnauthenticated: HTMLDivElement = document.querySelector("#alert-unauthenticated")!
|
|
||||||
const alertUnauthorized: HTMLDivElement = document.querySelector("#alert-unauthorized")!
|
|
||||||
const alertDivider: HTMLDivElement = document.querySelector("#alert-divider")!
|
|
||||||
const badgeUsername: HTMLDivElement = document.querySelector("#badge-username")!
|
|
||||||
const buttonLogin: HTMLDivElement = document.querySelector("#button-login")!
|
|
||||||
const usernameElement: HTMLSpanElement = badgeUsername.querySelector("#username")!
|
|
||||||
|
|
||||||
alertUnauthenticated.classList.toggle("hidden", auth.authenticated)
|
|
||||||
alertUnauthorized.classList.toggle("hidden", !auth.authenticated || auth.authorized)
|
|
||||||
alertDivider.classList.toggle("hidden", auth.authenticated && auth.authorized)
|
|
||||||
badgeUsername.classList.toggle("hidden", !auth.authenticated)
|
|
||||||
usernameElement.innerHTML = auth.username
|
|
||||||
buttonLogin.classList.toggle("hidden", auth.authenticated)
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh()
|
|
||||||
checkUser()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
doors[0] = {
|
|
||||||
id: "abcdef",
|
|
||||||
label: "Hauptraum",
|
|
||||||
state: "unknown",
|
|
||||||
batteryLow: true,
|
|
||||||
unreachable: false,
|
|
||||||
jammed: true,
|
|
||||||
}
|
|
||||||
doors[1] = {
|
|
||||||
id: "12345",
|
|
||||||
label: "Werkstatt",
|
|
||||||
state: "unlocking",
|
|
||||||
batteryLow: false,
|
|
||||||
unreachable: true,
|
|
||||||
jammed: false,
|
|
||||||
}
|
|
||||||
refresh()
|
|
||||||
}, 2000)
|
|
||||||
</script>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue