Compare commits

...

8 Commits

Author SHA1 Message Date
wmantly e249b4e168 Merge pull request #155 from theta42/bump-1.1.6
Bump version to 1.1.6
2026-07-16 19:04:34 -04:00
wmantly 34b1413c96 Bump version to 1.1.6; update CHANGELOG 2026-07-16 19:02:59 -04:00
wmantly eded87b6f9 Merge pull request #154 from theta42/fix-host-auth-mode-radios
hosts.ejs: fix Authentication tab radios not enforcing mutual exclusivity
2026-07-16 19:01:26 -04:00
wmantly a57f3f03f6 hosts.ejs: fix Authentication tab radios not enforcing mutual exclusivity
The three auth_mode radios (Off / Basic / SSO) had no shared [name]
attribute, so per the HTML spec each was its own independent group --
clicking one didn't uncheck the others, letting multiple options
appear selected at once despite the page's own text saying "basic
auth and SSO can't both be enabled."

Added name="auth_mode" to restore native browser radio-group
behavior. The original comment claimed the radios were deliberately
kept nameless to avoid polluting the submitted form data (formAJAX
serializes every [name] field in the form), but that reasoning
doesn't hold: model-redis's processKeys() rebuilds the saved object
strictly from the Host model's own _keyMap, so an unrecognized
auth_mode field is silently stripped before anything is ever
persisted -- confirmed directly with model-redis's own
object_validate.js. Updated the stale comment accordingly.
2026-07-16 18:59:50 -04:00
wmantly 5b08eecca9 Merge pull request #151 from theta42/bump-1.1.5
Bump version to 1.1.5
2026-07-16 18:35:41 -04:00
wmantly 7c0cb5eabd Bump version to 1.1.5; update CHANGELOG 2026-07-16 18:34:03 -04:00
wmantly 3f0d6fb438 Merge pull request #150 from theta42/jq-repeat-2.1.0
Update jq-repeat to 2.1.0; fix removed __setPut/__setTake API
2026-07-16 18:27:28 -04:00
wmantly 6cd3a5bc58 Update jq-repeat to 2.1.0; fix removed __setPut/__setTake API
jq-repeat 2.1.0 (release notes: https://github.com/wmantly/jq-repeat/releases/tag/v2.1.0)
brings real fixes (throttled-update race conditions, sorted-list
reverse() leaking elements, nested-scope isolation) and a few
behavior changes. Audited every usage in this repo against the
changelog before upgrading:

- push()/unshift() now return the new array length -- every call
  site in this repo is a bare statement, none consume the return
  value. No risk.
- update() is now trailing-edge throttled (~50ms) even on the first
  call, not just rapid subsequent ones -- no code in this repo reads
  DOM/item state immediately after calling update(), so no risk here
  (unlike sso-manager-node's companion PR, which needed a fix).
- jr-order-reverse and nested jq-repeat templates: not used anywhere
  in this repo.

Real breakage found and fixed: users.ejs/groups.ejs/permissions.ejs
called $.scope.X.__setPut(fn)/__setTake(fn) as setter METHODS -- that
API is gone in 2.1.0. Insert/remove hooks are now set via direct
property assignment ($.scope.X.__put = fn), per the current README.
Verified live (real dev server + Playwright): before the fix, all
three pages threw "__setTake is not a function" and the
insert/remove row animations were broken; after, zero errors and the
hooks fire correctly.
2026-07-16 18:26:02 -04:00
7 changed files with 39 additions and 24 deletions
+13 -1
View File
@@ -6,6 +6,16 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
## [Unreleased] ## [Unreleased]
## [1.1.6] - 2026-07-16
### Fixed
- Hosts admin UI's Authentication tab radios (Off / Basic / SSO) had no shared `name`, so clicking one didn't uncheck the others -- multiple options could appear selected at once. Added `name="auth_mode"` to restore standard exclusive radio-group behavior.
## [1.1.5] - 2026-07-16
### Fixed
- Bumped `jq-repeat` 2.0.1 -> 2.1.0. Fixed real breakage: `users.ejs`/`groups.ejs`/`permissions.ejs` called the removed `$.scope.X.__setPut(fn)`/`__setTake(fn)` setter-method API; insert/remove row hooks are now set via direct property assignment (`$.scope.X.__put = fn`), matching 2.1.0's API.
## [1.1.4] - 2026-07-16 ## [1.1.4] - 2026-07-16
### Added ### Added
@@ -39,7 +49,9 @@ First tagged release. Establishes the `vX.Y.Z` tag convention that the in-app up
- Standalone backup script (`ops/backup.sh`) for deployments not using theta-env's orchestrator — snapshots Redis and `./config`, with retention. - Standalone backup script (`ops/backup.sh`) for deployments not using theta-env's orchestrator — snapshots Redis and `./config`, with retention.
- Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates. - Admin-only in-app banner that checks GitHub releases every 24h and surfaces available updates.
[Unreleased]: https://github.com/theta42/proxy/compare/v1.1.4...HEAD [Unreleased]: https://github.com/theta42/proxy/compare/v1.1.6...HEAD
[1.1.6]: https://github.com/theta42/proxy/compare/v1.1.5...v1.1.6
[1.1.5]: https://github.com/theta42/proxy/compare/v1.1.4...v1.1.5
[1.1.4]: https://github.com/theta42/proxy/compare/v1.1.3...v1.1.4 [1.1.4]: https://github.com/theta42/proxy/compare/v1.1.3...v1.1.4
[1.1.3]: https://github.com/theta42/proxy/compare/v1.1.2...v1.1.3 [1.1.3]: https://github.com/theta42/proxy/compare/v1.1.2...v1.1.3
[1.1.2]: https://github.com/theta42/proxy/compare/v1.1.1...v1.1.2 [1.1.2]: https://github.com/theta42/proxy/compare/v1.1.1...v1.1.2
+6 -6
View File
@@ -1,12 +1,12 @@
{ {
"name": "proxy-api", "name": "proxy-api",
"version": "1.1.4", "version": "1.1.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "proxy-api", "name": "proxy-api",
"version": "1.1.4", "version": "1.1.6",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0", "@fortawesome/fontawesome-free": "^7.3.0",
@@ -21,7 +21,7 @@
"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",
"jq-repeat": "^2.0.1", "jq-repeat": "^2.1.0",
"jquery": "^4.0.0", "jquery": "^4.0.0",
"ldapts": "^8.1.8", "ldapts": "^8.1.8",
"linux-sys-user": "^1.2.0", "linux-sys-user": "^1.2.0",
@@ -1375,9 +1375,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/jq-repeat": { "node_modules/jq-repeat": {
"version": "2.0.1", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/jq-repeat/-/jq-repeat-2.0.1.tgz", "resolved": "https://registry.npmjs.org/jq-repeat/-/jq-repeat-2.1.0.tgz",
"integrity": "sha512-ATI25tKQG3uHW8f8XPqBe85JsH4PNGHA/YLy1KgMVeYDoUSf9cqGNBum+4A+Pg1WKh9PA6bYyWfYNsgktwIbSg==", "integrity": "sha512-e1OmSWeBEHEtyOhNVysx0bnT5wd6HlZ37JZgPcGPmACJ0K9bXDPq0xOwrM1slQMSTw7FOSNDX+MD6VwvPeeZyQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "proxy-api", "name": "proxy-api",
"version": "1.1.4", "version": "1.1.6",
"private": true, "private": true,
"author": [ "author": [
{ {
@@ -32,7 +32,7 @@
"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",
"jq-repeat": "^2.0.1", "jq-repeat": "^2.1.0",
"jquery": "^4.0.0", "jquery": "^4.0.0",
"ldapts": "^8.1.8", "ldapts": "^8.1.8",
"linux-sys-user": "^1.2.0", "linux-sys-user": "^1.2.0",
+2 -2
View File
@@ -60,10 +60,10 @@
loadUserSuggestions(); loadUserSuggestions();
$.scope.LocalGroup.__setTake(function($el){ $.scope.LocalGroup.__take = function($el){
$el.addClass('bg-danger'); $el.addClass('bg-danger');
$el.fadeOut(600, function(){ $el.remove(); }); $el.fadeOut(600, function(){ $el.remove(); });
}); };
app.subscribe(/^model:LocalGroup:create/, function(data){ app.subscribe(/^model:LocalGroup:create/, function(data){
$.scope.LocalGroup.remove(data.name); $.scope.LocalGroup.remove(data.name);
+10 -7
View File
@@ -115,10 +115,13 @@
// attach users to). // attach users to).
let hostFormCurrentHost = null; let hostFormCurrentHost = null;
// The auth_mode radios aren't real form fields (no [name]); this keeps the // The auth_mode radios share a name so the browser enforces mutual
// two hidden basicauth_enabled/sso_enabled inputs — the ones actually // exclusivity, but auth_mode itself isn't in Host's _keyMap -- the model
// submitted — in sync so only one can ever be true, and shows/hides the // layer strips unrecognized fields on save (see model-redis's
// matching field group. // processKeys), so it's never actually persisted. This keeps the two
// hidden basicauth_enabled/sso_enabled inputs -- the real, submitted
// fields -- in sync with whichever radio is selected, and shows/hides
// the matching field group.
function hostAuthModeChanged(mode){ function hostAuthModeChanged(mode){
$('#basicauth_enabled-hidden').val(mode === 'basic' ? 'true' : 'false'); $('#basicauth_enabled-hidden').val(mode === 'basic' ? 'true' : 'false');
$('#sso_enabled-hidden').val(mode === 'sso' ? 'true' : 'false'); $('#sso_enabled-hidden').val(mode === 'sso' ? 'true' : 'false');
@@ -713,15 +716,15 @@
<div class="form-group"> <div class="form-group">
<div class="radio"><label> <div class="radio"><label>
<input type="radio" id="auth_mode-none" value="none" checked onchange="hostAuthModeChanged('none')"> <input type="radio" name="auth_mode" id="auth_mode-none" value="none" checked onchange="hostAuthModeChanged('none')">
Off (public) Off (public)
</label></div> </label></div>
<div class="radio"><label> <div class="radio"><label>
<input type="radio" id="auth_mode-basic" value="basic" onchange="hostAuthModeChanged('basic')"> <input type="radio" name="auth_mode" id="auth_mode-basic" value="basic" onchange="hostAuthModeChanged('basic')">
Basic authentication Basic authentication
</label></div> </label></div>
<div class="radio"><label> <div class="radio"><label>
<input type="radio" id="auth_mode-sso" value="sso" onchange="hostAuthModeChanged('sso')"> <input type="radio" name="auth_mode" id="auth_mode-sso" value="sso" onchange="hostAuthModeChanged('sso')">
Single sign-on (SSO) Single sign-on (SSO)
</label></div> </label></div>
</div> </div>
+2 -2
View File
@@ -57,10 +57,10 @@
loadSubjectSuggestions(); loadSubjectSuggestions();
$.scope.Permission.__setTake(function($el, item, list){ $.scope.Permission.__take = function($el, item, list){
$el.addClass('bg-danger'); $el.addClass('bg-danger');
$el.fadeOut(600, function(){ $el.remove(); }); $el.fadeOut(600, function(){ $el.remove(); });
}); };
// Live updates (model:Permission:*), so adds/removes reflect for everyone. // Live updates (model:Permission:*), so adds/removes reflect for everyone.
app.subscribe(/^model:Permission:create/, function(data){ app.subscribe(/^model:Permission:create/, function(data){
+4 -4
View File
@@ -26,12 +26,12 @@
for(let user of data.results){ for(let user of data.results){
$.scope.users.push(user); $.scope.users.push(user);
} }
$.scope.users.__setPut(function($el, item, list){ $.scope.users.__put = function($el, item, list){
$el.addClass('bg-success'); $el.addClass('bg-success');
$el.fadeIn(3000, function(){ $el.fadeIn(3000, function(){
$el.removeClass('bg-success'); $el.removeClass('bg-success');
}); });
}) };
}); });
} }
@@ -45,12 +45,12 @@
$(document).ready(function(){ $(document).ready(function(){
populateUsers(); //populate the table populateUsers(); //populate the table
$.scope.users.__setTake(function($el, item, list){ $.scope.users.__take = function($el, item, list){
$el.addClass('bg-danger'); $el.addClass('bg-danger');
$el.fadeOut(1000, function(){ $el.fadeOut(1000, function(){
$el.remove() $el.remove()
}); });
}); };
}); });
</script> </script>