Files
kbcrm/Client/Components/auth/login.ts
T
gg 9ed5e88e01 Menü Projekt-Berechtigungen, Übersichtssichten und AppAdmin-Erkennung
- Menü: Dropdown "Projekt-Berechtigungen" unter Optionen mit
  Unterpunkten "Projekt-Zugriffe" und "Nutzer-Zugriffe" (nur SysAdmin/AppAdmin)
- AppAdmin-Erkennung: Server liefert isAppAdmin-Flag im Login- und
  getusers-Response (applikationsspezifisch via has_permission)
- Login: CurrentUserIsAppAdmin in sessionStorage speichern/entfernen
- Projekt-Zugriffe: geschachtelter Treetable (Projekt → Nutzer) als
  reine Anzeige, Detail-Sicht aus Projekt-Maske mit Bearbeitung
- Nutzer-Zugriffe: neue Komponente user_projektliste.ts, geschachtelter
  Treetable (Nutzer → Projekte), reine Anzeige
- Server: neuer Endpunkt GET /xhr/projektuser/listall (nur AppAdmin/SysAdmin)
- Server: get_all_projekt_users() mit Batch-Query für Projekte und User
- Treetable-Widget: Platzhalter-Pfeil bei kinderlosen Nodes nur wenn
  Geschwister mit Kindern existieren (hasSiblingsWithChildren)
- Datei projekt_user.ts umbenannt in projekt_userliste.ts (konsistent
  mit user_projektliste.ts)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-07 16:04:06 +02:00

126 lines
5.8 KiB
TypeScript

import m from "mithril";
import {ModalView, iModalView} from "../widgets/Modal"
import {iUser} from "./models/users"
import {showServerError} from "./authmessages"
import { headerdata } from "../header";
export interface iLoginscreen {
setnextpage: string
}
export const loginscreen: m.ClosureComponent<iLoginscreen> = (vnode) => {
let form: HTMLFormElement = null;
function reset (e) {
form.reset()
}
function checklogin(e: Event) {
e.preventDefault()
let formdata: FormData = new FormData(e.target as HTMLFormElement)
let user: iUser
m.request ({
method: "POST", // Hier muss POST verwendet werden, weil GET keinen Body hat und Parameter nur im URL transportieren kann
url: "/auth/checklogin",
body: Object.fromEntries(formdata.entries())
})
.then((result) => {
user = result as iUser;
// global.appglobals.currentuser = user.username
// global.appglobals.currentuserid = user.userid
sessionStorage.setItem("CurrentUser", user.username);
headerdata.username = user.username
headerdata.userid = user.userid
// nur wenn es sich um einen SysAdmin handelt, wird der SessionStorage gesetzt. Will man später wissen,
// ob der aktuelle User ein SysAdmin ist, braucht man nur auf das Vorhandensein des Schlüssels abzufragen
// und nicht auch noch auf den Inhalt
if (user.isSysAdmin) {
sessionStorage.setItem("CurrentUserIsSysAdmin", "1");
}
// AppAdmin: der Server prüft applikationsspezifisch via
// has_permission(appid, [APP_ADMIN]) und liefert isAppAdmin
// im Login-Response. Der Client wertet nur das Flag aus.
if (user.isAppAdmin) {
sessionStorage.setItem("CurrentUserIsAppAdmin", "1");
}
})
.then(() => {m.route.set(vnode.attrs.setnextpage)})
.catch((error) => {
sessionStorage.removeItem("CurrentUser");
sessionStorage.removeItem("CurrentUserIsSysAdmin");
sessionStorage.removeItem("CurrentUserIsAppAdmin");
showServerError(error)
})
}
return {
oninit: (vnode) => {
m.request({
method: "GET",
url: "/auth/logout"
})
.then (() => {
sessionStorage.removeItem("CurrentUser");
sessionStorage.removeItem("CurrentUserIsSysAdmin");
sessionStorage.removeItem("CurrentUserIsAppAdmin");
// global.appglobals.currentuser = "nicht angemeldet"
// global.appglobals.currentuserid = ""
})
},
view: (vnode) => {
return m(ModalView, {title: "Login", width: '33%'} as iModalView,
m("form.w3-padding-16", {oncreate: (vnode) => {form = vnode.dom as HTMLFormElement}, onsubmit: checklogin},
m("label", "Benutzer-ID"),
m("input.w3-input.w3-border", {name: 'userid', type: 'text'}),
m("label", "Kennwort"),
m("input.w3-input.w3-border", {name: 'password', type: 'password'}),
m("div.w3-bar.w3-padding-16",
[
m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, type: "submit"}, "Login"),
m("button.w3-button.w3-theme.w3-right", {onclick: reset, type: "button"}, "Zurücksetzen"),
]
)
)
)
}
}
}
export const changepasswordscreen: m.ClosureComponent = () => {
let form: HTMLFormElement = null;
function reset (e) {
form.reset()
}
function changepassword(e) {
e.preventDefault()
let formdata: FormData = new FormData(e.target as HTMLFormElement)
m.request ({
method: "POST", // Hier muss POST verwendet werden, weil GET keinen Body hat und Parameter nur im URL transportieren kann
url: "/auth/changepassword",
body: Object.fromEntries(formdata.entries())
})
.then(() => {m.route.set("/login")})
.catch((e) => {showServerError(e)})
}
return {
view: (vnode) => {
return m(ModalView, {title: "Kennwort ändern", width: '33%'} as iModalView,
m("form.w3-padding-16", {oncreate: (vnode) => {form = vnode.dom as HTMLFormElement}, onsubmit: changepassword},
m("label", "Benutzer-ID"),
m("input.w3-input.w3-border", {name: 'Userid', type: 'text', value: headerdata.userid, readonly: true}),
m("label", "Altes Kennwort"),
m("input.w3-input.w3-border", {name: 'OldPassword', type: 'password'}),
m("label", "Neues Kennwort"),
m("input.w3-input.w3-border", {name: 'NewPassword1', type: 'password'}),
m("label", "Neues Kennwort"),
m("input.w3-input.w3-border", {name: 'NewPassword2', type: 'password'}),
m("div.w3-bar.w3-padding-16",
[
m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, type: "submit"}, "Kennwort ändern"),
m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, onclick: reset, type: "button"}, "Zurücksetzen"),
m("button.w3-button.w3-theme.w3-right", {onclick: () => {history.back()}, type: "button"}, "Abbrechen"),
]
)
)
)
}
}
}