update api, operate doors
This commit is contained in:
parent
d1106fd3ed
commit
d19a26e128
4 changed files with 154 additions and 11 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
.envrc
|
.envrc
|
||||||
.direnv/
|
.direnv/
|
||||||
|
.env
|
||||||
|
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
api/dist
|
api/dist
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,23 @@ export interface paths {
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/auth/logout": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Logout */
|
||||||
|
get: operations["logout_auth_logout_get"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/locks/": {
|
"/api/locks/": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|
@ -72,6 +89,23 @@ export interface paths {
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/locks/{lock_id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
/** Operate Lock */
|
||||||
|
patch: operations["operate_lock_api_locks__lock_id__patch"];
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
|
|
@ -101,13 +135,23 @@ export interface components {
|
||||||
* @description Statically known HTTP problem types using the [type URI scheme](https://datatracker.ietf.org/doc/rfc4151/)
|
* @description Statically known HTTP problem types using the [type URI scheme](https://datatracker.ietf.org/doc/rfc4151/)
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
HttpProblemType: "type:noc@hamburg.ccc.de,2026:UNAUTHORIZED" | "type:noc@hamburg.ccc.de,2026:DOOR_NOT_FOUND";
|
HttpProblemType: "type:noc@hamburg.ccc.de,2026:UNAUTHORIZED" | "type:noc@hamburg.ccc.de,2026:LOCK_NOT_FOUND";
|
||||||
/** Lock */
|
/** Lock */
|
||||||
Lock: {
|
Lock: {
|
||||||
/** Name */
|
/** Name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Id */
|
||||||
|
id: string;
|
||||||
status: components["schemas"]["LockStatus"];
|
status: components["schemas"]["LockStatus"];
|
||||||
};
|
};
|
||||||
|
/** LockOperation */
|
||||||
|
LockOperation: {
|
||||||
|
/**
|
||||||
|
* Desired State
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
desired_state: "open" | "closed";
|
||||||
|
};
|
||||||
/** LockStatus */
|
/** LockStatus */
|
||||||
LockStatus: {
|
LockStatus: {
|
||||||
/** Is Unreachable */
|
/** Is Unreachable */
|
||||||
|
|
@ -241,6 +285,24 @@ export interface operations {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
logout_auth_logout_get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
302: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
list_locks_api_locks__get: {
|
list_locks_api_locks__get: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|
@ -270,4 +332,57 @@ export interface operations {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
operate_lock_api_locks__lock_id__patch: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
lock_id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["LockOperation"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HttpProblemDetail"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Not Found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HttpProblemDetail"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ async function checkUser() {
|
||||||
const {data: userInfo} = await getUserInfo({})
|
const {data: userInfo} = await getUserInfo({})
|
||||||
|
|
||||||
apiError.current = null
|
apiError.current = null
|
||||||
auth.authenticated = userInfo.is_logged_in
|
auth.authenticated = true
|
||||||
auth.authorized = userInfo.is_authorized
|
auth.authorized = userInfo.is_authorized
|
||||||
auth.until = userInfo.guaranteed_session_until ? new Date(userInfo.guaranteed_session_until) : null
|
auth.until = userInfo.guaranteed_session_until ? new Date(userInfo.guaranteed_session_until) : null
|
||||||
auth.username = userInfo.username ?? ""
|
auth.username = userInfo.username ?? ""
|
||||||
|
|
@ -98,7 +98,9 @@ async function checkUser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchDoors() {
|
async function fetchDoors() {
|
||||||
loading.doors = true
|
if (doors.length === 0) {
|
||||||
|
loading.doors = true
|
||||||
|
}
|
||||||
refresh()
|
refresh()
|
||||||
const getDoors = fetcher.path("/api/locks/").method("get").create()
|
const getDoors = fetcher.path("/api/locks/").method("get").create()
|
||||||
|
|
||||||
|
|
@ -123,7 +125,7 @@ async function fetchDoors() {
|
||||||
}
|
}
|
||||||
|
|
||||||
doors.push({
|
doors.push({
|
||||||
id: door.name, // TODO: replace by actual ID
|
id: door.id,
|
||||||
label: door.name,
|
label: door.name,
|
||||||
state: state,
|
state: state,
|
||||||
batteryLow: door.status.is_low_battery,
|
batteryLow: door.status.is_low_battery,
|
||||||
|
|
@ -157,9 +159,25 @@ async function fetchDoors() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDoorInfo(doorElement: HTMLDivElement, door: DoorType) {
|
async function doorAction(action: "unlock" | "lock", doorId: string) {
|
||||||
|
const operateDoors = fetcher.path("/api/locks/{lock_id}").method("patch").create()
|
||||||
|
|
||||||
|
const stateMap: Record<string, "open" | "closed"> = {
|
||||||
|
unlock: "open",
|
||||||
|
lock: "closed",
|
||||||
|
}
|
||||||
|
|
||||||
|
operateDoors({
|
||||||
|
lock_id: doorId,
|
||||||
|
desired_state: stateMap[action],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDoorInfo(doorElement: HTMLDivElement, door: DoorType, initial: boolean = false) {
|
||||||
const labelElement: HTMLDivElement = doorElement.querySelector("[data-label]")!
|
const labelElement: HTMLDivElement = doorElement.querySelector("[data-label]")!
|
||||||
const buttonElements: Array<HTMLButtonElement> = Array.from(doorElement.querySelectorAll("button"))
|
const lockButton: HTMLButtonElement = doorElement.querySelector("[data-button='lock']")!
|
||||||
|
const unlockButton: HTMLButtonElement = doorElement.querySelector("[data-button='unlock']")!
|
||||||
|
const buttonElements: Array<HTMLButtonElement> = [lockButton, unlockButton]
|
||||||
|
|
||||||
doorElement.dataset.id = door.id
|
doorElement.dataset.id = door.id
|
||||||
doorElement.dataset.state = door.state
|
doorElement.dataset.state = door.state
|
||||||
|
|
@ -190,6 +208,16 @@ function setDoorInfo(doorElement: HTMLDivElement, door: DoorType) {
|
||||||
|
|
||||||
labelElement.innerHTML = door.label
|
labelElement.innerHTML = door.label
|
||||||
|
|
||||||
|
if (initial) {
|
||||||
|
lockButton.addEventListener("click", () => {
|
||||||
|
doorAction("lock", door.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
unlockButton.addEventListener("click", () => {
|
||||||
|
doorAction("unlock", door.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
buttonElements.forEach(button => {
|
buttonElements.forEach(button => {
|
||||||
button.disabled = ["unlocking", "locking"].includes(door.state) || apiError.current !== null
|
button.disabled = ["unlocking", "locking"].includes(door.state) || apiError.current !== null
|
||||||
})
|
})
|
||||||
|
|
@ -205,7 +233,7 @@ function refresh() {
|
||||||
|
|
||||||
const targetElement = document.importNode(template.content, true)
|
const targetElement = document.importNode(template.content, true)
|
||||||
doorElement = targetElement.querySelector("[data-state]")!
|
doorElement = targetElement.querySelector("[data-state]")!
|
||||||
setDoorInfo(doorElement, door)
|
setDoorInfo(doorElement, door, true)
|
||||||
list.appendChild(targetElement)
|
list.appendChild(targetElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,9 +261,8 @@ function refresh() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
loadAuthFromLocalStorage()
|
loadAuthFromLocalStorage()
|
||||||
fetchDoors()
|
setInterval(fetchDoors, 250) // TODO: replace with SSE
|
||||||
checkUser()
|
checkUser()
|
||||||
|
|
||||||
document.addEventListener("loadeddata", () => {
|
document.addEventListener("loadeddata", () => {
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,10 @@ const t = useTranslations(lang)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden group-data-active:grid grid-cols-2 w-full items-center gap-2 mt-4">
|
<div class="hidden group-data-active:grid grid-cols-2 w-full items-center gap-2 mt-4">
|
||||||
<button class="btn btn-outline btn-error">
|
<button class="btn btn-outline btn-error" data-button="lock">
|
||||||
{t("button.close")}
|
{t("button.close")}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline btn-success">
|
<button class="btn btn-outline btn-success" data-button="unlock">
|
||||||
{t("button.open")}
|
{t("button.open")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue