[object Object]

← back to Credentials Agentabrams

group credential dashboard: action-needed on top, green connected section below; add Slack + new Shopify token as connected

fca2e631d5663e8be01406613c5fac1b04dcf3a7 · 2026-09-23 09:59:03 -0700 · Steve Abrams

Files touched

Diff

commit fca2e631d5663e8be01406613c5fac1b04dcf3a7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 09:59:03 2026 -0700

    group credential dashboard: action-needed on top, green connected section below; add Slack + new Shopify token as connected
---
 data/credentials.json | 30 ++++++++++++++++++++++++++++++
 public/index.html     | 39 ++++++++++++++++++++++++++++-----------
 2 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/data/credentials.json b/data/credentials.json
index dae4e93..9c49589 100644
--- a/data/credentials.json
+++ b/data/credentials.json
@@ -82,6 +82,36 @@
       "route_back": "(NEW token already routed 2026-09-23; only revoke remains)",
       "verify": "curl -s -H 'X-Shopify-Access-Token: shpat_…7d19' https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/shop.json — expect {\"errors\":\"[API] Invalid API key or access token\"} (HTTP 401)",
       "why": "Leaked token MUST be revoked, not just replaced. The fleet is safe on 6755. Uninstalling '81026 API' kills 7d19 cleanly with NO downtime."
+    },
+    {
+      "id": "shopify-admin-token-new",
+      "title": "SHOPIFY_ADMIN_TOKEN (new 6755)",
+      "priority": "P1",
+      "status": "CONNECTED",
+      "class": "console-mint",
+      "created_at": "2026-09-23T00:00:00-07:00",
+      "exposed": "Replacement full-access token shpat_…6755 minted in the STORE ADMIN custom app.",
+      "runbook": null,
+      "mint_url": null,
+      "console_steps": "Verified LIVE (HTTP 200, shop id 1541177456), routed to 6 local dests + alias, deployed to Kamatera /root/DW-Agents/.env + pm2 reload.",
+      "route_back": "node ~/Projects/secrets-manager/cli.js add SHOPIFY_ADMIN_TOKEN …6755 (done)",
+      "verify": "curl -s -H 'X-Shopify-Access-Token: shpat_…6755' https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/shop.json → HTTP 200",
+      "why": "The live working token the fleet now runs on. Connected 2026-09-23."
+    },
+    {
+      "id": "slack-bot-token",
+      "title": "SLACK_BOT_TOKEN",
+      "priority": "P2",
+      "status": "CONNECTED",
+      "class": "console-mint",
+      "created_at": "2026-09-22T00:00:00-07:00",
+      "exposed": "Old xoxb-…jpmsq was in tracked source (ecosystem.config.cjs, git history).",
+      "runbook": "~/.claude/yolo-queue/pending-approval/tk11786-rotate-exposed-secrets.md",
+      "mint_url": "https://api.slack.com/apps",
+      "console_steps": "Rotated by Steve → new token ends …OlqR, verified LIVE via auth.test (ok:true, team Designerwallcoverings, bot dw_reports_bot). Left: confirm old …jpmsq is revoked in Slack app admin + push new token to Kamatera prod.",
+      "route_back": "node ~/Projects/secrets-manager/cli.js add SLACK_BOT_TOKEN …OlqR (done)",
+      "verify": "curl -s -H 'Authorization: Bearer <token>' https://slack.com/api/auth.test → ok:true",
+      "why": "Rotated + verified live this session. Connected 2026-09-22."
     }
   ]
 }
diff --git a/public/index.html b/public/index.html
index 732cd26..62c4583 100644
--- a/public/index.html
+++ b/public/index.html
@@ -29,10 +29,17 @@
   .stat b{color:var(--ink)}
   #densWrap{display:none}
   main{padding:20px 24px}
+  .sec-hd{display:flex;align-items:center;gap:10px;margin:6px 0 12px;font-size:13px;font-weight:700;letter-spacing:.4px;text-transform:uppercase}
+  .sec-hd.need{color:#ff8b83}
+  .sec-hd.done{color:#6ee787;margin-top:26px;border-top:1px solid var(--line);padding-top:22px}
+  .sec-hd .n{font-size:11px;background:var(--panel2);border:1px solid var(--line);border-radius:20px;padding:2px 9px;color:var(--dim)}
+  .sec-empty{color:var(--dim);font-size:12.5px;font-style:italic;margin-bottom:8px}
+  .vb.done{opacity:.9}
+  .vb.done .li, .vb.done .card{border-left:3px solid var(--green)}
 
   /* ---- GRID (cards) ---- */
-  main.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:16px}
-  @media(max-width:900px){main.grid{grid-template-columns:1fr}}
+  .vb.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:16px}
+  @media(max-width:900px){.vb.grid{grid-template-columns:1fr}}
   .card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:10px;min-width:0}
   .card.p1{border-left:3px solid var(--red)}
   .card.resolved{opacity:.62}
@@ -54,7 +61,7 @@
   .why{font-size:12px;color:var(--dim);border-top:1px dashed var(--line);padding-top:8px}
 
   /* ---- LIST (compact single column) ---- */
-  main.list{display:flex;flex-direction:column;gap:10px}
+  .vb.list{display:flex;flex-direction:column;gap:10px}
   .li{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--line);border-radius:10px;padding:12px 14px}
   .li.p1{border-left-color:var(--red)}
   .li .hd{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px}
@@ -62,7 +69,7 @@
   .li .act b{color:var(--teal)}
 
   /* ---- TABLE ---- */
-  main.table{display:block;overflow-x:auto}
+  .vb.table{display:block;overflow-x:auto}
   table{border-collapse:collapse;width:100%;font-size:12.5px}
   th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--line);vertical-align:top}
   th{color:var(--dim);text-transform:uppercase;font-size:10px;letter-spacing:.5px;position:sticky;top:0;background:var(--panel)}
@@ -70,7 +77,7 @@
   td .action{color:var(--teal)}
 
   /* ---- KV (flat key/value) ---- */
-  main.kv{display:flex;flex-direction:column;gap:20px}
+  .vb.kv{display:flex;flex-direction:column;gap:20px}
   .kvblock{border-bottom:1px solid var(--line);padding-bottom:16px}
   .kvblock h3{margin:0 0 8px;font-size:14px}
   .kvrow{display:grid;grid-template-columns:120px 1fr;gap:10px;padding:3px 0;font-size:12.5px}
@@ -109,8 +116,9 @@ let ITEMS = [], VIEW = 'list';
 const PRI = { P1:0, P2:1, P3:2 };
 const fmtDate = iso => { try { return new Date(iso).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}); } catch(e){ return iso; } };
 const esc = s => String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
-const statusBadge = s => s==='RESOLVED'?'b-res':(s==='IN PROGRESS'?'b-prog':'b-out');
-const actionOf = i => i.status==='RESOLVED' ? 'done ✓' : (i.console_steps ? 'console action' : 'route key');
+const isDone = i => i.status==='RESOLVED' || i.status==='CONNECTED';
+const statusBadge = s => (s==='RESOLVED'||s==='CONNECTED')?'b-res':(s==='IN PROGRESS'?'b-prog':'b-out');
+const actionOf = i => isDone(i) ? 'connected ✓' : (i.console_steps ? 'console action' : 'route key');
 const linkify = t => { t=esc(t); return t.replace(/(https?:\/\/[^\s)]+)/g,'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>'); };
 
 function sorted(){
@@ -192,14 +200,23 @@ function renderKv(items){
   }).join('');
 }
 
+function viewBody(items, groupCls){
+  const inner = VIEW==='grid'?renderGrid(items):VIEW==='table'?renderTable(items):VIEW==='kv'?renderKv(items):renderList(items);
+  return `<div class="vb ${VIEW} ${groupCls||''}">${inner}</div>`;
+}
 function render(){
   const items = sorted();
-  const out = items.filter(i=>i.status!=='RESOLVED').length;
-  $('#stat').innerHTML = `<b>${out}</b> open · <b>${items.length}</b> shown`;
+  const need = items.filter(i=>!isDone(i));
+  const done = items.filter(isDone);
+  $('#stat').innerHTML = `<b>${need.length}</b> needed · <b>${done.length}</b> connected · <b>${items.length}</b> shown`;
   const main = $('#grid');
-  main.className = VIEW;
+  main.className = '';
   $('#densWrap').style.display = VIEW==='grid' ? '' : 'none';
-  main.innerHTML = VIEW==='grid'?renderGrid(items):VIEW==='table'?renderTable(items):VIEW==='kv'?renderKv(items):renderList(items);
+  main.innerHTML =
+    `<div class="sec-hd need">🔴 Action needed <span class="n">${need.length}</span></div>` +
+    (need.length ? viewBody(need) : `<div class="sec-empty">Nothing outstanding — all connected 🎉</div>`) +
+    `<div class="sec-hd done">🟢 Connected / done <span class="n">${done.length}</span></div>` +
+    (done.length ? viewBody(done,'done') : `<div class="sec-empty">Nothing connected yet.</div>`);
   document.querySelectorAll('#viewSeg button').forEach(b=>b.classList.toggle('on', b.dataset.v===VIEW));
 }
 

← 3bd0c68 add 4-way view switcher (list/grid/table/kv) + search to cre  ·  back to Credentials Agentabrams  ·  auto-data-snapshot: 2026-09-23T10:03:20 (1 data files) — dat 2c56e4b →