fleetlens 0.13.2 → 0.15.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.
- package/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/app-path-routes-manifest.json +4 -1
- package/app/apps/web/.next/build-manifest.json +3 -3
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/routes-manifest.json +21 -3
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/api/ask/route.js +3 -3
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/settings/route.js +4 -3
- package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team/autostart/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route.js +7 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js +8 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js +8 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/changelog/page.js +1 -1
- package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/page.js +2 -2
- package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -2
- package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +3 -3
- package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/settings/page.js +2 -2
- package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/team/onboarding/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js +19 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js.map +5 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/team/onboarding/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/team/page.js +3 -2
- package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app-paths-manifest.json +4 -1
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_team_onboarding_start_route_actions_0zeptl_.js +3 -0
- package/app/apps/web/.next/server/chunks/0fuv_zod_v4_classic_external_0.z.vap.js +39 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__038-.1r._.js +6 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0~utv~v._.js → [root-of-the-server]__03ewxxu._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__03rmbz6._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__065lib8._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0e.f-t.._.js → [root-of-the-server]__0a0~7d6._.js} +2 -2
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0gril0u._.js → [root-of-the-server]__0acikq.._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0et2sqb._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[externals]__09v47rg._.js → [root-of-the-server]__0jbc7tz._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mq0or2._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mvl0qn._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0n79hpm._.js → [root-of-the-server]__0ow1kn1._.js} +4 -4
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~xt99s._.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_autostart_route_actions_10-4s71.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_sync-projects_route_actions_0r_2f5n.js +3 -0
- package/app/apps/web/.next/server/chunks/node_modules_next_036my~r._.js +13 -0
- package/app/apps/web/.next/server/chunks/packages_09wmnv.._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_0o6.gpk._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
- package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0z7mg0b._.js → [root-of-the-server]__08l86e-._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0a7n44-._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0c00i76._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0i.q-1h._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0r4tks8._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0stu~~r._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ufi3.w._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0o03ymy._.js → [root-of-the-server]__10~b4bf._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_0.97l5.._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/{apps_web_app_settings_0u6z6rj._.js → apps_web_0q2x4a~._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/apps_web_114yo0v._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_team_onboarding_page_actions_0pss~.k.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0c-tpft._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0txtsv5._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_team-config_ts_10egg08._.js +39 -0
- package/app/apps/web/.next/server/chunks/ssr/{node_modules_next_dist_0t-hj0x._.js → node_modules_0yrkyef._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0jxvcfi.js +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0s5yhs4.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
- package/app/apps/web/.next/server/functions-config-manifest.json +3 -1
- package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/app/apps/web/.next/server/middleware-manifest.json +5 -5
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/{0wwcx42ng8i2e.js → 0-3_u.90d79wi.js} +1 -1
- package/app/apps/web/.next/static/chunks/00a9oyw~h5i1p.js +1 -0
- package/app/apps/web/.next/static/chunks/040s42ypidk7s.css +1 -0
- package/app/apps/web/.next/static/chunks/06eiwghjkkbnh.js +1 -0
- package/app/apps/web/.next/static/chunks/06qeg4pt5jdqs.js +1 -0
- package/app/apps/web/.next/static/chunks/{0e_fzy0n8ij-o.js → 0u6gxpqhl0gc7.js} +1 -1
- package/app/apps/web/.next/static/chunks/18cj08-la37o-.js +1 -0
- package/app/apps/web/app/api/team/autostart/route.ts +41 -0
- package/app/apps/web/app/api/team/onboarding/start/route.ts +96 -0
- package/app/apps/web/app/api/team/sync-projects/route.ts +39 -0
- package/app/apps/web/app/globals.css +82 -0
- package/app/apps/web/app/settings/ai-features-form.tsx +1 -1
- package/app/apps/web/app/settings/page.tsx +21 -7
- package/app/apps/web/app/{settings → team}/force-sync-button.tsx +2 -4
- package/app/apps/web/app/team/onboarding/onboarding-wizard.tsx +503 -0
- package/app/apps/web/app/team/onboarding/page.tsx +28 -0
- package/app/apps/web/app/team/page.tsx +60 -81
- package/app/apps/web/app/{settings/team-connection-section.tsx → team/sync-activity-section.tsx} +6 -23
- package/app/apps/web/app/team/synced-projects-form.tsx +201 -0
- package/app/apps/web/app/team/synced-projects-section.tsx +37 -0
- package/app/apps/web/components/autostart-toggle.tsx +59 -0
- package/app/apps/web/components/previous-cycles-trend.tsx +4 -1
- package/app/apps/web/components/project-sync-picker.tsx +109 -0
- package/app/apps/web/components/team-chip.tsx +1 -1
- package/app/apps/web/components/team-welcome-banner.tsx +1 -1
- package/app/apps/web/lib/autostart-data.ts +31 -0
- package/app/apps/web/lib/changelog-data.generated.ts +1 -1
- package/app/apps/web/lib/sync-projects-data.ts +36 -0
- package/app/apps/web/lib/team-config.ts +21 -4
- package/app/apps/web/package.json +1 -1
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/daemon-worker.js +2089 -1622
- package/dist/index.js +1462 -230
- package/package.json +1 -1
- package/app/apps/web/.next/server/app/api/team-config/route/app-paths-manifest.json +0 -3
- package/app/apps/web/.next/server/app/api/team-config/route.js +0 -8
- package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +0 -1
- package/app/apps/web/.next/server/app/api/team-config/route_client-reference-manifest.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__08agijy._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0atn.dt._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0b.b7~1._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0i06ito._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0y5p73y._.js +0 -3
- package/app/apps/web/.next/server/chunks/_0euqdko._.js +0 -49
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team-config_route_actions_0ad2gak.js +0 -3
- package/app/apps/web/.next/server/chunks/packages_0c-sz9n._.js +0 -3
- package/app/apps/web/.next/server/chunks/packages_0wywuu.._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__09yxy53._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0n-ax-5._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0q9qy0z._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/apps_web_app_team_team-sync-form_tsx_0choz0h._.js +0 -3
- package/app/apps/web/.next/static/chunks/083~hzkv577.5.css +0 -1
- package/app/apps/web/.next/static/chunks/0h.ua.-myj.6z.js +0 -1
- package/app/apps/web/.next/static/chunks/0uyctl-m8iydb.js +0 -1
- package/app/apps/web/.next/static/chunks/14joqbsr9ihhu.js +0 -1
- package/app/apps/web/app/api/team-config/route.ts +0 -39
- package/app/apps/web/app/team/team-sync-form.tsx +0 -144
- /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route/build-manifest.json +0 -0
- /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route/server-reference-manifest.json +0 -0
- /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route.js.map +0 -0
- /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_clientMiddlewareManifest.js +0 -0
- /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_ssgManifest.js +0 -0
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Team-sync
|
|
2
|
+
* Team-sync status page.
|
|
3
3
|
*
|
|
4
|
-
* Shows
|
|
5
|
-
* over the wire on the next push
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* The page
|
|
9
|
-
* reads, so a change here propagates to the next `fleetlens team push`.
|
|
4
|
+
* Shows which team this machine's daemon is paired with and what data flows
|
|
5
|
+
* over the wire on the next push. All LLM-enriched fields are shared for
|
|
6
|
+
* every synced project; which projects are synced is the member's own
|
|
7
|
+
* choice (Settings' Synced-projects selection, seeded by the onboarding
|
|
8
|
+
* wizard). The page reads the same on-disk team-config.json the daemon reads.
|
|
10
9
|
*/
|
|
11
|
-
import Link from "next/link";
|
|
12
|
-
import { groupByProject, type SessionMeta } from "@claude-lens/parser";
|
|
13
|
-
import { listSessions } from "@/lib/data";
|
|
14
10
|
import { readTeamConfig, toTeamConfigView } from "@/lib/team-config";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
11
|
+
import { SyncedProjectsSection } from "./synced-projects-section";
|
|
12
|
+
import { SyncActivitySection } from "./sync-activity-section";
|
|
17
13
|
|
|
18
14
|
export const dynamic = "force-dynamic";
|
|
19
15
|
|
|
@@ -36,33 +32,14 @@ function lastSyncedSnapshotLabel(iso: string | undefined): string {
|
|
|
36
32
|
return new Date(ms).toLocaleString();
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
const cutoff = Date.now() - days * 86_400_000;
|
|
41
|
-
return sessions.filter((s) => {
|
|
42
|
-
if (!s.firstTimestamp) return false;
|
|
43
|
-
const ms = Date.parse(s.firstTimestamp);
|
|
44
|
-
return !Number.isNaN(ms) && ms >= cutoff;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export default async function TeamPage() {
|
|
35
|
+
export default function TeamPage() {
|
|
49
36
|
const config = readTeamConfig();
|
|
50
|
-
const allSessions = await listSessions();
|
|
51
|
-
const recent = recentDaysFilter(allSessions, 30);
|
|
52
|
-
const projects = groupByProject(recent)
|
|
53
|
-
.map((p) => ({
|
|
54
|
-
name: p.projectName,
|
|
55
|
-
pretty: prettyProjectName(p.projectName),
|
|
56
|
-
sessions: p.sessions.length,
|
|
57
|
-
worktreeCount: p.worktreeCount,
|
|
58
|
-
}))
|
|
59
|
-
.sort((a, b) => b.sessions - a.sessions);
|
|
60
37
|
|
|
61
38
|
if (!config) {
|
|
62
39
|
return (
|
|
63
40
|
<main className="mx-auto max-w-3xl p-6 space-y-6">
|
|
64
41
|
<h1 className="text-2xl font-semibold">Team sync</h1>
|
|
65
|
-
<section className="
|
|
42
|
+
<section className="af-card border-dashed text-sm space-y-3">
|
|
66
43
|
<p className="font-medium">Not paired with any team.</p>
|
|
67
44
|
<p className="text-gray-600">
|
|
68
45
|
To pair this machine with a Fleetlens team server, run from a
|
|
@@ -81,20 +58,30 @@ fleetlens team join <server-url> <invite-token>
|
|
|
81
58
|
}
|
|
82
59
|
|
|
83
60
|
const view = toTeamConfigView(config);
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
61
|
+
const sp = config.syncProjects;
|
|
62
|
+
const syncSummary = sp
|
|
63
|
+
? sp.autoIncludeNew
|
|
64
|
+
? `Syncing: all projects except ${sp.excluded.length} excluded`
|
|
65
|
+
: `Syncing: only ${sp.included.length} selected project${sp.included.length === 1 ? "" : "s"}`
|
|
66
|
+
: null;
|
|
87
67
|
|
|
88
68
|
return (
|
|
89
69
|
<main className="mx-auto max-w-3xl p-6 space-y-8">
|
|
90
70
|
<header>
|
|
91
71
|
<h1 className="text-2xl font-semibold">Team sync</h1>
|
|
92
72
|
<p className="text-sm text-gray-500 mt-1">
|
|
93
|
-
|
|
73
|
+
What your daemon shares with your team server.
|
|
94
74
|
</p>
|
|
95
75
|
</header>
|
|
96
76
|
|
|
97
|
-
|
|
77
|
+
{config.setupPending && (
|
|
78
|
+
<div className="border border-amber-400/40 bg-amber-400/10 rounded-lg p-4 text-sm">
|
|
79
|
+
Setup isn’t finished — nothing is syncing yet.{" "}
|
|
80
|
+
<a className="underline font-medium" href="/team/onboarding">Finish onboarding</a>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<section className="af-card space-y-3">
|
|
98
85
|
<div className="flex items-baseline justify-between">
|
|
99
86
|
<h2 className="text-lg font-medium">Pairing</h2>
|
|
100
87
|
<span className="text-xs uppercase tracking-wide text-green-700 dark:text-green-400">
|
|
@@ -105,7 +92,11 @@ fleetlens team join <server-url> <invite-token>
|
|
|
105
92
|
<dt className="text-gray-500">Team slug</dt>
|
|
106
93
|
<dd className="font-mono">{view.teamSlug}</dd>
|
|
107
94
|
<dt className="text-gray-500">Server</dt>
|
|
108
|
-
<dd className="font-mono break-all">
|
|
95
|
+
<dd className="font-mono break-all">
|
|
96
|
+
<a href={view.serverUrl} target="_blank" rel="noreferrer" className="hover:underline">
|
|
97
|
+
{view.serverUrl} <span aria-hidden>↗</span>
|
|
98
|
+
</a>
|
|
99
|
+
</dd>
|
|
109
100
|
<dt className="text-gray-500">Member ID</dt>
|
|
110
101
|
<dd className="font-mono break-all text-xs">{view.memberId}</dd>
|
|
111
102
|
<dt className="text-gray-500">Bearer token</dt>
|
|
@@ -119,65 +110,53 @@ fleetlens team join <server-url> <invite-token>
|
|
|
119
110
|
</dl>
|
|
120
111
|
</section>
|
|
121
112
|
|
|
122
|
-
<section className="
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
<p className="text-sm text-gray-500 mt-1">
|
|
126
|
-
Toggles below take effect on the daemon’s next push (usually
|
|
127
|
-
within 5 minutes). See{" "}
|
|
128
|
-
<Link
|
|
129
|
-
href="https://github.com/cowcow02/fleetlens"
|
|
130
|
-
className="underline"
|
|
131
|
-
>
|
|
132
|
-
the bridge design spec
|
|
133
|
-
</Link>{" "}
|
|
134
|
-
for the full data inventory.
|
|
135
|
-
</p>
|
|
136
|
-
</div>
|
|
113
|
+
<section className="af-card">
|
|
114
|
+
<SyncActivitySection />
|
|
115
|
+
</section>
|
|
137
116
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
projects={projects}
|
|
141
|
-
/>
|
|
117
|
+
<section className="af-card">
|
|
118
|
+
<SyncedProjectsSection />
|
|
142
119
|
</section>
|
|
143
120
|
|
|
144
|
-
<section className="
|
|
145
|
-
<h2 className="text-lg font-medium">
|
|
121
|
+
<section className="af-card space-y-3">
|
|
122
|
+
<h2 className="text-lg font-medium">What gets shared</h2>
|
|
123
|
+
<p className="text-sm text-gray-500">
|
|
124
|
+
{syncSummary ?? "Syncing: all projects (no selection set)."}
|
|
125
|
+
</p>
|
|
146
126
|
<ul className="text-sm space-y-2">
|
|
147
127
|
<li>
|
|
148
128
|
<strong>Daily rollup</strong> — agent time, session count, tool
|
|
149
|
-
calls, turns, token totals.
|
|
129
|
+
calls, turns, token totals.
|
|
150
130
|
</li>
|
|
151
131
|
<li>
|
|
152
|
-
<strong>Rich rollup</strong> (Entry-derived counts) for
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
prompts or agent output.
|
|
132
|
+
<strong>Rich rollup</strong> (Entry-derived counts) for each
|
|
133
|
+
synced project: working-shape distribution, top skills, subagents
|
|
134
|
+
dispatched, long-autonomous turns, plan-mode usage, brainstorm
|
|
135
|
+
warmups, tool errors, PRs/commits/pushes. Project labels only;
|
|
136
|
+
never raw prompts or agent output.
|
|
158
137
|
</li>
|
|
159
138
|
<li>
|
|
160
139
|
<strong>Plan utilization snapshot</strong> — current 5h + 7d
|
|
161
|
-
utilization windows, per Anthropic’s rate-limit API.
|
|
162
|
-
shared when fresh.
|
|
140
|
+
utilization windows, per Anthropic’s rate-limit API.
|
|
163
141
|
</li>
|
|
164
142
|
<li>
|
|
165
|
-
<strong>Enriched extras</strong>
|
|
166
|
-
minute mix
|
|
167
|
-
|
|
168
|
-
<span className="text-green-700 dark:text-green-400">opted in</span>
|
|
169
|
-
) : (
|
|
170
|
-
<span className="text-gray-500">disabled</span>
|
|
171
|
-
)}
|
|
143
|
+
<strong>Enriched extras</strong> — outcome / helpfulness / goal
|
|
144
|
+
minute mix, derived locally by your AI-features pipeline (no new
|
|
145
|
+
model calls ride along with the push).
|
|
172
146
|
</li>
|
|
173
147
|
</ul>
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
text
|
|
177
|
-
|
|
148
|
+
<div className="af-panel p-4 text-sm space-y-2">
|
|
149
|
+
<div className="font-medium">What does NOT leave your machine</div>
|
|
150
|
+
<ul className="list-disc list-inside text-gray-600 space-y-1">
|
|
151
|
+
<li>Session transcripts, prompts, or assistant responses</li>
|
|
152
|
+
<li>Absolute paths, file contents, or tool-call payloads</li>
|
|
153
|
+
<li>Anything from projects you exclude above</li>
|
|
154
|
+
<li>Anything from sessions older than the start-of-day rollup window</li>
|
|
155
|
+
</ul>
|
|
156
|
+
</div>
|
|
178
157
|
</section>
|
|
179
158
|
|
|
180
|
-
<section className="
|
|
159
|
+
<section className="af-card space-y-3">
|
|
181
160
|
<h2 className="text-lg font-medium">Disconnect</h2>
|
|
182
161
|
<p className="text-sm text-gray-600">
|
|
183
162
|
To leave the team and stop all syncing, run:
|
package/app/apps/web/app/{settings/team-connection-section.tsx → team/sync-activity-section.tsx}
RENAMED
|
@@ -8,17 +8,17 @@ const HEALTH_COLORS = {
|
|
|
8
8
|
red: "var(--af-error, #ef4444)",
|
|
9
9
|
} as const;
|
|
10
10
|
|
|
11
|
-
export function
|
|
11
|
+
export function SyncActivitySection() {
|
|
12
12
|
const conn = readTeamConnection();
|
|
13
13
|
if (!conn.paired) return null;
|
|
14
14
|
|
|
15
|
-
const {
|
|
15
|
+
const { lastPush, health } = conn;
|
|
16
16
|
const cliAvailable = Boolean(process.env.FLEETLENS_CLI_BIN);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<section id="team" className="space-y-4">
|
|
20
20
|
<div className="flex items-baseline gap-3">
|
|
21
|
-
<h2 className="text-lg font-medium">
|
|
21
|
+
<h2 className="text-lg font-medium">Sync activity</h2>
|
|
22
22
|
<span
|
|
23
23
|
aria-hidden
|
|
24
24
|
style={{
|
|
@@ -29,12 +29,9 @@ export function TeamConnectionSection() {
|
|
|
29
29
|
display: "inline-block",
|
|
30
30
|
}}
|
|
31
31
|
/>
|
|
32
|
-
<span className="text-sm text-gray-500">{team.name}</span>
|
|
33
32
|
</div>
|
|
34
33
|
|
|
35
34
|
<dl className="grid grid-cols-[8rem_1fr] gap-y-1 text-sm text-gray-600">
|
|
36
|
-
<dt>Server</dt><dd className="font-mono text-xs">{team.serverUrl}</dd>
|
|
37
|
-
<dt>Paired</dt><dd>{new Date(member.pairedAt).toLocaleString()}</dd>
|
|
38
35
|
<dt>Last sync</dt>
|
|
39
36
|
<dd suppressHydrationWarning>
|
|
40
37
|
{lastPush.kind === "none"
|
|
@@ -44,7 +41,7 @@ export function TeamConnectionSection() {
|
|
|
44
41
|
</dl>
|
|
45
42
|
|
|
46
43
|
{lastPush.kind === "ok" && (
|
|
47
|
-
<div className="
|
|
44
|
+
<div className="af-panel p-4 space-y-3 text-sm">
|
|
48
45
|
<div className="text-xs uppercase tracking-wide text-gray-500">
|
|
49
46
|
Last push
|
|
50
47
|
{lastPush.payload.dailyRollup ? ` — ${lastPush.payload.dailyRollup.day}` : ""}
|
|
@@ -93,7 +90,7 @@ export function TeamConnectionSection() {
|
|
|
93
90
|
<summary className="cursor-pointer text-gray-500 hover:text-gray-700">
|
|
94
91
|
Show raw JSON payload
|
|
95
92
|
</summary>
|
|
96
|
-
<pre className="mt-2
|
|
93
|
+
<pre className="af-panel mt-2 p-3 font-mono text-[11px] whitespace-pre-wrap overflow-x-auto">
|
|
97
94
|
{JSON.stringify(lastPush.payload, null, 2)}
|
|
98
95
|
</pre>
|
|
99
96
|
</details>
|
|
@@ -101,27 +98,13 @@ export function TeamConnectionSection() {
|
|
|
101
98
|
)}
|
|
102
99
|
|
|
103
100
|
{lastPush.kind === "error" && (
|
|
104
|
-
<div className="
|
|
101
|
+
<div className="af-panel-danger p-4 text-sm">
|
|
105
102
|
<div className="font-medium">Last sync failed</div>
|
|
106
103
|
<div>{lastPush.error}</div>
|
|
107
104
|
</div>
|
|
108
105
|
)}
|
|
109
106
|
|
|
110
107
|
<ForceSyncButton cliAvailable={cliAvailable} />
|
|
111
|
-
|
|
112
|
-
<div className="rounded border border-gray-200 p-4 text-sm space-y-2">
|
|
113
|
-
<div className="font-medium">What does NOT leave your machine</div>
|
|
114
|
-
<ul className="list-disc list-inside text-gray-600 space-y-1">
|
|
115
|
-
<li>Session transcripts, prompts, or assistant responses</li>
|
|
116
|
-
<li>Project names, paths, or repo information</li>
|
|
117
|
-
<li>File contents or tool-call payloads</li>
|
|
118
|
-
<li>Anything from sessions older than the start-of-day rollup window</li>
|
|
119
|
-
</ul>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<p className="text-xs text-gray-500">
|
|
123
|
-
To disconnect from this team, run <code>fleetlens team leave</code> in your terminal.
|
|
124
|
-
</p>
|
|
125
108
|
</section>
|
|
126
109
|
);
|
|
127
110
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { ProjectSyncPicker } from "@/components/project-sync-picker";
|
|
4
|
+
import type { SyncProjectRow } from "@/lib/sync-projects-data";
|
|
5
|
+
import type { SyncProjects } from "@/lib/team-config";
|
|
6
|
+
|
|
7
|
+
const ts = () => new Date().toTimeString().slice(0, 8);
|
|
8
|
+
|
|
9
|
+
export function SyncedProjectsForm({
|
|
10
|
+
projects,
|
|
11
|
+
initial,
|
|
12
|
+
}: {
|
|
13
|
+
projects: SyncProjectRow[];
|
|
14
|
+
initial: SyncProjects;
|
|
15
|
+
}) {
|
|
16
|
+
const [value, setValue] = useState<SyncProjects>(initial);
|
|
17
|
+
// Last-persisted selection — the read-only summary renders from this, so a
|
|
18
|
+
// cancelled edit or in-flight change never lies about what's syncing.
|
|
19
|
+
const [saved, setSaved] = useState<SyncProjects>(initial);
|
|
20
|
+
const [editing, setEditing] = useState(false);
|
|
21
|
+
const [saving, setSaving] = useState(false);
|
|
22
|
+
const [savedMsg, setSavedMsg] = useState<string | null>(null);
|
|
23
|
+
const [log, setLog] = useState<{ text: string; tone: "default" | "warn" | "error" }[] | null>(null);
|
|
24
|
+
const logRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
logRef.current?.scrollTo({ top: logRef.current.scrollHeight });
|
|
28
|
+
}, [log]);
|
|
29
|
+
|
|
30
|
+
const append = (text: string, tone: "default" | "warn" | "error" = "default") =>
|
|
31
|
+
setLog((prev) => [...(prev ?? []), { text: `${ts()} ${text}`, tone }]);
|
|
32
|
+
|
|
33
|
+
// Same NDJSON→SSE stream the onboarding wizard consumes; a changed
|
|
34
|
+
// selection re-pushes FULL history so the server matches it.
|
|
35
|
+
async function streamResync() {
|
|
36
|
+
setLog([]);
|
|
37
|
+
append("re-pushing your full history under the new selection…");
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch("/api/team/onboarding/start", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "content-type": "application/json" },
|
|
42
|
+
body: JSON.stringify(value),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok || !res.body) {
|
|
45
|
+
const body = await res.json().catch(() => null);
|
|
46
|
+
append(body?.error ?? `Error ${res.status}`, "error");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const reader = res.body.getReader();
|
|
50
|
+
const decoder = new TextDecoder();
|
|
51
|
+
let buf = "";
|
|
52
|
+
for (;;) {
|
|
53
|
+
const { done, value: chunk } = await reader.read();
|
|
54
|
+
if (done) break;
|
|
55
|
+
buf += decoder.decode(chunk, { stream: true });
|
|
56
|
+
const frames = buf.split("\n\n");
|
|
57
|
+
buf = frames.pop() ?? "";
|
|
58
|
+
for (const frame of frames) {
|
|
59
|
+
const data = frame.split("\n").find((l) => l.startsWith("data: "))?.slice(6);
|
|
60
|
+
if (!data) continue;
|
|
61
|
+
let ev;
|
|
62
|
+
try {
|
|
63
|
+
ev = JSON.parse(data);
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (ev.type === "phase" && ev.phase === "activity") append(`pushing ${ev.totalDays} day(s), newest first…`);
|
|
68
|
+
else if (ev.type === "day")
|
|
69
|
+
append(
|
|
70
|
+
ev.outcome === "pushed"
|
|
71
|
+
? `✓ ${ev.day} pushed (${ev.index}/${ev.total})`
|
|
72
|
+
: `${ev.outcome === "queued" ? "⚠" : "✗"} ${ev.day} ${ev.outcome} (${ev.index}/${ev.total})`,
|
|
73
|
+
ev.outcome === "pushed" ? "default" : ev.outcome === "queued" ? "warn" : "error",
|
|
74
|
+
);
|
|
75
|
+
else if (ev.type === "done") append(`done — ${ev.pushed} day(s) pushed`);
|
|
76
|
+
else if (ev.type === "error") append(ev.message ?? "sync error", "error");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
append("server now matches your selection");
|
|
80
|
+
} catch (err) {
|
|
81
|
+
append(`stream failed: ${(err as Error).message} — the daemon finishes the re-push within ~5 min`, "warn");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function handleSave() {
|
|
86
|
+
setSaving(true);
|
|
87
|
+
setSavedMsg(null);
|
|
88
|
+
setLog(null);
|
|
89
|
+
const res = await fetch("/api/team/sync-projects", {
|
|
90
|
+
method: "PUT",
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
92
|
+
body: JSON.stringify(value),
|
|
93
|
+
});
|
|
94
|
+
const body = await res.json().catch(() => null);
|
|
95
|
+
setSaving(false);
|
|
96
|
+
setSavedMsg(
|
|
97
|
+
res.ok ? (body?.resync ? "Saved — selection changed." : "Saved.") : (body?.error ?? `Error: ${res.status}`),
|
|
98
|
+
);
|
|
99
|
+
if (res.ok) {
|
|
100
|
+
setSaved(value);
|
|
101
|
+
setEditing(false);
|
|
102
|
+
}
|
|
103
|
+
if (body?.resync) void streamResync();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isSynced = (name: string) =>
|
|
107
|
+
saved.excluded.includes(name) ? false : saved.included.includes(name) ? true : saved.autoIncludeNew;
|
|
108
|
+
const synced = projects.filter((p) => isSynced(p.name));
|
|
109
|
+
const privateCount = projects.length - synced.length;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="space-y-4">
|
|
113
|
+
{!editing && (
|
|
114
|
+
<div className="space-y-3">
|
|
115
|
+
<p className="text-sm">
|
|
116
|
+
Syncing <strong>{synced.length}</strong> of {projects.length} projects
|
|
117
|
+
{saved.autoIncludeNew ? " — new projects sync automatically." : " — new projects stay private."}
|
|
118
|
+
</p>
|
|
119
|
+
<div className="flex flex-wrap gap-1.5">
|
|
120
|
+
{synced.slice(0, 8).map((p) => (
|
|
121
|
+
<a
|
|
122
|
+
key={p.name}
|
|
123
|
+
href={`/projects/${encodeURIComponent(p.projectDir)}`}
|
|
124
|
+
className="af-chip"
|
|
125
|
+
>
|
|
126
|
+
{p.name}
|
|
127
|
+
</a>
|
|
128
|
+
))}
|
|
129
|
+
{synced.length > 8 && (
|
|
130
|
+
<span className="px-2 py-0.5 text-xs text-gray-500">+{synced.length - 8} more</span>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
{privateCount > 0 && (
|
|
134
|
+
<p className="text-xs text-gray-500">
|
|
135
|
+
{privateCount} project{privateCount === 1 ? " stays" : "s stay"} private on this machine.
|
|
136
|
+
</p>
|
|
137
|
+
)}
|
|
138
|
+
<div className="flex items-center gap-3">
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
onClick={() => {
|
|
142
|
+
setValue(saved);
|
|
143
|
+
setSavedMsg(null);
|
|
144
|
+
setEditing(true);
|
|
145
|
+
}}
|
|
146
|
+
className="af-btn"
|
|
147
|
+
>
|
|
148
|
+
Edit selection
|
|
149
|
+
</button>
|
|
150
|
+
{savedMsg && <p className="text-sm">{savedMsg}</p>}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
{editing && (
|
|
155
|
+
<>
|
|
156
|
+
<ProjectSyncPicker projects={projects} value={value} onChange={setValue} />
|
|
157
|
+
<div className="flex items-center gap-3">
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={handleSave}
|
|
161
|
+
disabled={saving}
|
|
162
|
+
className="af-btn-primary"
|
|
163
|
+
>
|
|
164
|
+
{saving ? "Saving…" : "Save"}
|
|
165
|
+
</button>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
onClick={() => {
|
|
169
|
+
setValue(saved);
|
|
170
|
+
setEditing(false);
|
|
171
|
+
setSavedMsg(null);
|
|
172
|
+
}}
|
|
173
|
+
disabled={saving}
|
|
174
|
+
className="af-btn"
|
|
175
|
+
>
|
|
176
|
+
Cancel
|
|
177
|
+
</button>
|
|
178
|
+
{savedMsg && <p className="text-sm">{savedMsg}</p>}
|
|
179
|
+
</div>
|
|
180
|
+
</>
|
|
181
|
+
)}
|
|
182
|
+
{log && (
|
|
183
|
+
<div
|
|
184
|
+
ref={logRef}
|
|
185
|
+
className="max-h-60 overflow-y-auto rounded-lg bg-gray-950 p-3 font-mono text-xs text-gray-200"
|
|
186
|
+
>
|
|
187
|
+
{log.map((line, i) => (
|
|
188
|
+
<div
|
|
189
|
+
key={i}
|
|
190
|
+
className={
|
|
191
|
+
line.tone === "error" ? "text-red-400" : line.tone === "warn" ? "text-amber-400" : undefined
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
{line.text}
|
|
195
|
+
</div>
|
|
196
|
+
))}
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readTeamConfig } from "@/lib/team-config";
|
|
2
|
+
import { listSyncProjectRows } from "@/lib/sync-projects-data";
|
|
3
|
+
import { SyncedProjectsForm } from "./synced-projects-form";
|
|
4
|
+
|
|
5
|
+
export async function SyncedProjectsSection() {
|
|
6
|
+
const config = readTeamConfig();
|
|
7
|
+
if (!config) return null;
|
|
8
|
+
|
|
9
|
+
if (config.setupPending) {
|
|
10
|
+
return (
|
|
11
|
+
<section className="space-y-2">
|
|
12
|
+
<h2 className="text-lg font-medium">Synced projects</h2>
|
|
13
|
+
<p className="text-sm text-gray-500">
|
|
14
|
+
Setup isn’t finished yet — finish the{" "}
|
|
15
|
+
<a className="underline" href="/team/onboarding">onboarding wizard</a>{" "}
|
|
16
|
+
to choose which projects sync to your team.
|
|
17
|
+
</p>
|
|
18
|
+
</section>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const projects = await listSyncProjectRows();
|
|
23
|
+
return (
|
|
24
|
+
<section className="space-y-3">
|
|
25
|
+
<h2 className="text-lg font-medium">Synced projects</h2>
|
|
26
|
+
<p className="text-sm text-gray-500">
|
|
27
|
+
Choose which projects share metrics with your team. Saving a changed
|
|
28
|
+
selection re-pushes your full history so the server matches it —
|
|
29
|
+
excluded projects disappear, newly included ones backfill.
|
|
30
|
+
</p>
|
|
31
|
+
<SyncedProjectsForm
|
|
32
|
+
projects={projects}
|
|
33
|
+
initial={config.syncProjects ?? { autoIncludeNew: true, included: [], excluded: [] }}
|
|
34
|
+
/>
|
|
35
|
+
</section>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import type { AutostartState } from "@/lib/autostart-data";
|
|
4
|
+
|
|
5
|
+
export function AutostartToggle({ initial }: { initial: AutostartState }) {
|
|
6
|
+
const [state, setState] = useState(initial);
|
|
7
|
+
const [busy, setBusy] = useState(false);
|
|
8
|
+
const [error, setError] = useState<string | null>(null);
|
|
9
|
+
|
|
10
|
+
if (!state.supported) {
|
|
11
|
+
return (
|
|
12
|
+
<p className="text-sm text-gray-500">
|
|
13
|
+
Auto-start uses a macOS LaunchAgent and is macOS-only for now. Add{" "}
|
|
14
|
+
<code className="font-mono text-xs">fleetlens daemon start</code> to your login items manually.
|
|
15
|
+
</p>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function toggle(enabled: boolean) {
|
|
20
|
+
setBusy(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
const res = await fetch("/api/team/autostart", {
|
|
23
|
+
method: "PUT",
|
|
24
|
+
headers: { "content-type": "application/json" },
|
|
25
|
+
body: JSON.stringify({ enabled }),
|
|
26
|
+
});
|
|
27
|
+
const body = await res.json().catch(() => null);
|
|
28
|
+
setBusy(false);
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
setError(body?.error ?? `Error ${res.status}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
setState(body);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="space-y-2">
|
|
38
|
+
<label className="flex cursor-pointer items-start gap-2 text-sm">
|
|
39
|
+
<input
|
|
40
|
+
type="checkbox"
|
|
41
|
+
checked={state.installed}
|
|
42
|
+
disabled={busy}
|
|
43
|
+
onChange={(e) => void toggle(e.target.checked)}
|
|
44
|
+
/>
|
|
45
|
+
<span>
|
|
46
|
+
Start Fleetlens at login (dashboard + usage daemon)
|
|
47
|
+
<span className="mt-0.5 block text-xs text-gray-500">
|
|
48
|
+
{state.installed
|
|
49
|
+
? "Enabled — the dashboard and reporting come back after a reboot."
|
|
50
|
+
: state.optedOut
|
|
51
|
+
? "Disabled — you opted out; joining a team won't re-enable it."
|
|
52
|
+
: "Disabled — pairing with a team enables this automatically."}
|
|
53
|
+
</span>
|
|
54
|
+
</span>
|
|
55
|
+
</label>
|
|
56
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -74,7 +74,10 @@ function CycleBar({
|
|
|
74
74
|
cycle: CyclePeak;
|
|
75
75
|
windowLabel: "5h" | "7d";
|
|
76
76
|
}) {
|
|
77
|
+
// Bar height / tone stay clamped to 100; the LABEL shows the true value so
|
|
78
|
+
// an overage cycle reads "247%", not a misleading "100%".
|
|
77
79
|
const pct = Math.max(0, Math.min(100, cycle.peakPct));
|
|
80
|
+
const labelPct = Math.max(0, cycle.peakPct);
|
|
78
81
|
const tone = cycle.current
|
|
79
82
|
? paceToneForCycle(pct, new Date(cycle.endsAt).getTime(), WINDOW_MS[windowLabel])
|
|
80
83
|
: utilizationTone(pct);
|
|
@@ -104,7 +107,7 @@ function CycleBar({
|
|
|
104
107
|
fontVariantNumeric: "tabular-nums",
|
|
105
108
|
}}
|
|
106
109
|
>
|
|
107
|
-
{
|
|
110
|
+
{labelPct.toFixed(0)}%
|
|
108
111
|
</div>
|
|
109
112
|
<div
|
|
110
113
|
style={{
|