Переглянути джерело

Fixes #7 Password manager link

isundil 1 рік тому
батько
коміт
219d81e6c1
5 змінених файлів з 184 додано та 102 видалено
  1. 50 9
      public/javascripts/entity.js
  2. 19 1
      public/stylesheets/main.css
  3. 16 7
      routes/entity.ts
  4. 96 84
      src/ConfigLoader.ts
  5. 3 1
      views/entity.pug

+ 50 - 9
public/javascripts/entity.js

@@ -50,10 +50,37 @@
         UpdateLdif();
     }
 
+    function getEditLink(className, attrName) {
+        for (var i in window.editlinks) {
+            if (window.editlinks[i].indexOf(`${className}.${attrName}`) >= 0)
+                return i;
+        }
+        return null;
+    }
+    function manageEditLink(input) {
+        const editLink = getEditLink(input.dataset.klass, input.dataset.attributeName);
+        if (!editLink)
+            return;
+        let linkWrapper = document.createElement("div");
+        let link = document.createElement("a");
+        input.disabled = true;
+        linkWrapper.className = "editlink";
+        link.textContent = editLink;
+        link.href = editLink;
+        link.target = "_blank";
+        linkWrapper.appendChild(link);
+        input.parentNode.appendChild(linkWrapper);
+    }
+
     let prevItem = null; // input field
     function removeButtonHandler(input, li) {
         return (e) => {
             e?.preventDefault();
+            if (multipleValues[input.dataset.attributeName])
+                multipleValues[input.dataset.attributeName]--;
+            if (!multipleValues[input.dataset.attributeName] && input.dataset.mandatory) {
+                // remove last remove button
+            }
             input.value = "";
             onValueChanged(input);
             li.bsTooltip.hide();
@@ -71,20 +98,26 @@
             let line = addButton.parentInput.parentNode.parentNode;
             let copy = line.cloneNode(true);
             line.parentNode.insertBefore(copy, line);
-            let input = copy.querySelector("label > input");
+            let input = copy.querySelector("label input");
             input.value = "";
             input.dataset.initialValue = "";
             input.addEventListener("change", e => onValueChanged(e.currentTarget));
             input.dataset.inputId = ++maxInputId;
-            copy.querySelector(".button-remove")?.addEventListener("click", removeButtonHandler(input, copy));
+            multipleValues[input.dataset.attributeName] = (multipleValues[input.dataset.attributeName] || 1) + 1;
+            if (!copy.querySelector(".button-remove")) {
+                addRemoveButton(line.querySelector("label > input"), line);
+                addRemoveButton(input, copy);
+            } else {
+                copy.querySelector(".button-remove")?.addEventListener("click", removeButtonHandler(input, copy));
+            }
             let button = copy.querySelector("label > span > .button-add");
             button.parentNode.removeChild(button);
             setTooltipFromInput(input);
         });
         li.querySelector(".LDAPAttribute > span").appendChild(addButton);
     }
-    function addRemoveButton(input, li) { // item is the input field
-        if (input.parentNode.children[0].classList.contains("mandatory"))
+    function addRemoveButton(input, li) {
+        if (input.dataset.isMandatory == "true" && (multipleValues[input.dataset.attributeName] || 1) <= 1)
             return;
         let button = document.createElement("a");
         button.innerText = "";
@@ -104,13 +137,14 @@
             return;
         li.dataset.bsTitle = description;
         li.dataset.bsToggle = "tooltip";
-        li.bsTooltip = new bootstrap.Tooltip(li);
+        li.bsTooltip = new bootstrap.Tooltip(li, { trigger: "hover" });
     }
     function setTooltipFromInput(input) {
         setTooltip(input.parentNode.parentNode, schema[input.dataset.klass]?.descriptions?.[input.dataset.attributeName]);
     }
     inputs.forEach(i => {
         maxInputId = Math.max(i.dataset.inputId, maxInputId);
+        i.dataset.isMandatory = i.parentNode.children[0].classList.contains("mandatory");
         let attrName = i.dataset.attributeName;
         if ((i.dataset.initialValue || "").length)
             multipleValues[attrName] = (multipleValues[attrName] || 0) + 1;
@@ -119,10 +153,13 @@
             prevItem.dataset.attributeName !== i.dataset.attributeName) {
             addAddButton(prevItem, prevItem.parentNode.parentNode);
         }
-        addRemoveButton(i, i.parentNode.parentNode);
         prevItem = i;
-        manageDuplicateAttributes(i.parentElement.parentElement, i);
+        manageDuplicateAttributes(i.parentElement.parentElement.parentElement, i);
         setTooltipFromInput(i);
+        manageEditLink(i);
+    });
+    inputs.forEach(i => {
+        addRemoveButton(i, i.parentNode.parentNode);
     });
     prevItem && addAddButton(prevItem, prevItem.parentNode.parentNode);
 
@@ -136,7 +173,9 @@
         if (isMandatory)
             span.classList.add("mandatory");
         span.innerText = name;
+        let inputWrapper = document.createElement("span");
         let input = document.createElement("input");
+        inputWrapper.className = "form-input-wrapper";
         input.type = "text";
         input.value = init;
         input.dataset.initialValue = init;
@@ -148,11 +187,13 @@
         input.className = "form-control";
         input.addEventListener("change", e => onValueChanged(e.currentTarget))
         label.appendChild(span);
-        label.appendChild(input);
+        label.appendChild(inputWrapper);
+        inputWrapper.appendChild(input);
         addAddButton(input, li);
         if (!isMandatory)
             addRemoveButton(input, li);
         manageDuplicateAttributes(li, input);
+        manageEditLink(input);
         setTooltipFromInput(input);
         return li;
     }
@@ -191,6 +232,6 @@
     });
 })(
     window['dn'], window['schema'],
-    document.querySelectorAll(".LDAPAttribute > input"), document.getElementById("ldifOutput"),
+    document.querySelectorAll(".LDAPAttribute input"), document.getElementById("ldifOutput"),
     document.getElementById("classContainer"),
     document.getElementById("addClassSelect"), document.getElementById("addClassBtn"));

+ 19 - 1
public/stylesheets/main.css

@@ -72,6 +72,7 @@ iframe#page {
     background: rgba(255, 255, 255, 0.33);
     background: linear-gradient(to right, rgba(255, 255, 255, 0.45), 66%, rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0));
     backdrop-filter: blur(2px);
+    display: inline-block;
 }
 #home-page {
     background: #edf6fb;
@@ -134,9 +135,26 @@ label.LDAPAttribute > span.mandatory::before {
 label.LDAPAttribute .button::before {
     cursor: pointer;
 }
-label.LDAPAttribute > input {
+label.LDAPAttribute > span {
     display: inline-block;
     width: 450px;
+    position: relative;
+}
+label.LDAPAttribute .editlink {
+    display: flex;
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    align-items: center;
+}
+label.LDAPAttribute .editlink a {
+    flex: 1;
+    margin: 3px;
+    background: rgba(255, 255, 255, 0.2);
+    backdrop-filter: blur(2px);
+    padding-left: .375rem;
 }
 #ldifOutput {
     width: 100%;

+ 16 - 7
routes/entity.ts

@@ -7,6 +7,7 @@ import Security from '../src/Security';
 import RouterUtils from '../src/RouterUtils';
 import { ILDAPManager } from '../src/ldapInterface';
 import { LDAPSchemaObjectClass, ClassType } from '../src/LDAPSchema';
+import ConfigManager from '../src/ConfigLoader';
 
 function LDAPEntryToAttributes(entry: Map<string, Array<string>>): any {
     let result: any = [];
@@ -44,7 +45,7 @@ function StructifySchema(schema: Map<string, LDAPSchemaObjectClass>): any {
             type: getType(schema, key)
         };
         result[key] = obj;
-	}
+    }
     return result;
 }
 
@@ -54,7 +55,7 @@ class AttributesByClasses {
             this.fEntries.set(eClass, new Map());
             let cl = classes.get(eClass);
             cl && this.fObjectClasses.push(cl);
-		}
+        }
         for (let [i, j] of entry) {
             if (i.toLowerCase() == 'objectclass')
                 continue;
@@ -63,7 +64,7 @@ class AttributesByClasses {
                 if (oc.HasAttribute(i)) {
                     this.fEntries.get(oc.GetName())?.set(i, j);
                     found = true;
-				}
+                }
             }
             if (!found)
                 this.fUnmapped.set(i, j);
@@ -79,7 +80,7 @@ class AttributesByClasses {
             for (let attr of klass.ListAttributes())
                 !classContent.has(attr) && !entry.has(attr) && classContent.set(attr, new Array());
             this.fEntries.set(className, classContent);
-		}
+        }
     }
 
     public IsMandatoryAttr(attr: string): boolean {
@@ -87,7 +88,7 @@ class AttributesByClasses {
             if (klass.HasMust(attr))
                 return true;
         return false;
-	}
+    }
 
     public ToMap(): any {
         let result: any = {};
@@ -117,13 +118,20 @@ class AttributesByClasses {
 
     public ClassExists(className: string): boolean {
         return this.fEntries.has(className);
-	}
+    }
 
     private fObjectClasses: LDAPSchemaObjectClass[] = new Array();
     private fEntries: Map<string, Map<string, string[]>> = new Map();
     private fUnmapped: Map<string, string[]> = new Map();
 }
 
+function StructifyEditLinks(input:Map<string, Array<string>>):any {
+    const result:any = {};
+    for (let i of input.keys())
+        result[i] = input.get(i);
+    return result;
+}
+
 router.get('/:dn', (req: express.Request, res: express.Response) => {
     if (!req.isUserLogged)
         return RouterUtils.Redirect(res, "/login");
@@ -142,6 +150,7 @@ router.get('/:dn', (req: express.Request, res: express.Response) => {
                 getType: (klass: string) => getType(schema, klass),
                 isMandatory: (attr: string): boolean => classes.IsMandatoryAttr(attr),
                 schema: StructifySchema(schema),
+                editLinks: StructifyEditLinks(ConfigManager.GetInstance().GetLDAPEditLinks()),
                 getUnusedClass: (): string[] => {
                     let result = [];
                     for (let [key, klass] of schema) {
@@ -165,7 +174,7 @@ router.delete('/', (req: express.Request, res: express.Response) => {
     if (!req.query["csrf"] || !req.query["dn"] || Array.isArray(req.query["csrf"]) || Array.isArray(req.query["dn"])) {
         res.sendStatus(400);
         return;
-	}
+    }
     if (!session || !req.query["csrf"] || req.query["csrf"] !== session.GetCSRFToken()) {
         res.sendStatus(403);
         return;

+ 96 - 84
src/ConfigLoader.ts

@@ -1,108 +1,120 @@
 
 export class LDAPAttribute {
-	constructor(config: any) {
-		this.name = config["name"];
-		this.type = config["type"];
-		this.mapped = config["mapped"];
-		this.filter = config["filter"] ? new RegExp(config["filter"]) : null;
-	}
-	public readonly name: string;
-	public readonly type: string;
-	public readonly mapped: string;
-	public readonly filter: RegExp | null;
+    constructor(config: any) {
+        this.name = config["name"];
+        this.type = config["type"];
+        this.mapped = config["mapped"];
+        this.filter = config["filter"] ? new RegExp(config["filter"]) : null;
+    }
+    public readonly name: string;
+    public readonly type: string;
+    public readonly mapped: string;
+    public readonly filter: RegExp | null;
 }
 
 export class LDAPCategory {
-	constructor(config: any) {
-		this.fName = config["name"];
-		this.fBase = config["base"];
-		this.fScope = config["scope"];
-		this.fAttributes = new Array();
-		this.fFilter = config["filter"] || null;
-		for (let i of config["attributes"])
-			this.fAttributes.push(new LDAPAttribute(i));
-	}
+    constructor(config: any) {
+        this.fName = config["name"];
+        this.fBase = config["base"];
+        this.fScope = config["scope"];
+        this.fAttributes = new Array();
+        this.fFilter = config["filter"] || null;
+        for (let i of config["attributes"])
+            this.fAttributes.push(new LDAPAttribute(i));
+    }
 
-	public GetBaseDn(): string { return this.fBase; }
-	public GetScope(): string { return this.fScope; }
-	public GetName(): string { return this.fName; }
-	public GetFilter(): string|null { return this.fFilter; }
-	public GetAttributes(): Array<LDAPAttribute> { return this.fAttributes; }
+    public GetBaseDn(): string { return this.fBase; }
+    public GetScope(): string { return this.fScope; }
+    public GetName(): string { return this.fName; }
+    public GetFilter(): string|null { return this.fFilter; }
+    public GetAttributes(): Array<LDAPAttribute> { return this.fAttributes; }
 
-	private fName: string;
-	private fBase: string;
-	private fScope: string;
-	private fFilter: string|null;
-	private fAttributes: Array<LDAPAttribute>;
+    private fName: string;
+    private fBase: string;
+    private fScope: string;
+    private fFilter: string|null;
+    private fAttributes: Array<LDAPAttribute>;
 }
 
 export interface IConfigLoader {
-	GetCategories(): Array<LDAPCategory>;
-	GetCategoryByName(name: string): LDAPCategory | null;
-	GetLDAPRoot(): string;
-	GetLDAPUrls(): Array<string>;
-	GetBindDn(): string;
-	GetBindPassword(): string;
-	GetNoLogin(): boolean;
-	GetLoginBase(): string;
-	GetLoginScope(): string;
-	GetLoginFilter(username: string): string;
+    GetCategories(): Array<LDAPCategory>;
+    GetCategoryByName(name: string): LDAPCategory | null;
+    GetLDAPRoot(): string;
+    GetLDAPUrls(): Array<string>;
+    GetLDAPEditLinks(): Map<string, Array<string>>;
+    GetBindDn(): string;
+    GetBindPassword(): string;
+    GetNoLogin(): boolean;
+    GetLoginBase(): string;
+    GetLoginScope(): string;
+    GetLoginFilter(username: string): string;
 }
 
 class ConfigLoader implements IConfigLoader {
-	public constructor(config: any) {
-		this.fCategories = new Array();
-		for (let i of config.LDAP_CATEGORIES) {
-			this.fCategories.push(new LDAPCategory(i));
-		}
-		this.fServer.root = config["LDAP_ROOT"];
-		this.fServer.urls = typeof config["LDAP_URL"] === "string" ? [config["LDAP_URL"]] : config["LDAP_URL"];
-		this.fServer.bindDn = config["LDAP_BIND_DN"];
-		this.fServer.bindPassword = config["LDAP_BIND_PASSWD"];
+    public constructor(config: any) {
+        this.fCategories = new Array();
+        for (let i of config.LDAP_CATEGORIES) {
+            this.fCategories.push(new LDAPCategory(i));
+        }
+        this.fEditLinks = new Map();
+        for (let i in config.EDIT_LINKS) {
+            this.fEditLinks.set(i, new Array());
+            for (let j of config.EDIT_LINKS[i])
+                this.fEditLinks.get(i)?.push(j);
+        }
+        this.fServer.root = config["LDAP_ROOT"];
+        this.fServer.urls = typeof config["LDAP_URL"] === "string" ? [config["LDAP_URL"]] : config["LDAP_URL"];
+        this.fServer.bindDn = config["LDAP_BIND_DN"];
+        this.fServer.bindPassword = config["LDAP_BIND_PASSWD"];
 
-		this.fLogin.fBase = config["LDAP_LOGIN_BASE"];
-		this.fLogin.fScope = config["LDAP_LOGIN_SCOPE"];
-		this.fLogin.fFilter = config["LDAP_LOGIN_FILTER"];
+        this.fLogin.fBase = config["LDAP_LOGIN_BASE"];
+        this.fLogin.fScope = config["LDAP_LOGIN_SCOPE"];
+        this.fLogin.fFilter = config["LDAP_LOGIN_FILTER"];
 
-		this.fNoLogin = !!config["NOLOGIN"];
-	}
+        this.fNoLogin = !!config["NOLOGIN"];
+    }
 
-	public GetLDAPRoot(): string { return this.fServer.root; }
-	public GetLDAPUrls(): Array<string> { return this.fServer.urls; }
-	public GetBindDn(): string { return this.fServer.bindDn; }
-	public GetBindPassword(): string { return this.fServer.bindPassword; }
+    public GetLDAPRoot(): string { return this.fServer.root; }
+    public GetLDAPUrls(): Array<string> { return this.fServer.urls; }
+    public GetBindDn(): string { return this.fServer.bindDn; }
+    public GetBindPassword(): string { return this.fServer.bindPassword; }
 
-	public GetCategories(): Array<LDAPCategory> { return this.fCategories; }
-	public GetCategoryByName(name: string): LDAPCategory | null {
-		for (let i of this.fCategories)
-			if (i.GetName() === name)
-				return i;
-		return null;
-	}
+    public GetLDAPEditLinks(): Map<string, Array<string>> {
+        return this.fEditLinks;
+    }
 
-	public GetLoginBase(): string { return this.fLogin.fBase; }
-	public GetLoginScope(): string { return this.fLogin.fScope; }
-	public GetLoginFilter(login: string): string { return this.fLogin.fFilter(login); }
-	public GetNoLogin(): boolean { return this.fNoLogin; }
+    public GetCategories(): Array<LDAPCategory> { return this.fCategories; }
+    public GetCategoryByName(name: string): LDAPCategory | null {
+        for (let i of this.fCategories)
+            if (i.GetName() === name)
+                return i;
+        return null;
+    }
 
-	private fCategories: Array<LDAPCategory>;
-	private fNoLogin: boolean = false;
-	private fServer = {
-		urls: [],
-		root: "",
-		bindDn: "",
-		bindPassword: ""
-	};
-	private fLogin = {
-		fBase: "",
-		fScope: "one",
-		fFilter: (username: string):string => ""
-	};
+    public GetLoginBase(): string { return this.fLogin.fBase; }
+    public GetLoginScope(): string { return this.fLogin.fScope; }
+    public GetLoginFilter(login: string): string { return this.fLogin.fFilter(login); }
+    public GetNoLogin(): boolean { return this.fNoLogin; }
+
+    private fCategories: Array<LDAPCategory>;
+    private fEditLinks: Map<string, Array<string>>;
+    private fNoLogin: boolean = false;
+    private fServer = {
+        urls: [],
+        root: "",
+        bindDn: "",
+        bindPassword: ""
+    };
+    private fLogin = {
+        fBase: "",
+        fScope: "one",
+        fFilter: (username: string):string => ""
+    };
 }
 let _instance: IConfigLoader = new ConfigLoader(require("../config.js"));
 
 export default class ConfigManager {
-	public static GetInstance(): IConfigLoader {
-		return _instance;
-	}
+    public static GetInstance(): IConfigLoader {
+        return _instance;
+    }
 }

+ 3 - 1
views/entity.pug

@@ -16,7 +16,8 @@ block content
                   label(class="LDAPAttribute form-label")
                     -mandatory=isMandatory(key)
                     span(class=mandatory ? "mandatory": "")=key
-                    input(type="text",value=val,data-initial-value=val,data-input-id=id,data-attribute-name=key,required=mandatory,data-klass=className,class="form-control")
+                    span(class="form-input-wrapper")
+                      input(type="text",value=val,data-initial-value=val,data-input-id=id,data-attribute-name=key,required=mandatory,data-klass=className,class="form-control")
                     -id++
     div
       select(id="addClassSelect")
@@ -32,4 +33,5 @@ block append scripts
   script.
     window['dn']="#{dn}";
     window['schema']=!{JSON.stringify(schema)};
+    window['editlinks']=!{JSON.stringify(editLinks)};
   script(src="/javascripts/entity.js")