aimeat 1.4.8 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/locales/en.json +60 -4
  2. package/dist/locales/fi.json +60 -4
  3. package/dist/public/css/views/profile.css +37 -0
  4. package/dist/public/js/services/admin.js +5 -0
  5. package/dist/public/js/services/boards.js +6 -3
  6. package/dist/public/js/services/memory.js +10 -0
  7. package/dist/public/views/admin/federation-tab.js +93 -7
  8. package/dist/public/views/admin/ghii-tab.js +2 -0
  9. package/dist/public/views/admin.js +1 -1
  10. package/dist/public/views/profile/access-tab.js +160 -58
  11. package/dist/public/views/profile/agents-tab.js +13 -1
  12. package/dist/public/views/profile/boards-tab.js +12 -0
  13. package/dist/public/views/profile/data-wallet-tab.js +29 -2
  14. package/dist/public/views/profile/knowledge-tab.js +64 -2
  15. package/dist/public/views/profile/landing-page.js +9 -6
  16. package/dist/public/views/profile/memory-tab.js +92 -4
  17. package/dist/src/auth/jwt.d.ts +6 -0
  18. package/dist/src/auth/jwt.d.ts.map +1 -1
  19. package/dist/src/auth/jwt.js +6 -0
  20. package/dist/src/auth/jwt.js.map +1 -1
  21. package/dist/src/auth/middleware.d.ts +7 -0
  22. package/dist/src/auth/middleware.d.ts.map +1 -1
  23. package/dist/src/auth/middleware.js +20 -0
  24. package/dist/src/auth/middleware.js.map +1 -1
  25. package/dist/src/middleware/federation-proxy.d.ts +35 -0
  26. package/dist/src/middleware/federation-proxy.d.ts.map +1 -0
  27. package/dist/src/middleware/federation-proxy.js +92 -0
  28. package/dist/src/middleware/federation-proxy.js.map +1 -0
  29. package/dist/src/routes/actions.d.ts.map +1 -1
  30. package/dist/src/routes/actions.js +7 -2
  31. package/dist/src/routes/actions.js.map +1 -1
  32. package/dist/src/routes/admin-agents.d.ts.map +1 -1
  33. package/dist/src/routes/admin-agents.js +1 -0
  34. package/dist/src/routes/admin-agents.js.map +1 -1
  35. package/dist/src/routes/admin-features.d.ts.map +1 -1
  36. package/dist/src/routes/admin-features.js +2 -0
  37. package/dist/src/routes/admin-features.js.map +1 -1
  38. package/dist/src/routes/agents.d.ts.map +1 -1
  39. package/dist/src/routes/agents.js +36 -3
  40. package/dist/src/routes/agents.js.map +1 -1
  41. package/dist/src/routes/boards.d.ts.map +1 -1
  42. package/dist/src/routes/boards.js +12 -5
  43. package/dist/src/routes/boards.js.map +1 -1
  44. package/dist/src/routes/federation-auth.d.ts +15 -0
  45. package/dist/src/routes/federation-auth.d.ts.map +1 -0
  46. package/dist/src/routes/federation-auth.js +182 -0
  47. package/dist/src/routes/federation-auth.js.map +1 -0
  48. package/dist/src/routes/federation-genesis.d.ts +2 -1
  49. package/dist/src/routes/federation-genesis.d.ts.map +1 -1
  50. package/dist/src/routes/federation-genesis.js +58 -2
  51. package/dist/src/routes/federation-genesis.js.map +1 -1
  52. package/dist/src/routes/federation-peer.d.ts.map +1 -1
  53. package/dist/src/routes/federation-peer.js +79 -2
  54. package/dist/src/routes/federation-peer.js.map +1 -1
  55. package/dist/src/routes/federation-sync.d.ts.map +1 -1
  56. package/dist/src/routes/federation-sync.js +14 -1
  57. package/dist/src/routes/federation-sync.js.map +1 -1
  58. package/dist/src/routes/federation.d.ts +2 -1
  59. package/dist/src/routes/federation.d.ts.map +1 -1
  60. package/dist/src/routes/federation.js +4 -2
  61. package/dist/src/routes/federation.js.map +1 -1
  62. package/dist/src/routes/ghii.d.ts +2 -1
  63. package/dist/src/routes/ghii.d.ts.map +1 -1
  64. package/dist/src/routes/ghii.js +88 -4
  65. package/dist/src/routes/ghii.js.map +1 -1
  66. package/dist/src/routes/libs.js +42 -10
  67. package/dist/src/routes/libs.js.map +1 -1
  68. package/dist/src/routes/memory.d.ts.map +1 -1
  69. package/dist/src/routes/memory.js +162 -0
  70. package/dist/src/routes/memory.js.map +1 -1
  71. package/dist/src/server-bootstrap/routes-loader.d.ts +2 -0
  72. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  73. package/dist/src/server-bootstrap/routes-loader.js +7 -4
  74. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  75. package/dist/src/server-bootstrap/service-init.d.ts +2 -0
  76. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  77. package/dist/src/server-bootstrap/service-init.js +8 -1
  78. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  79. package/dist/src/server.d.ts.map +1 -1
  80. package/dist/src/server.js +1 -0
  81. package/dist/src/server.js.map +1 -1
  82. package/dist/src/services/federation.d.ts +6 -6
  83. package/dist/src/services/federation.d.ts.map +1 -1
  84. package/dist/src/services/federation.js +53 -1
  85. package/dist/src/services/federation.js.map +1 -1
  86. package/dist/src/services/memory-replication.d.ts.map +1 -1
  87. package/dist/src/services/memory-replication.js +3 -0
  88. package/dist/src/services/memory-replication.js.map +1 -1
  89. package/dist/src/storage/interface.d.ts +9 -1
  90. package/dist/src/storage/interface.d.ts.map +1 -1
  91. package/dist/src/storage/providers/mongodb/index.d.ts +2 -1
  92. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  93. package/dist/src/storage/providers/mongodb/index.js +31 -10
  94. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  95. package/dist/src/storage/providers/sqlite/index.d.ts +2 -1
  96. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  97. package/dist/src/storage/providers/sqlite/index.js +47 -19
  98. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  99. package/dist/src/storage/providers/sqlite/repos/action.d.ts.map +1 -1
  100. package/dist/src/storage/providers/sqlite/repos/action.js +5 -4
  101. package/dist/src/storage/providers/sqlite/repos/action.js.map +1 -1
  102. package/dist/src/storage/providers/sqlite/repos/agent.d.ts.map +1 -1
  103. package/dist/src/storage/providers/sqlite/repos/agent.js +5 -4
  104. package/dist/src/storage/providers/sqlite/repos/agent.js.map +1 -1
  105. package/dist/src/storage/providers/sqlite/repos/board.d.ts.map +1 -1
  106. package/dist/src/storage/providers/sqlite/repos/board.js +3 -2
  107. package/dist/src/storage/providers/sqlite/repos/board.js.map +1 -1
  108. package/dist/src/storage/providers/sqlite/repos/storage-file.d.ts.map +1 -1
  109. package/dist/src/storage/providers/sqlite/repos/storage-file.js +4 -2
  110. package/dist/src/storage/providers/sqlite/repos/storage-file.js.map +1 -1
  111. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  112. package/dist/src/storage/providers/sqlite/schema.js +10 -0
  113. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  114. package/dist/src/storage/repositories/board.repository.d.ts +1 -1
  115. package/dist/src/storage/repositories/board.repository.d.ts.map +1 -1
  116. package/dist/src/storage/repositories/federation.repository.d.ts +1 -0
  117. package/dist/src/storage/repositories/federation.repository.d.ts.map +1 -1
  118. package/dist/src/utils/service-summary.d.ts +65 -0
  119. package/dist/src/utils/service-summary.d.ts.map +1 -0
  120. package/dist/src/utils/service-summary.js +100 -0
  121. package/dist/src/utils/service-summary.js.map +1 -0
  122. package/package.json +3 -1
  123. package/prisma/schema.prisma +14 -6
@@ -213,6 +213,10 @@
213
213
  "close": "Close",
214
214
  "error": "Error",
215
215
  "unknownError": "Unknown error",
216
+ "federate": "Federate",
217
+ "federateTooltip": "Make visible across the federation network",
218
+ "federated": "Federated",
219
+ "notFederated": "Local only",
216
220
  "stats": {
217
221
  "agents": "Agents",
218
222
  "morsels": "Morsels",
@@ -535,7 +539,17 @@
535
539
  "noAgents": "No agents registered yet. When you connect an AI assistant via MCP, its memory entries will appear here.",
536
540
  "setupMcp": "Set up MCP →",
537
541
  "agent": "Agent",
538
- "defaultAgent": "Default agent"
542
+ "defaultAgent": "Default agent",
543
+ "pullFromHome": "Copy from home",
544
+ "pushToHome": "Save to home",
545
+ "pullSuccess": "Entry copied from home node",
546
+ "pushSuccess": "Entry saved to home node",
547
+ "federatedSession": "Federated session. Some entries may be on your home node.",
548
+ "syncedToFederation": "Synced",
549
+ "shareToFederation": "Share",
550
+ "stopSharing": "Unshare",
551
+ "shareSuccess": "Shared to federation",
552
+ "unshareSuccess": "Removed from federation"
539
553
  },
540
554
  "files": {
541
555
  "title": "Stored Files",
@@ -724,7 +738,9 @@
724
738
  "error": "Could not load federation info.",
725
739
  "peers": "Connected Peers",
726
740
  "online": "Online",
727
- "offline": "Offline"
741
+ "offline": "Offline",
742
+ "statusConnected": "Connected to {count} nodes",
743
+ "statusStandalone": "Standalone"
728
744
  },
729
745
  "access": {
730
746
  "title": "Access Codes & Tokens",
@@ -744,7 +760,17 @@
744
760
  "keepSafe": "Keep this safe — it's your owner recovery key.",
745
761
  "keyCopied": "Key copied to clipboard!",
746
762
  "mcpEndpoint": "MCP Endpoint",
747
- "mcpDesc": "Use this URL to connect MCP-compatible AI platforms to your node."
763
+ "mcpDesc": "Use this URL to connect MCP-compatible AI platforms to your node.",
764
+ "fedTitle": "Federation Access",
765
+ "fedDesc": "Control which federation nodes can verify your identity for remote login.",
766
+ "fedAddNode": "Add Node",
767
+ "fedNodeId": "Node ID",
768
+ "fedAllowAll": "Allow all federation nodes",
769
+ "fedAllowAllWarn": "Any node can verify your identity",
770
+ "fedNoConsents": "No federation login access granted.",
771
+ "fedRemove": "Remove",
772
+ "fedGranted": "Auth access granted",
773
+ "fedRevoked": "Auth access revoked"
748
774
  },
749
775
  "nodes": {
750
776
  "title": "Personal Nodes",
@@ -2315,6 +2341,7 @@
2315
2341
  "created": "Created",
2316
2342
  "trust": "Trust",
2317
2343
  "lastSeen": "Last Seen",
2344
+ "lastLogin": "Last Login",
2318
2345
  "morselSupply": "Morsel Supply",
2319
2346
  "todayActivity": "Today's Activity",
2320
2347
  "morselPolicy": "Morsel Policy",
@@ -3181,6 +3208,25 @@
3181
3208
  "fedDepeeringExplain": "These peers are in the grace period before permanent removal. In-flight work can still complete.",
3182
3209
  "fedDepeeringGrace": "Grace ends",
3183
3210
 
3211
+ "fedShareCatalogue": "Share Catalogue",
3212
+ "fedReplicateMemory": "Replicate Memory",
3213
+ "fedAllowRouting": "Allow Routing",
3214
+ "fedPeerMode": "Peer Mode",
3215
+ "fedPeerModeFederation": "Federation",
3216
+ "fedPeerModePrivate": "Private P2P",
3217
+ "fedPolicyUpdated": "Peer policy updated",
3218
+ "fedPolicySectionTitle": "Policy",
3219
+
3220
+ "fedNetworkDirectoryTitle": "Network Directory",
3221
+ "fedNetworkDirectoryDesc": "Services and data available across the federation network.",
3222
+ "fedNetworkDirType": "Type",
3223
+ "fedNetworkDirName": "Name",
3224
+ "fedNetworkDirNode": "Node",
3225
+ "fedNetworkDirCategory": "Category",
3226
+ "fedNetworkDirPrice": "Price",
3227
+ "fedNetworkDirEmpty": "No federated services discovered yet. Services appear when peers share their catalogue.",
3228
+ "fedNetworkDirSearch": "Search services...",
3229
+
3184
3230
  "genesisExplain": "Genesis peers are the founding nodes of a federation cluster. The genesis node is the first node that creates the federation, and it manages peer approvals, key exchange, and cluster policies.",
3185
3231
  "genesisHelpTitle": "What are genesis peers?",
3186
3232
  "genesisHelpDetail": "When you create a new federation, your node becomes the genesis (origin) node. Other nodes request to join, and you approve or reject them here. Genesis peers have special privileges: they can set cluster-wide policies, approve new members, and suspend misbehaving nodes. Approved peers can exchange data according to federation rules.",
@@ -3911,7 +3957,12 @@
3911
3957
  "revoke": "Revoke",
3912
3958
  "never": "Never",
3913
3959
  "revoking": "Revoking...",
3914
- "revoked": "Consent revoked"
3960
+ "revoked": "Consent revoked",
3961
+ "scopeFederation": "Federation",
3962
+ "scopeAuth": "Login Access",
3963
+ "filterAll": "All",
3964
+ "filterFederation": "Federation",
3965
+ "filterAuth": "Login Access"
3915
3966
  },
3916
3967
  "audit": {
3917
3968
  "title": "Audit Report",
@@ -4280,6 +4331,11 @@
4280
4331
  "owner": "My Agents",
4281
4332
  "shared": "My Agents"
4282
4333
  },
4334
+ "federate": "Federate",
4335
+ "federated": "Federated",
4336
+ "notFederated": "Local only",
4337
+ "federateSuccess": "Package shared to federation",
4338
+ "unfederateSuccess": "Package removed from federation",
4283
4339
  "operator": {
4284
4340
  "tabLabel": "Knowledge",
4285
4341
  "review": "Review",
@@ -213,6 +213,10 @@
213
213
  "close": "Sulje",
214
214
  "error": "Virhe",
215
215
  "unknownError": "Tuntematon virhe",
216
+ "federate": "Federoi",
217
+ "federateTooltip": "Tee näkyväksi federaatioverkossa",
218
+ "federated": "Federoitu",
219
+ "notFederated": "Vain paikallinen",
216
220
  "stats": {
217
221
  "agents": "Agentit",
218
222
  "morsels": "Muruset",
@@ -535,7 +539,17 @@
535
539
  "noAgents": "Ei rekisteröityjä agentteja. Kun yhdistät AI-assistentin MCP:llä, sen muistimerkinnät näkyvät täällä.",
536
540
  "setupMcp": "Aseta MCP →",
537
541
  "agent": "Agentti",
538
- "defaultAgent": "Oletusagentti"
542
+ "defaultAgent": "Oletusagentti",
543
+ "pullFromHome": "Kopioi kotisolmulta",
544
+ "pushToHome": "Tallenna kotisolmulle",
545
+ "pullSuccess": "Merkinta kopioitu kotisolmulta",
546
+ "pushSuccess": "Merkinta tallennettu kotisolmulle",
547
+ "federatedSession": "Federoitu istunto. Osa merkinnoista voi olla kotisolmullasi.",
548
+ "syncedToFederation": "Synkronoitu",
549
+ "shareToFederation": "Jaa",
550
+ "stopSharing": "Lopeta jakaminen",
551
+ "shareSuccess": "Jaettu federaatioon",
552
+ "unshareSuccess": "Poistettu federaatiosta"
539
553
  },
540
554
  "files": {
541
555
  "title": "Tallennetut tiedostot",
@@ -724,7 +738,9 @@
724
738
  "error": "Federaatiotietojen lataus epäonnistui.",
725
739
  "peers": "Yhdistetyt vertaiset",
726
740
  "online": "Online",
727
- "offline": "Offline"
741
+ "offline": "Offline",
742
+ "statusConnected": "Yhdistetty {count} solmuun",
743
+ "statusStandalone": "Itsenäinen"
728
744
  },
729
745
  "access": {
730
746
  "title": "Pääsykoodit & tokenit",
@@ -744,7 +760,17 @@
744
760
  "keepSafe": "Pidä tämä turvassa — se on palautusavaimesi.",
745
761
  "keyCopied": "Avain kopioitu leikepöydälle!",
746
762
  "mcpEndpoint": "MCP-päätepiste",
747
- "mcpDesc": "Käytä tätä URL:iä yhdistääksesi MCP-yhteensopivat AI-alustat solmuusi."
763
+ "mcpDesc": "Käytä tätä URL:iä yhdistääksesi MCP-yhteensopivat AI-alustat solmuusi.",
764
+ "fedTitle": "Federaatioyhteys",
765
+ "fedDesc": "Hallinnoi mitkä solmut voivat todentaa henkilöllisyytesi etäkirjautumista varten.",
766
+ "fedAddNode": "Lisää solmu",
767
+ "fedNodeId": "Solmun ID",
768
+ "fedAllowAll": "Salli kaikki federaatiosolmut",
769
+ "fedAllowAllWarn": "Mikä tahansa solmu voi todentaa henkilöllisyytesi",
770
+ "fedNoConsents": "Federaatiokirjautumista ei myönnetty.",
771
+ "fedRemove": "Poista",
772
+ "fedGranted": "Todennusoikeus myönnetty",
773
+ "fedRevoked": "Todennusoikeus peruutettu"
748
774
  },
749
775
  "nodes": {
750
776
  "title": "Henkilökohtaiset solmut",
@@ -2315,6 +2341,7 @@
2315
2341
  "created": "Luotu",
2316
2342
  "trust": "Luottamus",
2317
2343
  "lastSeen": "Viimeksi nähty",
2344
+ "lastLogin": "Viimeisin kirjautuminen",
2318
2345
  "morselSupply": "Murusten tarjonta",
2319
2346
  "todayActivity": "Päivän aktiviteetti",
2320
2347
  "morselPolicy": "Muruspolitiikka",
@@ -3181,6 +3208,25 @@
3181
3208
  "fedDepeeringExplain": "Nämä vertaiset ovat armonaikajaksolla ennen lopullista poistamista. Keskeneräiset työt voivat vielä valmistua.",
3182
3209
  "fedDepeeringGrace": "Armonaika päättyy",
3183
3210
 
3211
+ "fedShareCatalogue": "Jaa katalogi",
3212
+ "fedReplicateMemory": "Replikoi muisti",
3213
+ "fedAllowRouting": "Salli reititys",
3214
+ "fedPeerMode": "Vertaistila",
3215
+ "fedPeerModeFederation": "Federaatio",
3216
+ "fedPeerModePrivate": "Yksityinen P2P",
3217
+ "fedPolicyUpdated": "Vertaispolitiikka päivitetty",
3218
+ "fedPolicySectionTitle": "Politiikka",
3219
+
3220
+ "fedNetworkDirectoryTitle": "Verkkohakemisto",
3221
+ "fedNetworkDirectoryDesc": "Palvelut ja data saatavilla federaatioverkossa.",
3222
+ "fedNetworkDirType": "Tyyppi",
3223
+ "fedNetworkDirName": "Nimi",
3224
+ "fedNetworkDirNode": "Solmu",
3225
+ "fedNetworkDirCategory": "Kategoria",
3226
+ "fedNetworkDirPrice": "Hinta",
3227
+ "fedNetworkDirEmpty": "Federoituja palveluita ei vielä löydetty. Palvelut näkyvät kun vertaissolmut jakavat kataloginsa.",
3228
+ "fedNetworkDirSearch": "Hae palveluita...",
3229
+
3184
3230
  "genesisExplain": "Genesis-vertaiset ovat federaatioklusterin perustajasolmuja. Genesis-solmu on ensimmäinen solmu, joka luo federaation, ja se hallinnoi vertaishyväksyntöjä, avainten vaihtoa ja klusterikäytäntöjä.",
3185
3231
  "genesisHelpTitle": "Mitä genesis-vertaiset ovat?",
3186
3232
  "genesisHelpDetail": "Kun luot uuden federaation, solmustasi tulee genesis-solmu (alkuperä). Muut solmut pyytävät liittymistä, ja hyväksyt tai hylkäät ne täällä. Genesis-vertaisilla on erityisoikeuksia: ne voivat asettaa klusterinlaajuisia käytäntöjä, hyväksyä uusia jäseniä ja keskeyttää väärin käyttäytyvät solmut. Hyväksytyt vertaiset voivat vaihtaa dataa federaatiosääntöjen mukaisesti.",
@@ -3911,7 +3957,12 @@
3911
3957
  "revoke": "Peru",
3912
3958
  "never": "Ei koskaan",
3913
3959
  "revoking": "Perutaan...",
3914
- "revoked": "Suostumus peruttu"
3960
+ "revoked": "Suostumus peruttu",
3961
+ "scopeFederation": "Federaatio",
3962
+ "scopeAuth": "Kirjautumisoikeus",
3963
+ "filterAll": "Kaikki",
3964
+ "filterFederation": "Federaatio",
3965
+ "filterAuth": "Kirjautumisoikeus"
3915
3966
  },
3916
3967
  "audit": {
3917
3968
  "title": "Auditointiraportti",
@@ -4280,6 +4331,11 @@
4280
4331
  "owner": "Omat agentit",
4281
4332
  "shared": "Omat agentit"
4282
4333
  },
4334
+ "federate": "Federoi",
4335
+ "federated": "Federoitu",
4336
+ "notFederated": "Vain paikallinen",
4337
+ "federateSuccess": "Paketti jaettu federaatioon",
4338
+ "unfederateSuccess": "Paketti poistettu federaatiosta",
4283
4339
  "operator": {
4284
4340
  "tabLabel": "Tietopankki",
4285
4341
  "review": "Arvioi",
@@ -1240,6 +1240,7 @@
1240
1240
  .pf .access-warn-text { font-size: .75rem; color: var(--warn); margin-top: .5rem; }
1241
1241
  .pf .access-mcp-url { font-family: monospace; font-size: .85rem; color: var(--love3); }
1242
1242
  .pf .access-mcp-desc { font-size: .75rem; color: var(--muted); margin-top: .3rem; }
1243
+ .pf .access-fed-add { margin-top: .5rem; }
1243
1244
 
1244
1245
  /* ── Wallet tab ── */
1245
1246
  .pf .wl-val-success { color: var(--success, #22c55e); }
@@ -1742,6 +1743,42 @@
1742
1743
  color: var(--danger, #ef4444);
1743
1744
  }
1744
1745
 
1746
+ /* --- Federation toggle (knowledge-tab) --- */
1747
+ .pf .kpkg-fed-toggle {
1748
+ cursor: pointer;
1749
+ border: none;
1750
+ font-size: .7rem;
1751
+ padding: .15rem .5rem;
1752
+ border-radius: 6px;
1753
+ font-weight: 700;
1754
+ letter-spacing: .03em;
1755
+ }
1756
+ .pf .kpkg-fed-toggle:disabled { opacity: .5; cursor: wait; }
1757
+
1758
+ /* --- Data Wallet scope filter buttons --- */
1759
+ .pf .dw-scope-filters {
1760
+ display: flex;
1761
+ gap: 4px;
1762
+ flex-shrink: 0;
1763
+ }
1764
+ .pf .dw-filter-row {
1765
+ flex-wrap: wrap;
1766
+ gap: 6px;
1767
+ }
1768
+
1769
+ /* --- Memory federation badge --- */
1770
+ .pf .pf-fed-badge {
1771
+ font-size: .65rem;
1772
+ padding: .1rem .4rem;
1773
+ margin-left: 4px;
1774
+ }
1775
+
1776
+ /* --- Federation standalone variant --- */
1777
+ .pf .pf-federation-standalone {
1778
+ color: var(--text-dim, #6B7280);
1779
+ background: var(--bg-dim, #f5f5f5);
1780
+ }
1781
+
1745
1782
  .pf-gen-test-component.pf-gen-test-skipped .pf-gen-test-badge {
1746
1783
  background: var(--bg-dim);
1747
1784
  color: var(--text-muted);
@@ -51,11 +51,13 @@ export const getFederationPeers = () => apiGet('/v1/federation/peers');
51
51
  export const getFederationDirectory = () => apiGet('/v1/federation/directory');
52
52
  export const approvePeeringRequest = (id) => apiPut(`/v1/admin/peering/requests/${encodeURIComponent(id)}`, { decision: 'approve' });
53
53
  export const rejectPeeringRequest = (id) => apiPut(`/v1/admin/peering/requests/${encodeURIComponent(id)}`, { decision: 'reject' });
54
+ export const deletePeeringRequest = (id) => apiDelete(`/v1/admin/peering/requests/${encodeURIComponent(id)}`);
54
55
  export const activatePeer = (nodeId) => apiPost('/v1/federation/peer/activate', { peer_node_id: nodeId });
55
56
  export const addPeerDirect = (nodeId, url, publicKey) => apiPost('/v1/federation/peers', { node_id: nodeId, url, public_key: publicKey || undefined });
56
57
  export const removePeer = (nodeId) => apiDelete(`/v1/federation/peers/${encodeURIComponent(nodeId)}`);
57
58
  export const removePeerEmergency = (nodeId) => apiDelete(`/v1/federation/peers/${encodeURIComponent(nodeId)}?emergency=true`);
58
59
  export const testFederationNode = (targetUrl) => apiPost('/v1/federation/test', { target_url: targetUrl });
60
+ export const updatePeerPolicy = (nodeId, policy) => apiPut(`/v1/federation/peers/${encodeURIComponent(nodeId)}`, policy);
59
61
  export const joinGenesisNetwork = (genesisUrl, role) => apiPost('/v1/admin/federation/join', { genesis_url: genesisUrl, role: role || 'contributor' });
60
62
 
61
63
  // ── Hooks ──
@@ -129,6 +131,9 @@ export const deleteMsm = (name) => apiDelete(`/v1/admin/msm/${encodeURI
129
131
  export const getMsmTemplates = () => apiGet('/v1/msm/templates');
130
132
  export const getMsmTemplate = (type) => fetch(`/v1/msm/templates/${encodeURIComponent(type)}`, { headers: { 'Accept': 'application/x-yaml' } }).then(r => r.text());
131
133
 
134
+ // ── Network Directory ──
135
+ export const getNetworkDirectory = (keyword) => apiGet(`/v1/federation/cross-catalogue${keyword ? '?keyword=' + encodeURIComponent(keyword) + '&source=network' : '?source=network'}`);
136
+
132
137
  // ── Genesis ──
133
138
  export const getGenesisPeers = () => apiGet('/v1/admin/genesis-peers');
134
139
  export const approveGenesisPeer = (id) => apiPost(`/v1/admin/genesis-peers/${encodeURIComponent(id)}/approve`);
@@ -29,11 +29,14 @@ export async function deleteBoard(boardId) {
29
29
  return apiDelete(`/v1/boards/${encodeURIComponent(boardId)}`);
30
30
  }
31
31
 
32
- /** Update board visibility. */
33
- export async function updateBoardVisibility(boardId, visibility) {
32
+ /** Update board visibility and/or federate flag. */
33
+ export async function updateBoardVisibility(boardId, visibility, federate) {
34
+ const body = {};
35
+ if (visibility !== undefined) body.visibility = visibility;
36
+ if (federate !== undefined) body.federate = federate;
34
37
  return api(`/v1/boards/${encodeURIComponent(boardId)}/visibility`, {
35
38
  method: 'PATCH',
36
- body: JSON.stringify({ visibility }),
39
+ body: JSON.stringify(body),
37
40
  });
38
41
  }
39
42
 
@@ -110,3 +110,13 @@ export async function updateFileVisibility(key, visibility) {
110
110
  export async function deleteFile(key) {
111
111
  return apiDelete(`/v1/memory/files/${encodeURIComponent(key)}`);
112
112
  }
113
+
114
+ /** Pull (copy) a memory entry from the home node to this node (federated sessions). */
115
+ export async function pullFromHome(key) {
116
+ return apiPost('/v1/memory/pull', { key });
117
+ }
118
+
119
+ /** Push (save) a local memory entry to the home node (federated sessions). */
120
+ export async function pushToHome(key) {
121
+ return apiPost('/v1/memory/push-home', { key });
122
+ }
@@ -1,5 +1,5 @@
1
1
  import { h } from 'preact';
2
- import { useState, useCallback } from 'preact/hooks';
2
+ import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
3
3
  import htm from 'htm';
4
4
  const html = htm.bind(h);
5
5
  import { t } from '/js/i18n.js';
@@ -7,8 +7,8 @@ import { escHtml } from '/js/utils.js';
7
7
  import { dt, num, Badge, EconRow, StatsGrid, Empty, ExpandableHelp, ErrorBox } from './shared.js';
8
8
  import {
9
9
  getFederationPeers, getFederationDirectory, approvePeeringRequest, rejectPeeringRequest,
10
- activatePeer, addPeerDirect, removePeer, removePeerEmergency, testFederationNode,
11
- joinGenesisNetwork,
10
+ deletePeeringRequest, activatePeer, addPeerDirect, removePeer, removePeerEmergency,
11
+ testFederationNode, joinGenesisNetwork, updatePeerPolicy, getNetworkDirectory,
12
12
  } from '/js/services/admin.js';
13
13
  import { useConfirm } from '/components/Modal.js';
14
14
 
@@ -30,14 +30,43 @@ export default function FederationTab({ data, reload }) {
30
30
  const [joining, setJoining] = useState(false);
31
31
  const [joinResult, setJoinResult] = useState(null);
32
32
 
33
+ // ── Network Directory state ──
34
+ const [dirEntries, setDirEntries] = useState([]);
35
+ const [dirSearch, setDirSearch] = useState('');
36
+ const [dirLoading, setDirLoading] = useState(false);
37
+ const dirDebounceRef = useRef(null);
38
+
39
+ const loadDirectory = useCallback(async (keyword) => {
40
+ setDirLoading(true);
41
+ try {
42
+ const r = await getNetworkDirectory(keyword || '');
43
+ setDirEntries(r.data?.entries || []);
44
+ } catch { setDirEntries([]); }
45
+ finally { setDirLoading(false); }
46
+ }, []);
47
+
48
+ useEffect(() => { loadDirectory(''); }, []);
49
+
50
+ useEffect(() => {
51
+ const handler = () => loadDirectory(dirSearch);
52
+ window.addEventListener('aimeat-live-update', handler);
53
+ return () => window.removeEventListener('aimeat-live-update', handler);
54
+ }, [dirSearch]);
55
+
56
+ const onDirSearchInput = useCallback((e) => {
57
+ const val = e.target.value;
58
+ setDirSearch(val);
59
+ if (dirDebounceRef.current) clearTimeout(dirDebounceRef.current);
60
+ dirDebounceRef.current = setTimeout(() => loadDirectory(val), 400);
61
+ }, [loadDirectory]);
62
+
33
63
  // ── Derived stats ──
34
64
  const activePeers = livePeers.filter(p => p.status === 'active');
35
65
  const degradedPeers = livePeers.filter(p => p.status === 'degraded');
36
66
  const offlinePeers = livePeers.filter(p => p.status === 'offline');
37
67
  const depeeringPeers = livePeers.filter(p => p.status === 'depeering');
38
68
  const pendingRequests = peeringRequests.filter(r => r.status === 'pending');
39
- const approvedRequests = peeringRequests.filter(r => r.status === 'approved');
40
- const rejectedRequests = peeringRequests.filter(r => r.status === 'rejected');
69
+ const historyRequests = peeringRequests.filter(r => r.status !== 'pending');
41
70
 
42
71
  const { confirm, ConfirmUI } = useConfirm();
43
72
 
@@ -59,6 +88,13 @@ export default function FederationTab({ data, reload }) {
59
88
  }, { danger: true });
60
89
  }, [reload]);
61
90
 
91
+ const doDeleteRequest = useCallback((id) => {
92
+ confirm(t('dashboard.fedDeleteRequestConfirm') || 'Delete this peering request?', async () => {
93
+ try { await deletePeeringRequest(id); flash(t('dashboard.fedRequestDeleted') || 'Request deleted'); reload(); }
94
+ catch (e) { flashErr(e.message); }
95
+ }, { danger: true });
96
+ }, [reload]);
97
+
62
98
  const doActivate = useCallback((nodeId) => {
63
99
  confirm(t('dashboard.fedActivateConfirm'), async () => {
64
100
  try { await activatePeer(nodeId); flash(t('dashboard.fedPeerActivated')); reload(); }
@@ -80,6 +116,14 @@ export default function FederationTab({ data, reload }) {
80
116
  }, { danger: true });
81
117
  }, [reload]);
82
118
 
119
+ const doUpdatePolicy = useCallback(async (nodeId, field, value) => {
120
+ try {
121
+ await updatePeerPolicy(nodeId, { [field]: value });
122
+ flash(t('dashboard.fedPolicyUpdated'));
123
+ reload();
124
+ } catch (e) { flashErr(e.message); }
125
+ }, [reload]);
126
+
83
127
  const doTest = useCallback(async () => {
84
128
  if (!testUrl) return;
85
129
  setTesting(true); setTestResult(null);
@@ -173,6 +217,36 @@ export default function FederationTab({ data, reload }) {
173
217
  { label: t('dashboard.fedPendingRequests'), value: pendingRequests.length, color: '#06b6d4' },
174
218
  ]} />
175
219
 
220
+ <!-- ═══ Network Directory ═══ -->
221
+ <div class="adm-card adm-mt-lg">
222
+ <h4 class="adm-mb-sm" style="margin:0">${t('dashboard.fedNetworkDirectoryTitle')}</h4>
223
+ <p class="adm-text-dim adm-text-base adm-mb-md" style="margin:0">${t('dashboard.fedNetworkDirectoryDesc')}</p>
224
+ <div class="adm-mb-md">
225
+ <input class="input-field" placeholder=${t('dashboard.fedNetworkDirSearch')} value=${dirSearch} onInput=${onDirSearchInput} />
226
+ </div>
227
+ ${dirLoading
228
+ ? html`<p class="adm-text-dim">...</p>`
229
+ : !dirEntries.length
230
+ ? html`<${Empty} text=${t('dashboard.fedNetworkDirEmpty')} />`
231
+ : html`<div class="scrollable"><table>
232
+ <thead><tr>
233
+ <th>${t('dashboard.fedNetworkDirType')}</th>
234
+ <th>${t('dashboard.fedNetworkDirName')}</th>
235
+ <th>${t('dashboard.fedNetworkDirNode')}</th>
236
+ <th>${t('dashboard.fedNetworkDirCategory')}</th>
237
+ </tr></thead>
238
+ <tbody>
239
+ ${dirEntries.map(e => html`<tr>
240
+ <td><${Badge} type=${e.type === 'action' ? 'info' : e.type === 'agent' ? 'healthy' : e.type === 'board' ? 'watch' : 'neutral'} label=${e.type} /></td>
241
+ <td>${escHtml(e.name || e.id || '—')}</td>
242
+ <td class="mono adm-text-sm">${escHtml(e.source_node || '—')}</td>
243
+ <td>${escHtml(e.category || e.service_type || '—')}</td>
244
+ </tr>`)}
245
+ </tbody>
246
+ </table></div>`
247
+ }
248
+ </div>
249
+
176
250
  <!-- ═══ Pending Peering Requests ═══ -->
177
251
  ${pendingRequests.length > 0 && html`
178
252
  <div class="adm-card adm-mt-lg" style="border-left:3px solid #f59e0b">
@@ -224,6 +298,7 @@ export default function FederationTab({ data, reload }) {
224
298
  <th>${t('dashboard.fedAddedAt')}</th>
225
299
  <th>${t('dashboard.lastSeen')}</th>
226
300
  <th>${t('dashboard.fedPublicKey')}</th>
301
+ <th>${t('dashboard.fedPolicySectionTitle')}</th>
227
302
  <th>${t('dashboard.actions')}</th>
228
303
  </tr></thead>
229
304
  <tbody>
@@ -234,6 +309,15 @@ export default function FederationTab({ data, reload }) {
234
309
  <td class="adm-text-dim">${dt(p.added_at)}</td>
235
310
  <td class="adm-text-dim">${dt(p.last_seen)}</td>
236
311
  <td class="mono" style="font-size:.7rem;max-width:100px;overflow:hidden;text-overflow:ellipsis" title=${p.public_key || ''}>${p.public_key ? p.public_key.substring(0, 16) + '...' : '\u2014'}</td>
312
+ <td>
313
+ <label class="adm-text-sm"><input type="checkbox" checked=${p.share_catalogue !== false} onChange=${(e) => doUpdatePolicy(p.node_id, 'share_catalogue', e.target.checked)} /> ${t('dashboard.fedShareCatalogue')}</label>
314
+ <label class="adm-text-sm"><input type="checkbox" checked=${p.replicate_memory !== false} onChange=${(e) => doUpdatePolicy(p.node_id, 'replicate_memory', e.target.checked)} /> ${t('dashboard.fedReplicateMemory')}</label>
315
+ <label class="adm-text-sm"><input type="checkbox" checked=${p.allow_routing !== false} onChange=${(e) => doUpdatePolicy(p.node_id, 'allow_routing', e.target.checked)} /> ${t('dashboard.fedAllowRouting')}</label>
316
+ <select class="adm-input" style="font-size:.75rem;padding:2px 4px;margin-top:4px" value=${p.peer_mode || 'federation'} onChange=${(e) => doUpdatePolicy(p.node_id, 'peer_mode', e.target.value)}>
317
+ <option value="federation">${t('dashboard.fedPeerModeFederation')}</option>
318
+ <option value="private">${t('dashboard.fedPeerModePrivate')}</option>
319
+ </select>
320
+ </td>
237
321
  <td style="display:flex;gap:4px;flex-wrap:wrap">
238
322
  ${p.status === 'approved' && html`
239
323
  <button class="adm-btn-sm" onClick=${() => doActivate(p.node_id)}>\u25B6 ${t('dashboard.fedActivate')}</button>
@@ -252,7 +336,7 @@ export default function FederationTab({ data, reload }) {
252
336
  </div>
253
337
 
254
338
  <!-- ═══ Peering Request History ═══ -->
255
- ${(approvedRequests.length > 0 || rejectedRequests.length > 0) && html`
339
+ ${historyRequests.length > 0 && html`
256
340
  <div class="adm-card adm-mt-lg">
257
341
  <h4 class="adm-mb-sm" style="margin:0">\u{1F4DC} ${t('dashboard.fedRequestHistoryTitle')}</h4>
258
342
  <div class="scrollable"><table>
@@ -262,14 +346,16 @@ export default function FederationTab({ data, reload }) {
262
346
  <th>${t('dashboard.endpoint')}</th>
263
347
  <th>${t('dashboard.statusLabel')}</th>
264
348
  <th>${t('dashboard.fedCreatedAt')}</th>
349
+ <th></th>
265
350
  </tr></thead>
266
351
  <tbody>
267
- ${[...approvedRequests, ...rejectedRequests].map(r => html`<tr>
352
+ ${historyRequests.map(r => html`<tr>
268
353
  <td class="mono" style="font-size:.75rem">${escHtml(r.id)}</td>
269
354
  <td class="mono adm-text-sm">${escHtml(r.from_node_id || '\u2014')}</td>
270
355
  <td class="mono adm-text-sm">${escHtml(r.from_node_url || r.target_url || '\u2014')}</td>
271
356
  <td>${statusBadge(r.status)}</td>
272
357
  <td class="adm-text-dim">${dt(r.created_at)}</td>
358
+ <td><button class="adm-btn-sm adm-text-error" onClick=${() => doDeleteRequest(r.id)}>\u2718</button></td>
273
359
  </tr>`)}
274
360
  </tbody>
275
361
  </table></div>
@@ -65,6 +65,7 @@ export default function GhiiTab({ data, reload }) {
65
65
  <th>${t('dashboard.ghiiEmail')}</th>
66
66
  <th>${t('dashboard.verification')}</th>
67
67
  <th>${t('dashboard.totp')}</th>
68
+ <th>${t('dashboard.lastLogin')}</th>
68
69
  <th>${t('dashboard.created')}</th>
69
70
  <th>${t('dashboard.actions')}</th>
70
71
  </tr></thead>
@@ -80,6 +81,7 @@ export default function GhiiTab({ data, reload }) {
80
81
  }</td>
81
82
  <td><${Badge} type=${vBadge} /> L${u.verification_level}</td>
82
83
  <td><${Badge} type=${u.totp_enabled ? 'healthy' : 'critical'} /></td>
84
+ <td>${u.last_login_at ? dt(u.last_login_at) : html`<span style="color:var(--text-dim)">–</span>`}</td>
83
85
  <td>${dt(u.created_at)}</td>
84
86
  <td>
85
87
  <select onChange=${e => setLevel(u.ghii, e.target.value)}>
@@ -269,7 +269,7 @@ export default function Admin({ navigate, locale }) {
269
269
  // Final counts
270
270
  newCounts.owners = d.owners.length;
271
271
  newCounts.work = d.workItems.length;
272
- newCounts.peers = d.livePeers.length || d.federation.length;
272
+ newCounts.peers = d.livePeers.length;
273
273
  newCounts.rooms = d.realtime?.stats?.rooms || 0;
274
274
  newCounts.ghii = d.ghiiUsers.length;
275
275
  newCounts.genesis = d.genesis?.peers?.length || 0;