diff --git a/api.go b/api.go index af24370..2fb6d4d 100644 --- a/api.go +++ b/api.go @@ -758,7 +758,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) { respondBool(200, true, gc) } -// @Summary Extend time before the user(s) expiry. +// @Summary Extend time before the user(s) expiry, or create and expiry if it doesn't exist. // @Produce json // @Param extendExpiryDTO body extendExpiryDTO true "Extend expiry object" // @Success 200 {object} boolResponse @@ -780,6 +780,9 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) { if expiry, ok := app.storage.users[id]; ok { app.storage.users[id] = expiry.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute) app.debug.Printf("Expiry extended for \"%s\"", id) + } else { + app.storage.users[id] = time.Now().AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute) + app.debug.Printf("Created expiry for \"%s\"", id) } } if err := app.storage.storeUsers(); err != nil { diff --git a/go.sum b/go.sum index b30922b..fd35cc7 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4= github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -58,6 +59,7 @@ github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 h1:QthAQCekS1YOeYWS github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZd/Y= github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -216,6 +218,7 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -263,8 +266,10 @@ github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2t github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= diff --git a/html/admin.html b/html/admin.html index 92f37ec..da5ecd6 100644 --- a/html/admin.html +++ b/html/admin.html @@ -152,6 +152,11 @@ + + + {{ .strings.sendDeleteNotificationEmail }} + + {{ .strings.submit }} @@ -574,7 +579,14 @@ {{ .strings.modifySettings }} {{ .strings.extendExpiry }} - {{ .strings.disable }} + + {{ .strings.disable }} + + + {{ .strings.setExpiry }} + + + {{ .strings.sendPWR }} {{ .quantityStrings.deleteUser.Singular }} diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json index 4c4a4ed..90aa665 100644 --- a/lang/admin/en-us.json +++ b/lang/admin/en-us.json @@ -27,6 +27,7 @@ "enabled": "Enabled", "disabled": "Disabled", "reEnable": "Re-enable", + "setExpiry": "Set expiry", "disable": "Disable", "admin": "Admin", "updates": "Updates", diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index b31c037..f3a6839 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -560,9 +560,12 @@ export class accountsList { private _announceTextarea = document.getElementById("textarea-announce") as HTMLTextAreaElement; private _deleteUser = document.getElementById("accounts-delete-user") as HTMLSpanElement; private _disableEnable = document.getElementById("accounts-disable-enable") as HTMLSpanElement; + private _enableExpiry = document.getElementById("accounts-enable-expiry") as HTMLSpanElement; private _deleteNotify = document.getElementById("delete-user-notify") as HTMLInputElement; private _deleteReason = document.getElementById("textarea-delete-user") as HTMLTextAreaElement; private _extendExpiry = document.getElementById("accounts-extend-expiry") as HTMLSpanElement; + private _enableExpiryNotify = document.getElementById("expiry-extend-enable") as HTMLInputElement; + private _enableExpiryReason = document.getElementById("textarea-extend-enable") as HTMLTextAreaElement; private _modifySettings = document.getElementById("accounts-modify-user") as HTMLSpanElement; private _modifySettingsProfile = document.getElementById("radio-use-profile") as HTMLInputElement; private _modifySettingsUser = document.getElementById("radio-use-user") as HTMLInputElement; @@ -751,10 +754,12 @@ export class accountsList { if (showDisableEnable) { let message: string; if (this._shouldEnable) { + this._disableEnable.parentElement.classList.remove("manual"); message = window.lang.strings("reEnable"); this._disableEnable.classList.add("~positive"); this._disableEnable.classList.remove("~warning"); } else { + this._disableEnable.parentElement.classList.add("manual"); message = window.lang.strings("disable"); this._disableEnable.classList.add("~warning"); this._disableEnable.classList.remove("~positive"); @@ -964,6 +969,17 @@ export class accountsList { } } }); + + private _enableDisableUsers = (users: string[], enable: boolean, notify: boolean, reason: string|null, post: (req: XMLHttpRequest) => void) => { + let send = { + "users": users, + "enabled": enable, + "notify": notify + }; + if (reason) send["reason"] = reason; + _post("/users/enable", send, post, true); + }; + enableDisableUsers = () => { // We can share the delete modal for this const modalHeader = document.getElementById("header-delete-user"); @@ -987,13 +1003,7 @@ export class accountsList { form.onsubmit = (event: Event) => { event.preventDefault(); toggleLoader(button); - let send = { - "users": list, - "enabled": this._shouldEnable, - "notify": this._deleteNotify.checked, - "reason": this._deleteNotify ? this._deleteReason.value : "" - }; - _post("/users/enable", send, (req: XMLHttpRequest) => { + this._enableDisableUsers(list, this._shouldEnable, this._deleteNotify.checked, this._deleteNotify ? this._deleteReason.value : null, (req: XMLHttpRequest) => { if (req.readyState == 4) { toggleLoader(button); window.modals.deleteUser.close(); @@ -1010,7 +1020,7 @@ export class accountsList { } this.reload(); } - }, true); + }); } window.modals.deleteUser.show(); } @@ -1176,18 +1186,27 @@ export class accountsList { window.modals.modifyUser.show(); } - extendExpiry = () => { + extendExpiry = (enableUser?: boolean) => { const list = this._collectUsers(); let applyList: string[] = []; for (let id of list) { - if (this._users[id].expiry) { + if (this._users[id].expiry || enableUser) { applyList.push(id); } } - document.getElementById("header-extend-expiry").textContent = window.lang.quantity("extendExpiry", applyList.length); - const form = document.getElementById("form-extend-expiry") as HTMLFormElement; - form.onsubmit = (event: Event) => { - event.preventDefault(); + this._enableExpiryReason.classList.add("unfocused"); + let header: string; + if (enableUser) { + header = window.lang.quantity("reEnableUsers", list.length); + this._enableExpiryNotify.parentElement.classList.remove("unfocused"); + this._enableExpiryNotify.checked = false; + this._enableExpiryReason.value = ""; + } else { + header = window.lang.quantity("extendExpiry", applyList.length); + this._enableExpiryNotify.parentElement.classList.add("unfocused"); + } + document.getElementById("header-extend-expiry").textContent = header; + const extend = () => { let send = { "users": applyList } for (let field of ["months", "days", "hours", "minutes"]) { send[field] = +(document.getElementById("extend-expiry-"+field) as HTMLSelectElement).value; @@ -1203,6 +1222,28 @@ export class accountsList { this.reload(); } }); + }; + const form = document.getElementById("form-extend-expiry") as HTMLFormElement; + form.onsubmit = (event: Event) => { + event.preventDefault(); + if (enableUser) { + this._enableDisableUsers(applyList, true, this._enableExpiryNotify.checked, this._enableExpiryNotify ? this._enableExpiryReason.value : null, (req: XMLHttpRequest) => { + if (req.readyState == 4) { + if (req.status != 200 && req.status != 204) { + window.modals.extendExpiry.close(); + let errorMsg = window.lang.notif("errorFailureCheckLogs"); + if (!("error" in req.response)) { + errorMsg = window.lang.notif("errorPartialFailureCheckLogs"); + } + window.notifications.customError("deleteUserError", errorMsg); + return; + } + extend(); + } + }); + } else { + extend(); + } } window.modals.extendExpiry.show(); } @@ -1257,12 +1298,21 @@ export class accountsList { this._announceButton.onclick = this.announce; this._announceButton.classList.add("unfocused"); - this._extendExpiry.onclick = this.extendExpiry; + this._extendExpiry.onclick = () => { this.extendExpiry(); }; this._extendExpiry.classList.add("unfocused"); this._disableEnable.onclick = this.enableDisableUsers; this._disableEnable.classList.add("unfocused"); + this._enableExpiry.onclick = () => { this.extendExpiry(true); }; + this._enableExpiryNotify.onchange = () => { + if (this._enableExpiryNotify.checked) { + this._enableExpiryReason.classList.remove("unfocused"); + } else { + this._enableExpiryReason.classList.add("unfocused"); + } + }; + if (!window.usernameEnabled) { this._addUserName.classList.add("unfocused"); this._addUserName = this._addUserEmail;