616 lines
28 KiB
TypeScript
616 lines
28 KiB
TypeScript
import m from "mithril"
|
|
import { Treetable, iTreetable, iTreenode, Treenode, TopbarButton, iTopbarButton, changeTreenodes } from "../widgets/Treetable_V2"
|
|
import {ModalType, ModalView, iModalView} from "../widgets/Modal"
|
|
import { iApplication} from "./models/applications"
|
|
import { iRole } from "./models/roles"
|
|
import {gridMountpoints} from "../mainlayout"
|
|
import {WrongEditSelectionMessage, iWrongEditSelectionMessage, showServerError} from "./authmessages"
|
|
|
|
const MOVERIGHT = "Die markierten und nicht markierten Rollen werden den markierten Anwendungen zugeordnet"
|
|
|
|
const localDataModel = {
|
|
applist: [] as iApplication[],
|
|
rolelist: [] as iRole[],
|
|
appnodes: [] as iTreenode[],
|
|
rolenodes: [] as iTreenode[],
|
|
loadData: () => {
|
|
Promise.all([
|
|
m.request({
|
|
method: "GET",
|
|
url: "/auth/getapplications"
|
|
}).then((result) => localDataModel.applist = result as iApplication[]),
|
|
m.request({
|
|
method: "GET",
|
|
url: "/auth/getroles"
|
|
}).then((result) => {localDataModel.rolelist = result as iRole[]})
|
|
])
|
|
.then (() => {
|
|
localDataModel.appnodes = []
|
|
localDataModel.rolenodes = []
|
|
localDataModel.applist.map((app: iApplication) => {
|
|
let appnode: iTreenode = new Treenode(app, false, [])
|
|
app.roles.forEach((role: iRole) => {
|
|
let newChildnode = new Treenode(role)
|
|
newChildnode.isChecked = true
|
|
appnode.children.push(newChildnode)
|
|
})
|
|
appnode.children.sort(sortRoleNodes)
|
|
localDataModel.appnodes.push(appnode)
|
|
})
|
|
localDataModel.rolelist.map((role: iRole) => {
|
|
let rolenode: iTreenode = new Treenode(role, false, [])
|
|
localDataModel.rolenodes.push(rolenode)
|
|
})
|
|
localDataModel.rolenodes.sort(sortRoleNodes)
|
|
changeTreenodes(localDataModel.appnodes, true)
|
|
})
|
|
},
|
|
updateRole: (role: iRole) => {
|
|
m.request ({
|
|
method: "POST", // Hier muss POST verwendet werden, weil GET keinen Body hat und Parameter nur im URL transportieren kann
|
|
url: "/auth/updaterole",
|
|
body: role
|
|
})
|
|
.catch((e) => {showServerError(e)})
|
|
.finally(() => localDataModel.loadData())
|
|
},
|
|
deleteRoles: (roles: iRole[]) => {
|
|
let roleids: string[] = []
|
|
roles.forEach((role: iRole) => {
|
|
roleids.push(role.roleid)
|
|
})
|
|
m.request({
|
|
method: "POST",
|
|
url: "/auth/deleteroles",
|
|
body: roleids
|
|
})
|
|
.catch((e) => {showServerError(e)})
|
|
.finally(() => localDataModel.loadData())
|
|
},
|
|
updateApplication: (app: iApplication) => {
|
|
m.request ({
|
|
method: "POST", // Hier muss POST verwendet werden, weil GET keinen Body hat und Parameter nur im URL transportieren kann
|
|
url: "/auth/updateapplication",
|
|
body: app
|
|
})
|
|
.catch((e) => {showServerError(e)})
|
|
.finally(() => localDataModel.loadData())
|
|
},
|
|
deleteApplications: (apps: iApplication[]) => {
|
|
let appids: string[] = []
|
|
apps.forEach((app: iApplication) => {
|
|
appids.push(app.appid)
|
|
})
|
|
m.request({
|
|
method: "POST",
|
|
url: "/auth/deleteapplications",
|
|
body: appids
|
|
})
|
|
.catch((e) => {showServerError(e)})
|
|
.finally(() => localDataModel.loadData())
|
|
},
|
|
updateApplicationRoles: () => {
|
|
interface iApplicationRole {
|
|
appid: string
|
|
roles: {roleid: string, isChecked: boolean}[]
|
|
}
|
|
let applicationroles: iApplicationRole[] = []
|
|
localDataModel.appnodes.forEach((appnode: iTreenode) => {
|
|
let app: iApplication = appnode.nodevalue
|
|
let approle: iApplicationRole = {
|
|
appid: app.appid,
|
|
roles: []
|
|
}
|
|
appnode.children.forEach((rolenode: iTreenode) => {
|
|
let role: iRole = rolenode.nodevalue
|
|
approle.roles.push({roleid: role.roleid, isChecked: rolenode.isChecked})
|
|
})
|
|
applicationroles.push(approle)
|
|
})
|
|
m.request({
|
|
method: "POST",
|
|
url: "/auth/updateapplicationroles",
|
|
body: applicationroles
|
|
})
|
|
.catch((e) => {showServerError(e)})
|
|
.finally(() => localDataModel.loadData())
|
|
}
|
|
}
|
|
|
|
export const applicationpage: m.ClosureComponent = (vnode) => {
|
|
return {
|
|
oninit: (vnode) => {
|
|
localDataModel.loadData()
|
|
},
|
|
view: (vnode) => {
|
|
return m("div.w3-container.w3-content", {
|
|
style: {'max-width': '80%', display: 'flex', 'flex-direction': 'column', height: 'inherit', 'overflow-y': 'inherit'}
|
|
},
|
|
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
|
m("div.w3-cell-row",
|
|
m("div.w3-cell.w3-padding.w3-mobile",
|
|
m("h1", "Rollen"),
|
|
m(Treetable,
|
|
{
|
|
treenodes: localDataModel.rolenodes,
|
|
startexpanded: false,
|
|
onRowMarked: (node: iTreenode) => {
|
|
//alert(node.nodevalue['roleid'])
|
|
},
|
|
columnmapper: (node: iTreenode) => {
|
|
let role = node.nodevalue as iRole
|
|
return [
|
|
m("span.w3-button",
|
|
{
|
|
style: {padding: '0px'},
|
|
onclick: () => {
|
|
node.isChecked = !node.isChecked
|
|
}
|
|
},
|
|
m("i.material-icons.w3-theme-light", node.isChecked?"check_box":"check_box_outline_blank")
|
|
),
|
|
m("div", role.level),
|
|
m("div", role.roleid),
|
|
m("div", role.name)
|
|
]
|
|
}
|
|
} as iTreetable,
|
|
m(TopbarButton,
|
|
{
|
|
onclick: applyRoles,
|
|
tooltip: MOVERIGHT
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "keyboard_double_arrow_right")
|
|
),
|
|
m(TopbarButton,
|
|
{
|
|
onclick: () => {m.mount(gridMountpoints.modalhook, {
|
|
view: (vnode) => {
|
|
return m(RoleForm, {role: {id: -1, level: 0, name: "", roleid: ""} as iRole})
|
|
}
|
|
})}
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "add")
|
|
),
|
|
m(TopbarButton,
|
|
{
|
|
onclick: () => {m.mount(gridMountpoints.modalhook, DeleteRolesMessage)}
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "delete_forever")
|
|
),
|
|
m(TopbarButton,
|
|
{
|
|
onclick: () => {
|
|
let roles: iRole[] = []
|
|
localDataModel.rolenodes.forEach((rolenode: Treenode) => {
|
|
if (rolenode.isChecked) {
|
|
roles.push(rolenode.nodevalue)
|
|
}
|
|
})
|
|
if (roles.length != 1) {
|
|
m.mount(gridMountpoints.modalhook, WrongRoleEditSelectionMessage)
|
|
}
|
|
else {
|
|
m.mount(gridMountpoints.modalhook, {
|
|
view: (vnode) => {
|
|
return m(RoleForm, {
|
|
role: roles[0] as iRole
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "edit")
|
|
)
|
|
)
|
|
),
|
|
m("div.w3-cell.w3-padding.w3-mobile",
|
|
m("h1", "Anwendungen"),
|
|
m(Treetable,
|
|
{
|
|
treenodes: localDataModel.appnodes,
|
|
startexpanded: true,
|
|
columnmapper: (node: iTreenode) => {
|
|
let cols: any[] = []
|
|
// Hier wird "Duck-Typing" genutzt, um den Typ von node.nodevalue zu ermitteln
|
|
// Je nach Typ werden die Spalten der Tabelle dann anders befüllt
|
|
if (node.nodevalue['appid'] != undefined) {
|
|
let app = node.nodevalue as iApplication
|
|
cols[0] = "enabled"
|
|
cols[1] = app.appid
|
|
cols[2] = app.name
|
|
}
|
|
else if (node.nodevalue['roleid'] != undefined) {
|
|
let role = node.nodevalue as iRole
|
|
cols[0] = "disabled"
|
|
cols[1] = role.roleid
|
|
cols[2] = role.name
|
|
}
|
|
|
|
return [
|
|
m(cols[0] == "enabled"?"span.w3-button":"span",
|
|
{
|
|
style: {padding: '0px'},
|
|
onclick: () => {
|
|
if (cols[0] == "enabled") {
|
|
node.isChecked = !node.isChecked
|
|
}
|
|
}
|
|
},
|
|
m("i.material-icons.w3-theme-light", node.isChecked?"check_box":"check_box_outline_blank")
|
|
),
|
|
m("div", cols[1]),
|
|
m("div", cols[2])
|
|
]
|
|
|
|
}
|
|
} as iTreetable,
|
|
m(TopbarButton,
|
|
{
|
|
onclick: applyRoles,
|
|
tooltip: MOVERIGHT
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "keyboard_double_arrow_right")
|
|
),
|
|
m(TopbarButton,
|
|
{
|
|
onclick: () => {m.mount(gridMountpoints.modalhook,
|
|
{
|
|
view: (vnode) => {
|
|
return m(ApplicationForm, {application: {id: -1} as iApplication} as iApplicationForm)
|
|
}
|
|
}
|
|
)}
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "add")
|
|
),
|
|
m(TopbarButton,
|
|
{
|
|
onclick: () => {m.mount(gridMountpoints.modalhook, DeleteApplicatonsMessage)}
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "delete_forever")
|
|
),
|
|
m(TopbarButton,
|
|
{
|
|
onclick: () => {
|
|
let apps: iApplication[] = []
|
|
localDataModel.appnodes.forEach((appnode: Treenode) => {
|
|
if (appnode.isChecked) {
|
|
apps.push(appnode.nodevalue)
|
|
}
|
|
})
|
|
if (apps.length != 1) {
|
|
m.mount(gridMountpoints.modalhook, WrongApplicationEditSelectionMessage)
|
|
}
|
|
else {
|
|
m.mount(gridMountpoints.modalhook, {
|
|
view: (vnode) => {
|
|
return m(ApplicationForm, {
|
|
application: apps[0] as iApplication
|
|
} as iApplicationForm)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
} as iTopbarButton,
|
|
m("i.material-icons", "edit")
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
function sortRoleNodes(node1: iTreenode, node2: iTreenode) {
|
|
// es soll zuerst nach Level und dann nach rowid sortiert werden
|
|
let v1 = (node1.nodevalue['level'].toString().padStart(6,'0') + node1.nodevalue['roleid']).toLocaleLowerCase('de-de')
|
|
let v2 = (node2.nodevalue['level'].toString().padStart(6,'0') + node2.nodevalue['roleid']).toLocaleLowerCase('de-de')
|
|
if (v1 < v2) {
|
|
return -1
|
|
}
|
|
if (v1 > v2) {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
function applyRoles() {
|
|
let checkedApps = localDataModel.appnodes.filter((appnode: iTreenode) => {
|
|
return appnode.isChecked
|
|
})
|
|
if (checkedApps.length == 0) {
|
|
m.mount(gridMountpoints.modalhook, ErrorMessage)
|
|
return
|
|
}
|
|
|
|
m.mount(gridMountpoints.modalhook, {view: () => {return m(ApplyRolesWarning, {applyroles: doit})}})
|
|
|
|
function doit() {
|
|
checkedApps.forEach((appnode: iTreenode) => {
|
|
localDataModel.rolenodes.forEach((rolenode: iTreenode) => {
|
|
let approle: iTreenode = appnode.children.find((child: iTreenode) => {
|
|
return child.nodevalue['roleid'] === rolenode.nodevalue['roleid']
|
|
})
|
|
if (approle != undefined) {
|
|
approle.isChecked = rolenode.isChecked
|
|
}
|
|
else if (rolenode.isChecked) {
|
|
let newapprole = new Treenode(rolenode.nodevalue, false, [])
|
|
newapprole.isChecked = rolenode.isChecked
|
|
appnode.children.push(newapprole)
|
|
}
|
|
})
|
|
})
|
|
localDataModel.updateApplicationRoles()
|
|
}
|
|
}
|
|
|
|
const ErrorMessage: m.Component = {
|
|
view: (vnode) => {
|
|
return (
|
|
m(ModalView,
|
|
{
|
|
title: "Fehler",
|
|
type: ModalType.ERROR,
|
|
width: '33%'
|
|
} as iModalView,
|
|
m("p", "Es wurden noch keine Ziel-Anwendungen markiert!"),
|
|
m("div.w3-bar.w3-padding-16",
|
|
[
|
|
m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "OK"),
|
|
]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
export interface iApplyRolesWaring {
|
|
applyroles: Function
|
|
}
|
|
const ApplyRolesWarning: m.Component<iApplyRolesWaring> = {
|
|
view: (vnode) => {
|
|
return (
|
|
m(ModalView,
|
|
{
|
|
title: "Hinweis",
|
|
type: ModalType.MESSAGE,
|
|
width: '33%'
|
|
} as iModalView,
|
|
m("p", "Wenn Rollen nicht ausgewählt sind bedeutet dies, dass die Rollen in den ausgewählten Anwendungen gelöscht werden!"),
|
|
m("div.w3-bar.w3-padding-16",
|
|
[
|
|
m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "Abbruch"),
|
|
m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null); vnode.attrs.applyroles()}}, "OK"),
|
|
]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
const WrongRoleEditSelectionMessage: m.Component = {
|
|
view: () => {
|
|
return m(WrongEditSelectionMessage, {elementname: "Rolle"} as iWrongEditSelectionMessage)
|
|
}
|
|
}
|
|
const WrongApplicationEditSelectionMessage: m.Component = {
|
|
view: () => {
|
|
return m(WrongEditSelectionMessage, {elementname: "Anwendung"} as iWrongEditSelectionMessage)
|
|
}
|
|
}
|
|
|
|
const DeleteRolesMessage: m.ClosureComponent = (vnode) => {
|
|
let roles: iRole[]
|
|
return {
|
|
oninit: (vnode) => {
|
|
roles = []
|
|
localDataModel.rolenodes.forEach((rolenode: iTreenode) => {
|
|
if (rolenode.isChecked) {
|
|
roles.push(rolenode.nodevalue)
|
|
}
|
|
})
|
|
},
|
|
view: (vnode) => {
|
|
return (
|
|
m(ModalView,
|
|
{
|
|
title: "Rollen löschen",
|
|
type: ModalType.MESSAGE,
|
|
width: '33%'
|
|
} as iModalView,
|
|
m("p", "Sie wollen die folgenden Rollen löschen:"),
|
|
m("ul",
|
|
roles.map((role: iRole) => {
|
|
return m("li", role.roleid)
|
|
})
|
|
),
|
|
m("div.w3-bar.w3-padding-16",
|
|
[
|
|
m("button.w3-button.w3-theme.w3-right",
|
|
{
|
|
style: {'margin-left': '4px'},
|
|
onclick: () => {
|
|
localDataModel.deleteRoles(roles);
|
|
m.mount(gridMountpoints.modalhook, null)
|
|
}}, "Löschen"),
|
|
m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "Abbrechen"),
|
|
]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
interface iRoleForm {
|
|
role: iRole
|
|
}
|
|
const RoleForm: m.ClosureComponent<iRoleForm> = () => {
|
|
let form: HTMLFormElement = null;
|
|
let currentrole: iRole
|
|
function reset (e) {
|
|
form.reset()
|
|
}
|
|
function unmount () {
|
|
m.mount(gridMountpoints.modalhook, null)
|
|
}
|
|
function submit (e) {
|
|
e.preventDefault()
|
|
let formdata: FormData = new FormData(e.target as HTMLFormElement)
|
|
|
|
for (let element of formdata) {
|
|
currentrole[element[0]] = element[1]
|
|
}
|
|
localDataModel.updateRole(currentrole)
|
|
unmount()
|
|
}
|
|
return {
|
|
view: (vnode) => {
|
|
currentrole = (vnode.attrs.role === undefined) ? {} as iRole : vnode.attrs.role
|
|
return m(ModalView,
|
|
{
|
|
title: "Rolle",
|
|
type: ModalType.MESSAGE,
|
|
width: 'max-content'
|
|
} as iModalView,
|
|
|
|
m("form.w3-padding-16", {oncreate: (vnode) => {form = vnode.dom as HTMLFormElement}, onsubmit: submit},
|
|
m("label", "ID (intern)"),
|
|
m("div.w3-padding-small", currentrole.id),
|
|
m("label", "Rollen-ID"),
|
|
m("input.w3-input.w3-border", {name: 'roleid', type: 'text', value: currentrole.roleid}),
|
|
m("label", "Name"),
|
|
m("input.w3-input.w3-border", {name: 'name', type: 'text', value: currentrole.name}),
|
|
m("label", "Level"),
|
|
m("input.w3-input.w3-border", {name: 'level', type: 'number', value: currentrole.level}),
|
|
m("label", "Beschreibung"),
|
|
m("input.w3-input.w3-border", {name: 'beschreibung', type: 'text', value: currentrole.beschreibung}),
|
|
m("div.w3-bar.w3-padding-16",
|
|
[
|
|
m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, type: "submit"}, "Speichern"),
|
|
m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, onclick: unmount, type: "button"}, "Abbrechen"),
|
|
m("button.w3-button.w3-theme.w3-right", {onclick: reset, type: "button"}, "Zurücksetzen")
|
|
|
|
]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
interface iApplicationForm {
|
|
application: iApplication
|
|
}
|
|
const ApplicationForm: m.ClosureComponent<iApplicationForm> = () => {
|
|
let form: HTMLFormElement = null;
|
|
let currentapp: iApplication
|
|
let isDirty = false
|
|
function reset (e) {
|
|
form.reset()
|
|
}
|
|
function unmount () {
|
|
if (isDirty) {
|
|
alert("is Dirty")
|
|
}
|
|
m.mount(gridMountpoints.modalhook, null)
|
|
}
|
|
function submit (e) {
|
|
e.preventDefault()
|
|
let formdata: FormData = new FormData(e.target as HTMLFormElement)
|
|
|
|
for (let element of formdata) {
|
|
currentapp[element[0]] = element[1]
|
|
}
|
|
localDataModel.updateApplication(currentapp)
|
|
unmount()
|
|
}
|
|
return {
|
|
view: (vnode) => {
|
|
currentapp = (vnode.attrs.application === undefined) ? {} as iApplication : vnode.attrs.application
|
|
return m(ModalView,
|
|
{
|
|
title: "Anwendung",
|
|
type: ModalType.MESSAGE,
|
|
width: 'max-content'
|
|
} as iModalView,
|
|
|
|
m("form.w3-padding-16", {oncreate: (vnode) => {form = vnode.dom as HTMLFormElement}, onsubmit: submit},
|
|
m("label", "ID (intern)"),
|
|
m("div.w3-padding-small", currentapp.id),
|
|
m("label", "Application-ID"),
|
|
m("input.w3-input.w3-border", {name: 'appid', type: 'text', value: currentapp.appid}),
|
|
m("label", "Name"),
|
|
m("input.w3-input.w3-border", {name: 'name', type: 'text', value: currentapp.name}),
|
|
m("label", "Minimum-Version"),
|
|
m("input.w3-input.w3-border", {name: 'minversion', type: 'text', value: currentapp.minversion}),
|
|
m("label", "Beschreibung"),
|
|
m("input.w3-input.w3-border", {name: 'beschreibung', type: 'text', value: currentapp.beschreibung}),
|
|
m("div.w3-bar.w3-padding-16",
|
|
[
|
|
m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, type: "submit"}, "Speichern"),
|
|
m("button.w3-button.w3-theme.w3-right", {style: {'margin-left': '4px'}, onclick: unmount, type: "button"}, "Abbrechen"),
|
|
m("button.w3-button.w3-theme.w3-right", {onclick: reset, type: "button"}, "Zurücksetzen")
|
|
|
|
]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
const DeleteApplicatonsMessage: m.ClosureComponent = (vnode) => {
|
|
let apps: iApplication[]
|
|
return {
|
|
oninit: (vnode) => {
|
|
apps = []
|
|
localDataModel.appnodes.forEach((appnode: iTreenode) => {
|
|
if (appnode.isChecked) {
|
|
apps.push(appnode.nodevalue)
|
|
}
|
|
})
|
|
},
|
|
view: (vnode) => {
|
|
return (
|
|
m(ModalView,
|
|
{
|
|
title: "Anwendungen löschen",
|
|
type: ModalType.MESSAGE,
|
|
width: '33%'
|
|
} as iModalView,
|
|
m("p.w3-panel.w3-red",
|
|
[
|
|
"Vor dem Löschen muss sichergestellt sein, dass es noch mindestens eine App gibt, in der die Rolle 'SysAdmin' ",
|
|
"vergeben ist und in der ein Benutzer diese Rolle besitzt!"
|
|
]
|
|
),
|
|
m("p", "Sie wollen die folgenden Anwendungen löschen:"),
|
|
m("ul",
|
|
apps.map((app: iApplication) => {
|
|
return m("li", app.appid)
|
|
})
|
|
),
|
|
m("div.w3-bar.w3-padding-16",
|
|
[
|
|
m("button.w3-button.w3-theme.w3-right",
|
|
{
|
|
style: {'margin-left': '4px'},
|
|
onclick: () => {
|
|
localDataModel.deleteApplications(apps);
|
|
m.mount(gridMountpoints.modalhook, null)
|
|
}}, "Löschen"),
|
|
m("button.w3-button.w3-theme.w3-right", {onclick: () => {m.mount(gridMountpoints.modalhook, null)}}, "Abbrechen"),
|
|
]
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
} |