release(v2.0.3): fix Directory tab managed-filter bug and site-status 500 (#186)
- GET /api/directory-admin/resources let every kind:'host' resource through regardless of promotion status, so "Auto-promote to Directory" unchecked on a discovery plugin never kept unpromoted devices out of the Directory tab. - GET /api/directory-admin/site-status queried the nonexistent Resource.subType column instead of metadata.subType, throwing SequelizeDatabaseError. - Added a "Show ignored" toggle to Discovered Inventory (off by default). - Untracked nodejs/config/inventory.sqlite -- the app's default runtime DB, not a fixture, committed by mistake across 13 prior releases. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -91,6 +91,12 @@ secrets.js
|
|||||||
# they must never be committed. The empty *.example templates ARE tracked.
|
# they must never be committed. The empty *.example templates ARE tracked.
|
||||||
config/*-secrets.js
|
config/*-secrets.js
|
||||||
|
|
||||||
|
# Default sqlite ORM storage (nodejs/models/index.js falls back to this path
|
||||||
|
# when no external DB is configured via conf.orm) -- live runtime data, not a
|
||||||
|
# fixture. Was committed by mistake across many prior releases. NB: this is
|
||||||
|
# nodejs/config/, distinct from the root ./config/ secrets dir above.
|
||||||
|
nodejs/config/*.sqlite
|
||||||
|
|
||||||
# Jekyll build artifact (GitHub Pages builds remotely; ignore locally)
|
# Jekyll build artifact (GitHub Pages builds remotely; ignore locally)
|
||||||
docs/_site
|
docs/_site
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
# v2.0.3 - 2026-08-09
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Directory tab showed unpromoted discoveries.** `GET /api/directory-admin/resources` unconditionally admitted every `kind: 'host'` resource, and every discovery plugin (UniFi, Proxmox, nmap) creates its finds as `kind: 'host'` — so unchecking "Auto-promote to Directory" on a plugin never actually kept undiscovered/unpromoted devices out of the Directory tab, only out of the LDAP-group auto-provisioning. Now only `site` resources are unconditionally shown; anything else that discovery ever touched requires `metadata.managed === true` (set by promotion, an agent, or merging into an already-managed resource).
|
||||||
|
- **`GET /api/directory-admin/site-status` 500'd.** Queried `Resource.list({ where: { subType: 'wireguard' } })`, but `subType` only ever lives in `metadata.subType` (every driver/discovery plugin reads it that way) — never a top-level DB column, so SQLite raised `no such column: Resource.subType`. Filters in JS over `metadata.subType` now.
|
||||||
|
- **Discovered Inventory had no way to review ignored devices.** Added a "Show ignored" toggle (off by default) to the tab, so `metadata.ignored === true` rows stay hidden from routine triage but remain reachable.
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
- **Untracked `nodejs/config/inventory.sqlite`.** It's the app's default runtime DB (`nodejs/models/index.js` falls back to this path when no external DB is configured), not a fixture — it had been committed by mistake across 13 prior releases, churning on every local run. Removed from tracking and gitignored.
|
||||||
|
|
||||||
# v2.0.2 - 2026-08-09
|
# v2.0.2 - 2026-08-09
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
Binary file not shown.
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-theta-directory",
|
"name": "t42-theta-directory",
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "t42-theta-directory",
|
"name": "t42-theta-directory",
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^7.3.0",
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-theta-directory",
|
"name": "t42-theta-directory",
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"description": "A very simple LDAP management and SSO system",
|
"description": "A very simple LDAP management and SSO system",
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -199,10 +199,17 @@ router.get('/resources', async (req, res, next) => {
|
|||||||
try {
|
try {
|
||||||
let resources = await Resource.list();
|
let resources = await Resource.list();
|
||||||
resources = resources.filter(r => {
|
resources = resources.filter(r => {
|
||||||
if (r.kind === 'host' || r.kind === 'site') return true;
|
// Sites are structural containers, not discovery output -- always shown.
|
||||||
const isAuto = r.metadata?.discovery_sources?.length > 0 && !r.metadata.discovery_sources.includes('manual');
|
if (r.kind === 'site') return true;
|
||||||
|
// A resource discovery ever touched only belongs in the Directory once
|
||||||
|
// it's explicitly managed (created by an agent, promoted by a user, or
|
||||||
|
// merged into an already-managed resource). Until then it's pending
|
||||||
|
// review in the Discovered Inventory tab. Anything discovery never
|
||||||
|
// touched (created directly through this admin UI) has no
|
||||||
|
// discovery_sources and is always shown.
|
||||||
|
const isDiscovered = r.metadata?.discovery_sources?.length > 0;
|
||||||
const isManaged = r.metadata?.managed === true;
|
const isManaged = r.metadata?.managed === true;
|
||||||
return !isAuto || isManaged;
|
return !isDiscovered || isManaged;
|
||||||
});
|
});
|
||||||
// Even admins never receive secret metadata (e.g. client_secret_hash) over
|
// Even admins never receive secret metadata (e.g. client_secret_hash) over
|
||||||
// the wire; projectResources strips it unconditionally.
|
// the wire; projectResources strips it unconditionally.
|
||||||
@@ -885,7 +892,8 @@ let localSiteConfig = {
|
|||||||
router.get('/site-status', async (req, res, next) => {
|
router.get('/site-status', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const sites = await Resource.list({ where: { kind: 'site' } });
|
const sites = await Resource.list({ where: { kind: 'site' } });
|
||||||
const gateResources = await Resource.list({ where: { subType: 'wireguard' } });
|
const allResources = await Resource.list();
|
||||||
|
const gateResources = allResources.filter(r => r.metadata && r.metadata.subType === 'wireguard');
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
|
|||||||
@@ -136,6 +136,10 @@
|
|||||||
<i class="fa-solid fa-network-wired"></i> Network Discovery Dashboard
|
<i class="fa-solid fa-network-wired"></i> Network Discovery Dashboard
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
<div class="form-check form-switch mb-0">
|
||||||
|
<input class="form-check-input" type="checkbox" id="discovery-show-ignored" onchange="renderDiscoveryTable()">
|
||||||
|
<label class="form-check-label small" for="discovery-show-ignored">Show ignored</label>
|
||||||
|
</div>
|
||||||
<input type="text" id="discovery-search-filter" class="form-control form-control-sm shadow-sm" placeholder="Search resources..." onkeyup="renderDiscoveryTable()" style="width: 250px;">
|
<input type="text" id="discovery-search-filter" class="form-control form-control-sm shadow-sm" placeholder="Search resources..." onkeyup="renderDiscoveryTable()" style="width: 250px;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2258,11 +2262,14 @@
|
|||||||
|
|
||||||
function renderDiscoveryTable() {
|
function renderDiscoveryTable() {
|
||||||
const search = $('#discovery-search-filter').val().toLowerCase();
|
const search = $('#discovery-search-filter').val().toLowerCase();
|
||||||
|
const showIgnored = $('#discovery-show-ignored').is(':checked');
|
||||||
const filtered = allDiscoveryResources.filter(r => {
|
const filtered = allDiscoveryResources.filter(r => {
|
||||||
if (search && !r.name.toLowerCase().includes(search) && !r.slug.toLowerCase().includes(search)) return false;
|
if (search && !r.name.toLowerCase().includes(search) && !r.slug.toLowerCase().includes(search)) return false;
|
||||||
// Directory contains managed items; Discovered Inventory only shows unmanaged/pending items awaiting promotion
|
// Directory contains managed items; Discovered Inventory only shows unmanaged/pending items awaiting promotion
|
||||||
const isExplicitManaged = r.metadata && (r.metadata.managed === true || r.metadata.managed === 'true');
|
const isExplicitManaged = r.metadata && (r.metadata.managed === true || r.metadata.managed === 'true');
|
||||||
if (isExplicitManaged || r.kind === 'site' || r.kind === 'service') return false;
|
if (isExplicitManaged || r.kind === 'site' || r.kind === 'service') return false;
|
||||||
|
const isIgnored = r.metadata && (r.metadata.ignored === true || r.metadata.ignored === 'true');
|
||||||
|
if (isIgnored && !showIgnored) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user