diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 50581dc..49bde5b 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -1,12 +1,12 @@ { "name": "t42-sso-manager", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "t42-sso-manager", - "version": "1.5.0", + "version": "1.5.1", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^7.3.0", @@ -14,6 +14,7 @@ "@simpleworkjs/app-stack": "^1.0.0", "@simpleworkjs/conf": "^1.2.0", "@simpleworkjs/directory-schema": "^1.0.0", + "@simpleworkjs/frontend": "^0.2.5", "@simpleworkjs/ldap": "^1.0.0", "@simpleworkjs/orm": "^0.2.8", "bcrypt": "^6.0.0", @@ -1278,6 +1279,15 @@ "node": ">=18.0.0" } }, + "node_modules/@simpleworkjs/frontend": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@simpleworkjs/frontend/-/frontend-0.2.5.tgz", + "integrity": "sha512-PxR7UVPv3gRpdF0WsuAZplF1vYvKsEJQevVPhz9d72U+69vP/OH3tlaAXjtO/apMHfhT1viOPw2gMVOrPSxYZw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@simpleworkjs/ldap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@simpleworkjs/ldap/-/ldap-1.0.0.tgz", diff --git a/nodejs/package.json b/nodejs/package.json index 19a80c4..9605107 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -23,10 +23,11 @@ "dependencies": { "@fortawesome/fontawesome-free": "^7.3.0", "@popperjs/core": "^2.11.8", - "@simpleworkjs/conf": "^1.2.0", "@simpleworkjs/app-stack": "^1.0.0", - "@simpleworkjs/ldap": "^1.0.0", + "@simpleworkjs/conf": "^1.2.0", "@simpleworkjs/directory-schema": "^1.0.0", + "@simpleworkjs/frontend": "^0.2.5", + "@simpleworkjs/ldap": "^1.0.0", "@simpleworkjs/orm": "^0.2.8", "bcrypt": "^6.0.0", "bootstrap": "^5.3.8", diff --git a/nodejs/public/lib/js/app-base.js b/nodejs/public/lib/js/app-base.js index 7aa874d..7fe4a17 100644 --- a/nodejs/public/lib/js/app-base.js +++ b/nodejs/public/lib/js/app-base.js @@ -363,7 +363,7 @@ app.auth = (function(app){ } if(requiredGroups && !await memberOf(requiredGroups, user)){ - app.util.actionMessage( + app.messages.action( `

You do not have permission to be here. @@ -520,68 +520,15 @@ app.util = (function(app){ return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); }; - function actionMessage(message, $targetPassed, type, callback){ - message = message || ''; - - let $target = $targetPassed.closest('div.card').find('.actionMessage'); - if(!$target.length) $target = $($targetPassed.find('.actionMessage')[0]); - - type = type || 'info'; - callback = callback || function(){}; - - if($target.html() === message) return; - - if($target.html()){ - $target.slideUp('fast', function(){ - $target.html('') - $target.removeClass (function(index, className){ - return (className.match (/(^|\s)bg-\S+/g) || []).join(' '); - }); - if(message) return actionMessage(message, $target, type, callback); - $target.hide() - }) - }else{ - if(type) $target.addClass('bg-' + type); - - // Messages that bring their own buttons (actionConfirm) are left - // alone; everything else gets the standard dismiss button. - if(!message.includes('${message} - - ` - $target.html(message).slideDown('fast'); - } - setTimeout(callback,10) - } - - function actionConfirm(message, $target, type, callback){ - return new Promise((resolve, reject) =>{ - let id = crypto.randomUUID(); - message = ` -

- - ${message} - - - - -

- ` - actionMessage(message, $target, type); - $("body").on('click', `.confirm-${id}`, function(){ - actionMessage('', $target, type); - resolve(!!$(this).data('confirm')); - }); - }); - + // escapeHtml/actionMessage/actionConfirm moved to @simpleworkjs/frontend's + // app.util.escapeHtml and app.messages.action/confirm. + function escapeHtml(s){ + return String(s == null ? '' : s) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); } $.fn.serializeObject = function() { @@ -640,8 +587,7 @@ app.util = (function(app){ return { downloadFile: downloadFile, getUrlParameter: getUrlParameter, - actionMessage: actionMessage, - actionConfirm, + escapeHtml: escapeHtml, } })(app); @@ -696,9 +642,9 @@ $( document ).ready(async function(){ $(this).closest('.card').slideUp('fast'); }); - $('.actionMessage').on('click', 'button.action-close', function(event){ - app.util.actionMessage(null, $(this)); - }); + // action-close click handling is wired by @simpleworkjs/frontend's + // app.messages.js (delegated on document, so it also covers messages + // rendered after this ready handler runs). setInterval(()=>{ $('.momentFromNow').each((idx, el)=>{ @@ -729,11 +675,11 @@ function formAJAX(btn){ var method = ($form.attr('method') || 'post').toLowerCase(); if($form.validate && !$form.validate()){ - app.util.actionMessage('Please fix the form errors.', $form, 'danger') + app.messages.action('Please fix the form errors.', $form, 'danger') return false; } - app.util.actionMessage( + app.messages.action( `
Loading...
`, @@ -742,7 +688,7 @@ function formAJAX(btn){ ); app.api[method]($form.attr('action'), formData, function(error, data){ - app.util.actionMessage(data.message, $form, error ? 'danger' : 'success'); //re-populate table + app.messages.action(data.message, $form, error ? 'danger' : 'success'); //re-populate table $form.validateClear(); if(!error){ $form.trigger("reset"); @@ -750,7 +696,7 @@ function formAJAX(btn){ }else{ console.log('formAJAX res error', error, data) if(data && data.name === 'ObjectValidateError'){ - app.util.actionMessage('Please fix the form errors', $form, 'danger'); //re-populate table + app.messages.action('Please fix the form errors', $form, 'danger'); //re-populate table } if(data && data.keys){ console.log('form key errors', data.keys) diff --git a/nodejs/public/lib/js/val.js b/nodejs/public/lib/js/val.js deleted file mode 100644 index fd3da8f..0000000 --- a/nodejs/public/lib/js/val.js +++ /dev/null @@ -1,201 +0,0 @@ -( function( $ ) { - var settings = { - rule: { - eq: function(value, options){ - var compare = $('[name=' + options + ']').val(); - - if ( value != compare ) { - return "Miss-match"; - } - } - }, - }; - - $.fn.validate = function(event) { - // let thisSettings = $.extend(true, settings, settingsObj); - let hasErrors = false; - - if(this.is('[validate]')) return this.validateField(event); - - if(!this.attr('isValid')){ - console.log('adding reset event') - this.on('reset', function(){ - $(this).attr('isValid', false); - $(this).validateClear(); - }) - } - - this.find('[validate]').each(function(){ - if(!$(this).validateField()) hasErrors = true; - }); - - this.attr('isValid', !hasErrors); - - if(hasErrors && event) event.preventDefault(); - - return !hasErrors; - }; - - $.fn.validateClear = function(){ - $(this).find('input').each(function(){ - $(this).removeClass('is-invalid'); - $(this).removeClass('is-valid'); - }) - } - - $.fn.validateField = function(){ - var attr = this.attr('validate').split(':'); //array of params - var rule = attr[0]; - var options = attr[1]; - var value = this.val(); //link to input value - var message; - - if(this.prop('disabled')) return true; - - - //checks if field is required, and length - if(!isNaN(options) && value.length < options){ - message = `Must be ${options} characters`; - } - - //checks if empty to stop processing - if(!isNaN(options) && value.length === 0) { - }else if(rule in settings.rule){ - message = settings.rule[rule].apply(this, [value, options]); - } - - this.validateMessage(message) - return !message; - } - - $.fn.validateMessage = function(message){ - if(message && message !== true){ - this.closest('.form-group').find('b.invalid-feedback').html(message); - this.addClass('is-invalid'); - }else{ - this.removeClass('is-invalid'); - this.addClass('is-valid'); - } - return this; - }; - - jQuery.extend({ - validateSettings: function( settingsObj ) { - $.extend( true, settings, settingsObj ); - }, - - validateInit: function( ettingsObj ) { - $( '[action]' ).on( 'submit', function ( event, settingsObj ){ - $( this ).validate( settingsObj, event ); - }); - } - }); - -}( jQuery )); - -// Host / target validation, mirrored from the backend (utils/hostname_validate.js): -// a bare hostname or IPv4 address, no protocol / "/" / ":" / whitespace. The -// incoming host may be a wildcard ("*.example.com"); the target may not. -(function(){ - var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i; - // Either one bare label (Docker service names, /etc/hosts entries) or a - // dotted hostname with an alphabetic TLD. - var HOSTNAME = /^(?=.{1,253}$)(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)$/i; - var FORBIDDEN = /[\s/:]/; - - function isIPv4( value ) { - var parts = value.split( '.' ); - if ( parts.length !== 4 ) return false; - return parts.every( function( p ) { - return /^(0|[1-9]\d{0,2})$/.test( p ) && Number( p ) <= 255; - }); - } - - // Incoming-host pattern: labels may be normal, "*" (one fragment), or "**" - // (any number of fragments, incl. a bare "**" global catch-all). - function isHostPattern( value ) { - if ( value.length > 253 ) return false; - return value.split( '.' ).every( function( l ) { - return l === '*' || l === '**' || LABEL.test( l ); - }); - } - - function forbidden( value ) { - return FORBIDDEN.test( value ) || value.includes( '://' ); - } - - // Incoming host: IPv4 or a wildcard host pattern. - function checkHost( value ) { - if ( typeof value !== 'string' || value.length === 0 ) return "Required"; - if ( forbidden( value ) ) return 'No protocol, "/", or ":"'; - if ( isIPv4( value ) || isHostPattern( value ) ) return; - return "Enter a valid host or wildcard (*, **)"; - } - - // Downstream target: IPv4 or a strict hostname, no wildcard. - function checkTarget( value ) { - if ( typeof value !== 'string' || value.length === 0 ) return "Required"; - if ( forbidden( value ) ) return 'No protocol, "/", or ":"'; - if ( isIPv4( value ) || HOSTNAME.test( value ) ) return; - return "Enter a valid hostname or IP"; - } - - $.validateSettings({ - rule:{ - ip: function( value ) { - value = value.split( '.' ); - - if ( value.length != 4 ) { - return "Malformed IP"; - } - - $.each( value, function( key, value ) { - if( value > 255 || value < 0 ) { - return "Malformed IP"; - } - }); - }, - - // Incoming host name — hostname, IPv4, or wildcard pattern (*, **). - host: function( value ) { - return checkHost( value ); - }, - - // Downstream target — hostname or IPv4, no wildcard. - target: function( value ) { - return checkTarget( value ); - }, - - // Back-compat alias (no wildcard). - hostname: function( value ) { - return checkTarget( value ); - }, - - user: function( value ) { - var reg = /^[a-z0-9\_\-\@\.]{1,32}$/; - if ( reg.test( value ) === false ) { - return "Invalid"; - } - }, - - // Mirrors utils/password_policy.js: >= 8 chars, and either 12+ chars - // or at least 3 of {lowercase, uppercase, number, symbol}. - password: function( value ) { - if ( typeof value !== 'string' || value.length < 8 ) { - return "Password must be at least 8 characters"; - } - if ( value.length >= 12 ) return; - - var classes = 0; - if ( /[a-z]/.test( value ) ) classes++; - if ( /[A-Z]/.test( value ) ) classes++; - if ( /[0-9]/.test( value ) ) classes++; - if ( /[^A-Za-z0-9]/.test( value ) ) classes++; - - if ( classes < 3 ) { - return "Use 3 of: lowercase, uppercase, number, symbol (or 12+ chars)"; - } - } - } - }); -})(); \ No newline at end of file diff --git a/nodejs/routes/index.js b/nodejs/routes/index.js index f235313..9da6db4 100755 --- a/nodejs/routes/index.js +++ b/nodejs/routes/index.js @@ -28,7 +28,7 @@ const values ={ // every deploy and isn't cache-busted/fingerprinted. mountStaticModules(router, { root: path.join(__dirname, '..'), - deps: ['bootstrap', 'mustache', 'jquery', '@fortawesome', 'moment', '@popper', 'jq-repeat'], + deps: ['bootstrap', 'mustache', 'jquery', '@fortawesome', 'moment', '@popper', 'jq-repeat', '@simpleworkjs/frontend'], }); // Public health endpoint for container/orchestration healthchecks. diff --git a/nodejs/views/directory.ejs b/nodejs/views/directory.ejs index 465f9db..b8d5d4c 100644 --- a/nodejs/views/directory.ejs +++ b/nodejs/views/directory.ejs @@ -714,7 +714,7 @@ await loadResources(); if (!id && data.kind === 'oauth' && res.results && res.results._raw_secret) { - app.util.alert('OAuth Secret', 'Save this client secret, it will not be shown again:

' + res.results._raw_secret + '', 'success'); + app.modal.open({title: 'OAuth Secret', bodyHtml: 'Save this client secret, it will not be shown again:

' + res.results._raw_secret + ''}); } } catch (err) { console.error(err); @@ -729,7 +729,7 @@ try { const res = await app.api.post(`directory-admin/resources/${id}/rotate-secret`); - app.util.alert('Secret Rotated', 'Save this NEW client secret, it will not be shown again:

' + res.secret + '', 'success'); + app.modal.open({title: 'Secret Rotated', bodyHtml: 'Save this NEW client secret, it will not be shown again:

' + res.secret + ''}); } catch (err) { console.error(err); alert(err.message || 'Failed to rotate secret'); diff --git a/nodejs/views/executive.ejs b/nodejs/views/executive.ejs index 88fcf6f..efbe15b 100644 --- a/nodejs/views/executive.ejs +++ b/nodejs/views/executive.ejs @@ -129,7 +129,7 @@ // trying the form out — make it a deliberate, confirmed action. if (filterType === 'all' || filterType === 'all_active') { const label = filterType === 'all' ? 'ALL users (including inactive)' : 'all ACTIVE users'; - const confirmed = await app.util.actionConfirm(`Send this notification to ${label}?`, $compose, 'warning'); + const confirmed = await app.messages.confirm(`Send this notification to ${label}?`, $compose, 'warning'); if (!confirmed) return; } diff --git a/nodejs/views/groups.ejs b/nodejs/views/groups.ejs index c1b52e6..89a1163 100644 --- a/nodejs/views/groups.ejs +++ b/nodejs/views/groups.ejs @@ -35,7 +35,7 @@ async function addedUser(message, group, user, $form){ let data = await app.group.get(group); $.scope.groupCard.update('cn', group, processGroup(data.results)); - app.util.actionMessage(message, $("#group-card-"+group), 'success'); + app.messages.action(message, $("#group-card-"+group), 'success'); $('a[href="#'+$form.closest('.tab-pane').attr('id')+'"]').tab('show'); setTimeout(function(group){ $("body,html").animate({ scrollTop: $("#group-card-" + group).offset().top }, 0); @@ -73,44 +73,44 @@ async function removeMember(groupCN, uid, btn) { const $item = $(btn).closest('li'); $item.addClass('list-group-item-warning'); - const confirmed = await app.util.actionConfirm(`Remove "${uid}" from "${groupCN}"?`, $item, 'warning'); + const confirmed = await app.messages.confirm(`Remove "${uid}" from "${groupCN}"?`, $item, 'warning'); if (!confirmed) { $item.removeClass('list-group-item-warning'); return; } try { const data = await app.api.delete(`group/${groupCN}/${uid}`); const groupData = await app.group.get(groupCN); $.scope.groupCard.update('cn', groupCN, processGroup(groupData.results)); - app.util.actionMessage(data.message, $('#group-card-' + groupCN), 'success'); + app.messages.action(data.message, $('#group-card-' + groupCN), 'success'); } catch(e) { $item.removeClass('list-group-item-warning'); - app.util.actionMessage(e.message || 'Failed to remove member', $('#group-card-' + groupCN), 'danger'); + app.messages.action(e.message || 'Failed to remove member', $('#group-card-' + groupCN), 'danger'); } } async function removeOwner(groupCN, uid, btn) { const $item = $(btn).closest('li'); $item.addClass('list-group-item-warning'); - const confirmed = await app.util.actionConfirm(`Remove "${uid}" as owner of "${groupCN}"?`, $item, 'warning'); + const confirmed = await app.messages.confirm(`Remove "${uid}" as owner of "${groupCN}"?`, $item, 'warning'); if (!confirmed) { $item.removeClass('list-group-item-warning'); return; } try { const data = await app.api.delete(`group/owner/${groupCN}/${uid}`); const groupData = await app.group.get(groupCN); $.scope.groupCard.update('cn', groupCN, processGroup(groupData.results)); - app.util.actionMessage(data.message, $('#group-card-' + groupCN), 'success'); + app.messages.action(data.message, $('#group-card-' + groupCN), 'success'); } catch(e) { $item.removeClass('list-group-item-warning'); - app.util.actionMessage(e.message || 'Failed to remove owner', $('#group-card-' + groupCN), 'danger'); + app.messages.action(e.message || 'Failed to remove owner', $('#group-card-' + groupCN), 'danger'); } } async function deleteGroup(cn, btn) { const $card = $(btn).closest('.card'); - const confirmed = await app.util.actionConfirm(`Delete group "${cn}"?`, $card, 'danger'); + const confirmed = await app.messages.confirm(`Delete group "${cn}"?`, $card, 'danger'); if (!confirmed) return; try { await app.api.delete(`group/${cn}`); $.scope.groupCard.remove('cn', cn); } catch(e) { - app.util.actionMessage(e.message || 'Failed to delete group', $card, 'danger'); + app.messages.action(e.message || 'Failed to delete group', $card, 'danger'); } } diff --git a/nodejs/views/impersonate_modal.ejs b/nodejs/views/impersonate_modal.ejs index 90bc1d3..4d19803 100644 --- a/nodejs/views/impersonate_modal.ejs +++ b/nodejs/views/impersonate_modal.ejs @@ -79,7 +79,7 @@ function startImpersonate(uid){ $('#impersonateStopBtn').off('click').on('click', function(){ app.impersonate.revoke(data.uid, function(err){ $('#impersonateModal').modal('hide'); - if(!err) app.util.actionMessage('Impersonation ended for ' + data.uid, $('body'), 'success'); + if(!err) app.messages.action('Impersonation ended for ' + data.uid, $('body'), 'success'); }); }); diff --git a/nodejs/views/invite.ejs b/nodejs/views/invite.ejs index 8e3ef67..8e3aec3 100644 --- a/nodejs/views/invite.ejs +++ b/nodejs/views/invite.ejs @@ -1,7 +1,7 @@ <%- include('top') %> diff --git a/nodejs/views/oauth_authorize.ejs b/nodejs/views/oauth_authorize.ejs index 3f128cd..56c429d 100644 --- a/nodejs/views/oauth_authorize.ejs +++ b/nodejs/views/oauth_authorize.ejs @@ -39,7 +39,7 @@ app.api.post('oauth/authorize', oauthParams, function(error, data){ if(error){ $btn.prop('disabled', false).html(' Allow'); - app.util.actionMessage(data.message || 'Authorization failed.', $('#authorize-card'), 'danger'); + app.messages.action(data.message || 'Authorization failed.', $('#authorize-card'), 'danger'); return; } window.location.href = data.redirect_url; diff --git a/nodejs/views/profile.ejs b/nodejs/views/profile.ejs index a8b2325..db4de7c 100644 --- a/nodejs/views/profile.ejs +++ b/nodejs/views/profile.ejs @@ -27,10 +27,10 @@ async function removeFromGroup(cn, btn){ const $row = $(btn).closest('tr'); - const confirmed = await app.util.actionConfirm(`Remove ${currentUser.uid} from "${cn}"?`, $row, 'warning'); + const confirmed = await app.messages.confirm(`Remove ${currentUser.uid} from "${cn}"?`, $row, 'warning'); if (!confirmed) return; app.api.delete('group/' + encodeURIComponent(cn) + '/' + encodeURIComponent(currentUser.uid), function(error, data){ - if(error){ app.util.actionMessage((data && data.message) || 'Failed to remove from group', $row, 'danger'); return; } + if(error){ app.messages.action((data && data.message) || 'Failed to remove from group', $row, 'danger'); return; } $.scope.mygroups.remove('cn', cn); }); } @@ -43,7 +43,7 @@ for(const cn of cns){ await new Promise(function(resolve){ app.api.put('group/' + encodeURIComponent(cn) + '/' + encodeURIComponent(currentUser.uid), {}, function(error, data){ - if(error) app.util.actionMessage((data && data.message) || `Failed to add to "${cn}"`, $card, 'danger'); + if(error) app.messages.action((data && data.message) || `Failed to add to "${cn}"`, $card, 'danger'); resolve(); }); }); @@ -64,10 +64,10 @@ async function removePersonalGroupMember(memberUid, btn){ const $row = $(btn).closest('tr'); - const confirmed = await app.util.actionConfirm(`Remove ${memberUid} from ${currentUser.uid}'s group?`, $row, 'warning'); + const confirmed = await app.messages.confirm(`Remove ${memberUid} from ${currentUser.uid}'s group?`, $row, 'warning'); if (!confirmed) return; app.api.delete('user/' + encodeURIComponent(currentUser.uid) + '/group-member/' + encodeURIComponent(memberUid), function(error, data){ - if(error){ app.util.actionMessage((data && data.message) || 'Failed to remove from group', $row, 'danger'); return; } + if(error){ app.messages.action((data && data.message) || 'Failed to remove from group', $row, 'danger'); return; } $.scope.personalGroupMembers.remove('uid', memberUid); }); } @@ -80,7 +80,7 @@ for(const uid of uids){ await new Promise(function(resolve){ app.api.put('user/' + encodeURIComponent(currentUser.uid) + '/group-member/' + encodeURIComponent(uid), {}, function(error, data){ - if(error) app.util.actionMessage((data && data.message) || `Failed to add "${uid}"`, $card, 'danger'); + if(error) app.messages.action((data && data.message) || `Failed to add "${uid}"`, $card, 'danger'); resolve(); }); }); @@ -184,11 +184,11 @@ async function deleteUser(uid, btn){ const $card = $(btn).closest('.card'); - const confirmed = await app.util.actionConfirm(`Delete user "${uid}"?`, $card, 'warning'); + const confirmed = await app.messages.confirm(`Delete user "${uid}"?`, $card, 'warning'); if (!confirmed) return; app.api.delete('user/' + uid, function(error, data){ if (error) { - app.util.actionMessage(data.message || 'Failed to delete user', $card, 'danger'); + app.messages.action(data.message || 'Failed to delete user', $card, 'danger'); return; } window.location.href = '/users'; @@ -662,21 +662,21 @@ async function revokeToken(id, name, btn){ var $card = $(btn).closest('.card'); $card.addClass('table-warning'); - var confirmed = await app.util.actionConfirm('Revoke API token "' + name + '"? It stops working immediately.', $card, 'warning'); + var confirmed = await app.messages.confirm('Revoke API token "' + name + '"? It stops working immediately.', $card, 'warning'); $card.removeClass('table-warning'); if(!confirmed) return; app.apiToken.remove({id: id}, function(error, data){ - if(error){ app.util.actionMessage('Error: ' + data.message, $card, 'danger'); return; } + if(error){ app.messages.action('Error: ' + data.message, $card, 'danger'); return; } $.scope.apiTokenCard.remove('id', id); }); } async function rotateToken(id, name, btn){ var $card = $(btn).closest('.card'); - var confirmed = await app.util.actionConfirm('Rotate API token "' + name + '"? The old token stops working immediately.', $card, 'warning'); + var confirmed = await app.messages.confirm('Rotate API token "' + name + '"? The old token stops working immediately.', $card, 'warning'); if(!confirmed) return; app.apiToken.rotate({id: id}, function(error, data){ - if(error){ app.util.actionMessage('Error: ' + data.message, $card, 'danger'); return; } + if(error){ app.messages.action('Error: ' + data.message, $card, 'danger'); return; } showSecret(data.token); tableAJAX(); }); @@ -700,7 +700,7 @@ expires_in_days: $('#edit-expires_in_days').val(), }; app.apiToken.update(payload, function(error, data){ - if(error){ app.util.actionMessage((data && data.message) || 'Update failed.', $msg.parent(), 'danger'); return; } + if(error){ app.messages.action((data && data.message) || 'Update failed.', $msg.parent(), 'danger'); return; } editModal.hide(); tableAJAX(); }); diff --git a/nodejs/views/top.ejs b/nodejs/views/top.ejs index cc3c1cf..1fe6879 100755 --- a/nodejs/views/top.ejs +++ b/nodejs/views/top.ejs @@ -21,9 +21,11 @@ - + + + diff --git a/nodejs/views/users.ejs b/nodejs/views/users.ejs index d6df19f..0b5cc27 100755 --- a/nodejs/views/users.ejs +++ b/nodejs/views/users.ejs @@ -6,7 +6,7 @@ function renderUsers(){ app.user.list(function(error, data){ if(error){ - app.util.actionMessage(data.message, $('#tab-people'), 'danger'); + app.messages.action(data.message, $('#tab-people'), 'danger'); return; } $.scope.userRow.empty(); @@ -128,7 +128,7 @@ async function revokeInvite(tokenId, btn) { $thisRow = $(btn).closest('tr'); $thisRow.addClass('table-warning'); - let confirmation = await app.util.actionConfirm('Revoke selected invite token?', $thisRow, 'warning'); + let confirmation = await app.messages.confirm('Revoke selected invite token?', $thisRow, 'warning'); if(!confirmation){ $thisRow.removeClass('table-warning'); return; @@ -153,12 +153,12 @@ async function deleteUser(uid, btn){ const $row = $(btn).closest('tr'); $row.addClass('table-warning'); - const confirmed = await app.util.actionConfirm(`Delete user "${uid}"?`, $row, 'warning'); + const confirmed = await app.messages.confirm(`Delete user "${uid}"?`, $row, 'warning'); $row.removeClass('table-warning'); if (!confirmed) return; app.api.delete('user/' + uid, function(error, data){ if (error) { - app.util.actionMessage(data.message || 'Failed to delete user', $row, 'danger'); + app.messages.action(data.message || 'Failed to delete user', $row, 'danger'); return; } renderUsers();