- 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>
126 lines
5.8 KiB
TypeScript
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"),
|
|
]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|