Compare commits

..

6 Commits

Author SHA1 Message Date
wmantly a44d7ef7ab Merge pull request #199 from theta42/release-v1.12.1
Release v1.12.1
2026-08-01 11:12:56 -04:00
wmantly 144efdb5dd chore(release): v1.12.1 2026-08-01 11:11:37 -04:00
dependabot[bot] b54a738524 Bump brace-expansion and ejs in /nodejs (#179)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) to 5.0.9 and updates ancestor dependency [ejs](https://github.com/mde/ejs). These dependencies need to be updated together.


Updates `brace-expansion` from 5.0.7 to 5.0.9
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.7...v5.0.9)

Updates `ejs` from 3.1.10 to 6.0.1
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/RELEASE_NOTES_v5.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.10...v6.0.1)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.8
  dependency-type: indirect
- dependency-name: ejs
  dependency-version: 6.0.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Mantly <wmantly@gmail.com>
2026-08-01 11:11:21 -04:00
dependabot[bot] 8bf963f48b Bump body-parser from 2.2.2 to 2.3.0 in /nodejs (#175)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 2.2.2 to 2.3.0.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Mantly <wmantly@gmail.com>
2026-08-01 11:02:25 -04:00
wmantly 30835baaeb Merge pull request #197 from theta42/release-v1.12.0
chore(release): v1.12.0
2026-08-01 01:21:50 -04:00
wmantly 2319ac3a0e chore(release): v1.12.0 2026-08-01 01:20:22 -04:00
7 changed files with 127 additions and 292 deletions
+11
View File
@@ -6,6 +6,17 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
## [Unreleased] ## [Unreleased]
## [1.12.1] - 2026-08-01
### Changed
- Bumped `body-parser` 2.2.2 → 2.3.0 (Dependabot #175).
- Bumped `ejs` and `brace-expansion` (Dependabot #179, security maintenance).
## [1.12.0] - 2026-08-01
### Fixed
- Changed UNIX socket permission in `unix_socket_json.js` to `666` so OpenResty Nginx workers running as `nobody` can resolve targets properly.
## [1.9.0] - 2026-07-30 ## [1.9.0] - 2026-07-30
### Added ### Added
-86
View File
@@ -1,86 +0,0 @@
'use strict';
// Example secrets configuration for the theta42/proxy.
//
// The proxy is an OIDC client of an SSO Manager (or any OIDC provider) AND a
// direct LDAP client for user lookups. This file supplies that wiring.
//
// Docker / unified stack: place at ./config/proxy-secrets.js and bind-mount
// ./config at /config (see docker-compose.yml); docker-entrypoint.sh points the
// CONF_SECRETS env var at it so @simpleworkjs/conf reads it. No app_* env
// should be passed — app_* env beats this file in @simpleworkjs/conf, so the
// file is authoritative only if the matching app_* env is absent.
//
// Bare-metal: ops/install.sh seeds this file at /etc/proxy/secrets.js on first
// run (with placeholders for the values it can't guess) and points the
// systemd unit's CONF_SECRETS env var at it. Fill in your values, then
// `sudo systemctl restart proxy`. Values here override conf/base.js and win
// over <environment>.js.
//
// Only the keys the app reads are listed below. The `stack` key is read by the
// theta-env orchestrator (setup.sh) and ignored by the app.
module.exports = {
name: 'Dynamic Proxy', // shown in the UI
logo: '/static/img/theta42.svg', // nav image; point at your own file under public/ to white-label
// OpenID Connect — point at your SSO Manager. Issuer + authorization/
// endSession are browser-facing URLs; token/userinfo can be the internal
// URL if the SSO is on the same docker network (avoids a TLS hairpin).
oidc: {
enabled: true,
issuer: 'https://sso.example.com',
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
tokenEndpoint: 'http://sso-manager:3001/oauth/token',
userinfoEndpoint: 'http://sso-manager:3001/oauth/userinfo',
endSessionEndpoint: 'https://sso.example.com/oauth/logout',
clientId: '391136c8-9631-47c4-aac6-d6b760b7a9ae', // registered on the SSO
clientSecret: 'b29cce9c-de0c-4acc-b76a-494168f0381d', // from the SSO client record
redirectUri: 'https://proxy.example.com/api/auth/oidc/callback',
scopes: ['openid', 'profile', 'email', 'groups'],
groupsClaim: 'groups',
usernameClaim: 'preferred_username',
},
// Direct LDAP user lookups. ldaps:// + rejectUnauthorized:false for a
// self-signed cert (the SSO's default), or set tlsOptions.ca to a CA path
// for strict verification. bindPassword MUST match the
// serviceAccountPass in the SSO's sso-secrets.js (the proxy binds as that
// service account).
ldap: {
url: 'ldaps://sso-manager:636',
bindDN: 'cn=ldapclient,ou=people,dc=example,dc=com',
bindPassword: 'proxy-service-pass',
searchBase: 'ou=people,dc=example,dc=com',
userFilter: '(objectClass=inetOrgPerson)',
userNameAttribute: 'uid',
tlsOptions: {
rejectUnauthorized: false, // true + ca for a CA-signed cert
},
},
// Authorization. adminUsers is the local anti-lockout admin (matches
// auth.adminUsers in conf/base.js). adminGroups: SSO/LDAP groups whose
// members are always global admins.
auth: {
adminGroups: [],
adminUsers: ['proxyadmin'],
groupRoleMap: {},
// Optional: the local anti-lockout admin's initial password, used
// ONLY the first time that account is created. Leave unset and it
// defaults to the username itself ("proxyadmin2") — fine for a quick
// local test, but change it (or set this) before exposing the proxy
// publicly. Once the account exists, this key is never read again;
// change the password via the app itself (or delete the Redis user
// to force it to be re-bootstrapped with a new value here).
localAdminPass: 'proxyadmin-test-pass',
},
// ── Orchestrator-only (ignored by the app) ───────────────────────────────
// Read by the theta-env setup.sh (e.g. to seed the OAuth client). Omit for
// bare-metal use.
stack: {
ssoHost: 'sso.example.com', // public SSO hostname
proxyHost: 'proxy.example.com', // public proxy hostname
},
};
+55 -93
View File
@@ -1,12 +1,12 @@
{ {
"name": "proxy-api", "name": "proxy-api",
"version": "1.7.0", "version": "1.9.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "proxy-api", "name": "proxy-api",
"version": "1.7.0", "version": "1.9.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0", "@fortawesome/fontawesome-free": "^7.3.0",
@@ -21,7 +21,7 @@
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"bootstrap": "^5.3.8", "bootstrap": "^5.3.8",
"compression": "^1.8.1", "compression": "^1.8.1",
"ejs": "^3.1.10", "ejs": "^6.0.1",
"express": "^5.2.1", "express": "^5.2.1",
"express-rate-limit": "^8.5.2", "express-rate-limit": "^8.5.2",
"extend": "^3.0.2", "extend": "^3.0.2",
@@ -446,12 +446,6 @@
"node": ">=12.0.0" "node": ">=12.0.0"
} }
}, },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -517,20 +511,20 @@
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "2.2.2", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "^3.1.2", "bytes": "^3.1.2",
"content-type": "^1.0.5", "content-type": "^2.0.0",
"debug": "^4.4.3", "debug": "^4.4.3",
"http-errors": "^2.0.0", "http-errors": "^2.0.1",
"iconv-lite": "^0.7.0", "iconv-lite": "^0.7.2",
"on-finished": "^2.4.1", "on-finished": "^2.4.1",
"qs": "^6.14.1", "qs": "^6.15.2",
"raw-body": "^3.0.1", "raw-body": "^3.0.2",
"type-is": "^2.0.1" "type-is": "^2.1.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -540,6 +534,19 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/body-parser/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "5.3.8", "version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
@@ -560,16 +567,16 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "5.0.7", "version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^4.0.2" "balanced-match": "^4.0.2"
}, },
"engines": { "engines": {
"node": "18 || 20 || >=22" "node": "20 || >=22"
} }
}, },
"node_modules/braces": { "node_modules/braces": {
@@ -848,18 +855,15 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/ejs": { "node_modules/ejs": {
"version": "3.1.10", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-6.0.1.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "integrity": "sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
"bin": { "bin": {
"ejs": "bin/cli.js" "ejs": "bin/cli.js"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.12.18"
} }
}, },
"node_modules/encodeurl": { "node_modules/encodeurl": {
@@ -1071,42 +1075,6 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/filelist": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
"integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/filelist/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/filelist/node_modules/brace-expansion": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1483,23 +1451,6 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
"license": "Apache-2.0",
"dependencies": {
"async": "^3.2.6",
"filelist": "^1.0.4",
"picocolors": "^1.1.1"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/jq-repeat": { "node_modules/jq-repeat": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/jq-repeat/-/jq-repeat-2.2.0.tgz", "resolved": "https://registry.npmjs.org/jq-repeat/-/jq-repeat-2.2.0.tgz",
@@ -1806,12 +1757,6 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
@@ -2313,17 +2258,34 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/type-is": { "node_modules/type-is": {
"version": "2.0.1", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"content-type": "^1.0.5", "content-type": "^2.0.0",
"media-typer": "^1.1.0", "media-typer": "^1.1.0",
"mime-types": "^3.0.0" "mime-types": "^3.0.0"
}, },
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/type-is/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/undefsafe": { "node_modules/undefsafe": {
+1 -1
View File
@@ -31,7 +31,7 @@
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"bootstrap": "^5.3.8", "bootstrap": "^5.3.8",
"compression": "^1.8.1", "compression": "^1.8.1",
"ejs": "^3.1.10", "ejs": "^6.0.1",
"express": "^5.2.1", "express": "^5.2.1",
"express-rate-limit": "^8.5.2", "express-rate-limit": "^8.5.2",
"extend": "^3.0.2", "extend": "^3.0.2",
+3 -15
View File
@@ -33,14 +33,6 @@
}); });
} }
function refreshGroups(){
$.scope.LocalGroup.empty();
app.group.list(function(error, data){
if(error) return app.messages.action(error, $.scope.LocalGroup.$this, 'danger');
for(let g of data.results) $.scope.LocalGroup.push(g);
});
}
function removeMember(group, username){ function removeMember(group, username){
app.group.removeMember(group, username, function(error, data){ app.group.removeMember(group, username, function(error, data){
if(error) return app.messages.action(error, $.scope.LocalGroup.$this, 'danger'); if(error) return app.messages.action(error, $.scope.LocalGroup.$this, 'danger');
@@ -76,17 +68,13 @@
app.subscribe(/^model:LocalGroup:create/, function(data){ app.subscribe(/^model:LocalGroup:create/, function(data){
$.scope.LocalGroup.remove(data.name); $.scope.LocalGroup.remove(data.name);
$.scope.LocalGroup.unshift(data); $.scope.LocalGroup.unshift(data);
// Also refresh to ensure the full list is up to date
setTimeout(refreshGroups, 500);
});
app.subscribe(/^model:LocalGroup:remove/, function(data, topic){
$.scope.LocalGroup.remove(topic.split(':')[3]);
// Also refresh to ensure the full list is up to date
setTimeout(refreshGroups, 500);
}); });
app.subscribe(/^model:LocalGroup:update/, function(data, topic){ app.subscribe(/^model:LocalGroup:update/, function(data, topic){
$.scope.LocalGroup.update(topic.split(':')[3], data); $.scope.LocalGroup.update(topic.split(':')[3], data);
}); });
app.subscribe(/^model:LocalGroup:remove/, function(data, topic){
$.scope.LocalGroup.remove(topic.split(':')[3]);
});
}); });
</script> </script>
+26 -32
View File
@@ -9,9 +9,12 @@
font-weight: bold; font-weight: bold;
margin-bottom: 1px; margin-bottom: 1px;
} }
.card-title{ font-weight: bold; } .card-title{
.member-pill{ cursor: default; } font-weight: bold;
.member-pill i{ cursor: pointer; } }
.field-hint{
font-size: .8rem;
}
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
@@ -139,37 +142,28 @@
<div class="card-header actionMessage" style="display:none"></div> <div class="card-header actionMessage" style="display:none"></div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="row row-cols-1 row-cols-lg-2 g-3" id="permission-cards">
<table class="table table-striped mb-0"> <div class="col" jq-repeat="Permission" jq-repeat-index="id" id="permission-row-{{id}}" style="display:none">
<thead> <div class="card shadow-sm h-100">
<tr> <div class="card-body">
<th>Subject</th> <h6 class="mb-2">
<th>Scope</th> <span class="badge text-bg-secondary">{{ subjectType }}</span>
<th>Domain</th> {{ subject }}
<th>Role</th> </h6>
<th class="text-end">Actions</th> <dl class="row mb-2 small">
</tr> <dt class="col-4">Scope</dt><dd class="col-8">{{ scope }}</dd>
</thead> <dt class="col-4">Domain</dt><dd class="col-8">{{ domain }}</dd>
<tbody id="permission-cards"> <dt class="col-4">Role</dt><dd class="col-8">{{ role }}</dd>
<tr jq-repeat="Permission" jq-repeat-index="id" id="permission-row-{{id}}" style="display:none"> </dl>
<td> <button type="button" class="btn btn-sm btn-danger" onclick="removePermission('{{id}}')">
<span class="badge text-bg-secondary">{{ subjectType }}</span> <i class="fa-solid fa-trash"></i>
{{ subject }} Delete
</td> </button>
<td>{{ scope }}</td> </div>
<td>{{ domain }}</td> </div>
<td>{{ role }}</td>
<td class="text-end">
<button type="button" class="btn btn-sm btn-danger" onclick="removePermission('{{id}}')">
<i class="fa-solid fa-trash"></i>
Delete
</button>
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
+31 -65
View File
@@ -58,38 +58,18 @@
+ '<div class="form-group">' + '<div class="form-group">'
+ '<label class="control-label">User-name</label>' + '<label class="control-label">User-name</label>'
+ '<input type="text" class="form-control" name="username" placeholder="Letter, numbers, -, _, . and @ only" validate="user:3" />' + '<input type="text" class="form-control" name="username" placeholder="Letter, numbers, -, _, . and @ only" validate="user:3" />'
+ '<div class="invalid-feedback d-none" data-field-error="username"></div>'
+ '</div>' + '</div>'
+ '<div class="form-group">' + '<div class="form-group">'
+ '<label class="control-label">Password</label>' + '<label class="control-label">Password</label>'
+ '<input type="password" class="form-control" name="password" placeholder="8+ chars; mix upper/lower/number/symbol (or 12+)" validate="password"/>' + '<input type="password" class="form-control" name="password" placeholder="8+ chars; mix upper/lower/number/symbol (or 12+)" validate="password"/>'
+ '<div class="invalid-feedback d-none" data-field-error="password"></div>'
+ '</div>' + '</div>'
+ '<div class="form-group">' + '<div class="form-group">'
+ '<label class="control-label">Again</label>' + '<label class="control-label">Again</label>'
+ '<input type="password" class="form-control" name="passwordMatch" placeholder="Retype password" validate="eq:password"/>' + '<input type="password" class="form-control" name="passwordMatch" placeholder="Retype password" validate="eq:password"/>'
+ '<div class="invalid-feedback d-none" data-field-error="passwordMatch"></div>'
+ '</div>' + '</div>'
+ '<hr />' + '<hr />'
+ '<button type="submit" class="btn btn-info">Add</button>' + '<button type="submit" class="btn btn-info">Add</button>'
+ '</form>', + '</form>',
onValidationError: function(errors){
// Clear all field errors first
$('[data-field-error]').addClass('d-none').text('');
$('.form-control.is-invalid').removeClass('is-invalid');
// Show action message at top
let errorMsg = 'Please fix the following errors:';
for(let field in errors){
let $field = $('[name="' + field + '"]');
$field.addClass('is-invalid');
$field.siblings('[data-field-error]').removeClass('d-none').text(errors[field]);
errorMsg += ' ' + field + ': ' + errors[field] + ';';
}
$('.modal-body .actionMessage').first().length ?
$('.modal-body .actionMessage').first().text(errorMsg).removeClass('d-none').addClass('alert alert-danger') :
app.messages.action(errorMsg, $('.modal-body'), 'danger');
}
}); });
} }
@@ -128,53 +108,39 @@
<div class="card-header actionMessage" style="display:none"></div> <div class="card-header actionMessage" style="display:none"></div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="row row-cols-1 row-cols-lg-2 g-3" id="user-cards">
<table class="table table-striped mb-0"> <div class="col" jq-repeat="users" jq-repeat-index="username" id="user-row-{{username}}" style="display:none">
<thead> <div class="card shadow-sm h-100">
<tr> <div class="card-body">
<th>Username</th> <h6 class="d-flex align-items-center mb-2">
<th>Auth Type</th> <i class="fa-solid fa-user me-2"></i>
<th>Password</th> {{ username }}
<th>Actions</th> {{#isExternal}}
</tr> <span class="badge text-bg-secondary ms-2" title="Provisioned via SSO login; no local password to manage here.">
</thead> <i class="fa-solid fa-cloud"></i> External (SSO)
<tbody id="user-cards"> </span>
<tr jq-repeat="users" jq-repeat-index="username" id="user-row-{{username}}" style="display:none"> {{/isExternal}}
<td> </h6>
<strong>{{ username }}</strong>
</td> {{^isExternal}}
<td> <form class="input-group input-group-sm mb-2" action="user/password/{{ username }}" method="put" onsubmit="formAJAX(this)">
{{#isExternal}} <input type="password" name="password" class="form-control" placeholder="Change password" aria-label="Update password">
<span class="badge text-bg-secondary" title="Provisioned via SSO login; no local password to manage here."> <button class="btn btn-warning" type="submit">Change</button>
<i class="fa-solid fa-cloud"></i> External (SSO) </form>
</span> {{/isExternal}}
{{/isExternal}} {{#isExternal}}
{{^isExternal}} <p class="text-muted small mb-2">Authenticates via SSO -- cannot be edited here.</p>
<span class="badge text-bg-primary">Local</span> {{/isExternal}}
{{/isExternal}}
</td> <button type="button" class="btn btn-sm btn-danger" onclick="removeUser('{{username}}')">
<td> <i class="fa-solid fa-user-slash"></i>
{{^isExternal}} Delete
<form class="input-group input-group-sm" style="max-width: 350px;" action="user/password/{{ username }}" method="put" onsubmit="formAJAX(this)"> </button>
<input type="password" name="password" class="form-control" placeholder="Change password" aria-label="Update password"> </div>
<button class="btn btn-warning" type="submit">Change</button> </div>
</form>
{{/isExternal}}
{{#isExternal}}
<span class="text-muted">Authenticates via SSO — cannot be edited here.</span>
{{/isExternal}}
</td>
<td>
<button type="button" class="btn btn-sm btn-danger" onclick="removeUser('{{username}}')">
<i class="fa-solid fa-user-slash"></i>
Delete
</button>
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>
</div> </div>