From e54449464c1212d5481cdc0515335bd1fa95ddb9 Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Mon, 24 Nov 2025 02:52:15 -0800 Subject: [PATCH] refactor: update build management to use artifactPath instead of artifactUrl, enhance build retrieval logic, and streamline profileBuild handling for improved efficiency --- convex/builds.ts | 187 ++++-------- convex/http.ts | 1 + convex/profiles.ts | 110 +++---- convex/schema.ts | 24 +- src/App.tsx | 4 +- src/components/ProfileTargets.tsx | 1 - src/constants/versions.ts | 478 +++++++++++++++--------------- src/pages/Dashboard.tsx | 8 +- src/pages/ProfileDetail.tsx | 6 +- src/pages/ProfileFlash.tsx | 11 +- 10 files changed, 358 insertions(+), 472 deletions(-) diff --git a/convex/builds.ts b/convex/builds.ts index 5d1f902..40e7419 100644 --- a/convex/builds.ts +++ b/convex/builds.ts @@ -8,7 +8,6 @@ import modulesData from './modules.json' type BuildUpdateData = { status: string completedAt?: number - artifactUrl?: string } /** @@ -38,27 +37,25 @@ async function computeBuildHash( } /** - * Constructs the R2 artifact URL from a build hash. - * Uses the R2 public URL pattern: https://..r2.cloudflarestorage.com/.uf2 + * Constructs the R2 artifact URL from a build. + * Uses artifactPath if available, otherwise falls back to buildHash.uf2 * Or custom domain if R2_PUBLIC_URL is set. */ -function getR2ArtifactUrl(buildHash: string): string { +export function getR2ArtifactUrl(build: { + buildHash: string + artifactPath?: string +}): string { const r2PublicUrl = process.env.R2_PUBLIC_URL - if (r2PublicUrl) { - // Custom domain configured - return `${r2PublicUrl}/${buildHash}.uf2` + if (!r2PublicUrl) { + throw new Error('R2_PUBLIC_URL is not set') } - // Default R2 public URL pattern (requires public bucket) - const bucketName = process.env.R2_BUCKET_NAME || 'firmware-builds' - const accountId = process.env.R2_ACCOUNT_ID || '' - if (accountId) { - return `https://${bucketName}.${accountId}.r2.cloudflarestorage.com/${buildHash}.uf2` - } - // Fallback: assume custom domain or public bucket URL - return `https://${bucketName}.r2.cloudflarestorage.com/${buildHash}.uf2` + const path = build.artifactPath || `/${build.buildHash}.uf2` + // Ensure path starts with / + const normalizedPath = path.startsWith('/') ? path : `/${path}` + return `${r2PublicUrl}${normalizedPath}` } -export const triggerFlash = mutation({ +export const triggerBuildViaProfile = mutation({ args: { profileId: v.id('profiles'), target: v.string(), @@ -90,7 +87,7 @@ export const triggerFlash = mutation({ ) // Check if build already exists with this hash - const existingBuild = await ctx.db + let existingBuild = await ctx.db .query('builds') .withIndex('by_hash', (q) => q.eq('buildHash', buildHash)) .first() @@ -102,90 +99,54 @@ export const triggerFlash = mutation({ // Build already exists, use it buildId = existingBuild._id } else { - // Check cache for existing build - const cached = await ctx.db - .query('buildCache') - .withIndex('by_hash_target', (q) => - q.eq('buildHash', buildHash).eq('target', args.target) - ) - .first() - - if (cached) { - // Use cached artifact, create build with success status - const artifactUrl = getR2ArtifactUrl(buildHash) - buildId = await ctx.db.insert('builds', { - target: args.target, - githubRunId: 0, - status: 'success', - artifactUrl: artifactUrl, - startedAt: Date.now(), - completedAt: Date.now(), - buildHash: buildHash, - }) - } else { - // Not cached, create new build and dispatch workflow - buildId = await ctx.db.insert('builds', { - target: args.target, - githubRunId: 0, - status: 'queued', - startedAt: Date.now(), - buildHash: buildHash, - }) - shouldDispatch = true - } + // Create new build and dispatch workflow + buildId = await ctx.db.insert('builds', { + target: args.target, + githubRunId: 0, + status: 'queued', + startedAt: Date.now(), + buildHash: buildHash, + }) + shouldDispatch = true // Handle race condition - const raceCheckBuild = await ctx.db + existingBuild = await ctx.db .query('builds') .withIndex('by_hash', (q) => q.eq('buildHash', buildHash)) .first() - if (raceCheckBuild && raceCheckBuild._id !== buildId) { + if (existingBuild && existingBuild._id !== buildId) { await ctx.db.delete(buildId) - buildId = raceCheckBuild._id + buildId = existingBuild._id shouldDispatch = false } } - // Create or get profileTarget - let profileTarget = await ctx.db - .query('profileTargets') - .withIndex('by_profile_target', (q) => - q.eq('profileId', args.profileId).eq('target', args.target) - ) - .first() + // Create or update profileBuild record + // Check if a profileBuild already exists for this profile+target combination + const profileBuilds = await ctx.db + .query('profileBuilds') + .withIndex('by_profile', (q) => q.eq('profileId', args.profileId)) + .collect() - if (!profileTarget) { - const newProfileTargetId = await ctx.db.insert('profileTargets', { - profileId: args.profileId, - target: args.target, - createdAt: Date.now(), - }) - const retrieved = await ctx.db.get(newProfileTargetId) - if (!retrieved) { - throw new Error('Failed to create profileTarget') + // Find existing profileBuild with matching target by checking the build + let foundExisting = null + for (const pb of profileBuilds) { + const build = await ctx.db.get(pb.buildId) + if (build?.target === args.target) { + foundExisting = pb + break } - profileTarget = retrieved } - // Create or update profileBuild record - const existingProfileBuild = await ctx.db - .query('profileBuilds') - .withIndex('by_profile_target', (q) => - q.eq('profileId', args.profileId).eq('target', args.target) - ) - .first() - - if (existingProfileBuild) { - await ctx.db.patch(existingProfileBuild._id, { + if (foundExisting) { + await ctx.db.patch(foundExisting._id, { buildId: buildId, }) } else { await ctx.db.insert('profileBuilds', { profileId: args.profileId, buildId: buildId, - target: args.target, - createdAt: Date.now(), }) } @@ -200,7 +161,7 @@ export const triggerFlash = mutation({ }) } - return profileTarget._id + return buildId }, }) @@ -217,7 +178,12 @@ export const listByProfile = query({ const builds = await Promise.all( profileBuilds.map(async (pb) => { const build = await ctx.db.get(pb.buildId) - return build + if (!build) return null + // Return build with computed artifactUrl + return { + ...build, + artifactUrl: getR2ArtifactUrl(build), + } }) ) @@ -249,7 +215,11 @@ export const get = query({ for (const pb of profileBuilds) { const profile = await ctx.db.get(pb.profileId) if (profile && profile.userId === userId) { - return build + // Return build with computed artifactUrl + return { + ...build, + artifactUrl: getR2ArtifactUrl(build), + } } } @@ -318,13 +288,13 @@ export const updateBuildStatus = internalMutation({ args: { buildId: v.id('builds'), status: v.string(), // Accepts any status string value - artifactUrl: v.optional(v.string()), + artifactPath: v.optional(v.string()), }, handler: async (ctx, args) => { const build = await ctx.db.get(args.buildId) if (!build) return - const updateData: BuildUpdateData = { + const updateData: BuildUpdateData & { artifactPath?: string } = { status: args.status, } @@ -333,52 +303,11 @@ export const updateBuildStatus = internalMutation({ updateData.completedAt = Date.now() } - if (args.artifactUrl) { - updateData.artifactUrl = args.artifactUrl + // Set artifactPath if provided + if (args.artifactPath !== undefined) { + updateData.artifactPath = args.artifactPath } await ctx.db.patch(args.buildId, updateData) - - // If build succeeded, store in cache with R2 URL - if (args.status === 'success' && build.buildHash && build.target) { - // Get version from any profileBuild linked to this build - const profileBuilds = await ctx.db - .query('profileBuilds') - .withIndex('by_build', (q) => q.eq('buildId', args.buildId)) - .collect() - - if (profileBuilds.length > 0) { - const profileBuild = profileBuilds[0] - const profile = await ctx.db.get(profileBuild.profileId) - if (profile) { - // Construct R2 URL from hash - const artifactUrl = getR2ArtifactUrl(build.buildHash) - - // Update build with R2 URL if not already set - if (!args.artifactUrl) { - await ctx.db.patch(args.buildId, { artifactUrl }) - } - - // Check if cache entry already exists - const existing = await ctx.db - .query('buildCache') - .withIndex('by_hash_target', (q) => - q.eq('buildHash', build.buildHash).eq('target', build.target) - ) - .first() - - if (!existing) { - // Store in cache - await ctx.db.insert('buildCache', { - buildHash: build.buildHash, - target: build.target, - artifactUrl: artifactUrl, - version: profile.version, - createdAt: Date.now(), - }) - } - } - } - } }, }) diff --git a/convex/http.ts b/convex/http.ts index 561c519..c59852f 100644 --- a/convex/http.ts +++ b/convex/http.ts @@ -34,6 +34,7 @@ http.route({ await ctx.runMutation(internal.builds.updateBuildStatus, { buildId: payload.build_id, status: payload.status, + artifactPath: payload.artifactPath, }) return new Response(null, { status: 200 }) diff --git a/convex/profiles.ts b/convex/profiles.ts index ebfac4a..35d792f 100644 --- a/convex/profiles.ts +++ b/convex/profiles.ts @@ -1,6 +1,7 @@ import { getAuthUserId } from '@convex-dev/auth/server' import { v } from 'convex/values' import { mutation, query } from './_generated/server' +import { getR2ArtifactUrl } from './builds' export const list = query({ args: {}, @@ -45,36 +46,50 @@ export const get = query({ export const getTargets = query({ args: { profileId: v.id('profiles') }, handler: async (ctx, args) => { - const profileTargets = await ctx.db - .query('profileTargets') + const profileBuilds = await ctx.db + .query('profileBuilds') .withIndex('by_profile', (q) => q.eq('profileId', args.profileId)) .collect() - return profileTargets.map((pt) => pt.target) + // Get unique targets from builds + const builds = await Promise.all( + profileBuilds.map((pb) => ctx.db.get(pb.buildId)) + ) + const targets = new Set( + builds + .filter((b): b is NonNullable => b !== null) + .map((b) => b.target) + ) + return Array.from(targets) }, }) export const getProfileTarget = query({ - args: { profileTargetId: v.id('profileTargets') }, + args: { + profileId: v.id('profiles'), + target: v.string(), + }, handler: async (ctx, args) => { - const profileTarget = await ctx.db.get(args.profileTargetId) - if (!profileTarget) return null - - // Get the associated build via profileBuilds - const profileBuild = await ctx.db + // Get all profileBuilds for this profile + const profileBuilds = await ctx.db .query('profileBuilds') - .withIndex('by_profile_target', (q) => - q - .eq('profileId', profileTarget.profileId) - .eq('target', profileTarget.target) - ) - .first() + .withIndex('by_profile', (q) => q.eq('profileId', args.profileId)) + .collect() - const build = profileBuild ? await ctx.db.get(profileBuild.buildId) : null - - return { - profileTarget, - build, + // Find the profileBuild with matching target by checking the build + for (const profileBuild of profileBuilds) { + const build = await ctx.db.get(profileBuild.buildId) + if (build?.target === args.target) { + return { + profileBuild, + build: { + ...build, + artifactUrl: getR2ArtifactUrl(build), + }, + } + } } + + return null }, }) @@ -118,16 +133,8 @@ export const create = mutation({ isPublic: args.isPublic ?? true, }) - // Create profileTargets entries - if (args.targets) { - for (const target of args.targets) { - await ctx.db.insert('profileTargets', { - profileId, - target, - createdAt: Date.now(), - }) - } - } + // Note: targets are now tracked via profileBuilds when builds are triggered + // No need to create profileTargets entries return profileId }, @@ -160,37 +167,8 @@ export const update = mutation({ updatedAt: Date.now(), }) - // Sync profileTargets if targets are provided - if (args.targets !== undefined) { - const newTargets = new Set(args.targets) - - const existingProfileTargets = await ctx.db - .query('profileTargets') - .withIndex('by_profile', (q) => q.eq('profileId', args.id)) - .collect() - - const existingTargets = new Set( - existingProfileTargets.map((pt) => pt.target) - ) - - // Delete targets that are no longer in the list - for (const profileTarget of existingProfileTargets) { - if (!newTargets.has(profileTarget.target)) { - await ctx.db.delete(profileTarget._id) - } - } - - // Add new targets - for (const target of args.targets) { - if (!existingTargets.has(target)) { - await ctx.db.insert('profileTargets', { - profileId: args.id, - target, - createdAt: Date.now(), - }) - } - } - } + // Note: targets are now tracked via profileBuilds when builds are triggered + // No need to sync profileTargets }, }) @@ -205,14 +183,14 @@ export const remove = mutation({ throw new Error('Unauthorized') } - // Delete associated profileTargets - const profileTargets = await ctx.db - .query('profileTargets') + // Delete associated profileBuilds + const profileBuilds = await ctx.db + .query('profileBuilds') .withIndex('by_profile', (q) => q.eq('profileId', args.id)) .collect() - for (const profileTarget of profileTargets) { - await ctx.db.delete(profileTarget._id) + for (const profileBuild of profileBuilds) { + await ctx.db.delete(profileBuild._id) } await ctx.db.delete(args.id) diff --git a/convex/schema.ts b/convex/schema.ts index d31e165..a5c9dd6 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -14,39 +14,21 @@ export default defineSchema({ }) .index('by_user', ['userId']) .index('by_public', ['isPublic']), + builds: defineTable({ target: v.string(), githubRunId: v.number(), status: v.string(), // Accepts arbitrary status strings (e.g., "queued", "checking_out", "building", "uploading", "success", "failure") - artifactUrl: v.optional(v.string()), startedAt: v.number(), completedAt: v.optional(v.number()), buildHash: v.string(), + artifactPath: v.optional(v.string()), // Path to artifact in R2 (e.g., "/abc123.uf2" or "/abc123.bin") }).index('by_hash', ['buildHash']), - profileTargets: defineTable({ - profileId: v.id('profiles'), - target: v.string(), - createdAt: v.number(), - }) - .index('by_profile', ['profileId']) - .index('by_profile_target', ['profileId', 'target']), - profileBuilds: defineTable({ profileId: v.id('profiles'), buildId: v.id('builds'), - target: v.string(), - createdAt: v.number(), }) .index('by_profile', ['profileId']) - .index('by_build', ['buildId']) - .index('by_profile_target', ['profileId', 'target']), - - buildCache: defineTable({ - buildHash: v.string(), - target: v.string(), - artifactUrl: v.string(), - version: v.string(), - createdAt: v.number(), - }).index('by_hash_target', ['buildHash', 'target']), + .index('by_build', ['buildId']), }) diff --git a/src/App.tsx b/src/App.tsx index 0e7ff08..e82604e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,7 +24,7 @@ function App() { } /> } /> } /> } /> @@ -39,7 +39,7 @@ function App() { } /> } /> } /> } /> diff --git a/src/components/ProfileTargets.tsx b/src/components/ProfileTargets.tsx index 7043aad..62488b4 100644 --- a/src/components/ProfileTargets.tsx +++ b/src/components/ProfileTargets.tsx @@ -23,4 +23,3 @@ export default function ProfileTargets({ profileId }: ProfileTargetsProps) { ) } - diff --git a/src/constants/versions.ts b/src/constants/versions.ts index e46c665..7b188bc 100644 --- a/src/constants/versions.ts +++ b/src/constants/versions.ts @@ -1,242 +1,242 @@ // This file is auto-generated by scripts/generate-versions.js export const VERSIONS = [ - 'v2.7.15.567b8ea', - 'v2.7.14.e959000', - 'v2.7.13.597fa0b', - 'v2.7.12.45f15b8', - 'v2.7.11.ee68575', - 'v2.7.10.94d4bdf', - 'v2.7.9.70724be', - 'v2.7.8.a0c0388', - 'v2.7.7.5ae4ff9', - 'v2.7.6.834c3c5', - 'v2.7.5.ddd1499', - 'v2.7.4.c1f4f79', - 'v2.7.3.cf574c7', - 'v2.7.2.f6d3782', - 'v2.7.1.f35ca81', - 'v2.7.0.705515a', - 'v2.7.0.195b7cc', - 'v2.6.13.0561f2c', - 'v2.6.12.9861e82', - 'v2.6.11.60ec05e', - 'v2.6.10.9ce4455', - 'v2.6.9.f223b8a', - 'v2.6.8.ef9d0d7', - 'v2.6.7.2d6181f', - 'v2.6.6.54c1423', - 'v2.6.5.fc3d9f2', - 'v2.6.4.b89355f', - 'v2.6.3.d28af68', - 'v2.6.3.640e731', - 'v2.6.2.31c0e8f', - 'v2.6.1.7c3edde', - 'v2.6.0.f7afa9a', - 'v2.5.23.bf958ed', - 'v2.5.22.d1fa27d', - 'v2.5.21.447533a', - 'v2.5.20.4c97351', - 'v2.5.19.f9876cf', - 'v2.5.19.d5cd6f8', - 'v2.5.18.89ebafc', - 'v2.5.17.b4b2fd6', - 'v2.5.16.f81d3b0', - 'v2.5.15.79da236', - 'v2.5.14.f2ee0df', - 'v2.5.13.295278b', - 'v2.5.13.1a06f88', - 'v2.5.12.aa184e6', - 'v2.5.11.8e2a3e5', - 'v2.5.10.0fc5c9b', - 'v2.5.9.936260f', - 'v2.5.8.6485f03', - 'v2.5.7.f77c87d', - 'v2.5.6.d55c08d', - 'v2.5.5.e182ae7', - 'v2.5.4.8d288d5', - 'v2.5.3.a70d5ee', - 'v2.5.2.771cb52', - 'v2.5.1.c13b44b', - 'v2.5.0.e470619', - 'v2.5.0.d6dac17', - 'v2.5.0.ab7de7f', - 'v2.5.0.33eb073', - 'v2.5.0.9e55e6b', - 'v2.5.0.9ac0e26', - 'v2.4.3.efc27f2', - 'v2.4.3.91d6612', - 'v2.4.2.5b45303', - 'v2.4.1.394e0e1', - 'v2.4.0.46d7b82', - 'v2.3.15.deb7c27', - 'v2.3.14.64531fa', - 'v2.3.13.83f5ba0', - 'v2.3.12.24458a7', - 'v2.3.11.2740a56', - 'v2.3.10.d19607b', - 'v2.3.9.f06c56a', - 'v2.3.8.d490a33', - 'v2.3.7.30fbcab', - 'v2.3.6.7a3570a', - 'v2.3.5.2f9b68e', - 'v2.3.4.ea61808', - 'v2.3.3.8187fa7', - 'v2.3.2.63df972', - 'v2.3.1.4fa7f5a', - 'v2.3.0.5f47ca1', - 'v2.2.24.e6a2c06', - 'v2.2.23.5672e68', - 'v2.2.22.404d0dd', - 'v2.2.21.7f7c5cb', - 'v2.2.20.af5ac32', - 'v2.2.19.8f6a283', - 'v2.2.18.e9bde80', - 'v2.2.17.dbac2b1', - 'v2.2.16.1c6acfd', - 'v2.2.15.31c4693', - 'v2.2.14.57542ce', - 'v2.2.13.f570204', - 'v2.2.12.092e6f2', - 'v2.2.11.10265aa', - 'v2.2.10.7cebd79', - 'v2.2.9.47301a5', - 'v2.2.8.61f6fb2', - 'v2.2.7.e8970ad', - 'v2.2.6.b53cb38', - 'v2.2.5.8255128', - 'v2.2.4.3bcab0e', - 'v2.2.3.282cc0b', - 'v2.2.2.f35c7be', - 'v2.2.1.fb5f2e4', - 'v2.2.0.9f6584b', - 'v2.1.23.04bbdc6', - 'v2.1.22.191a69d', - 'v2.1.21.97d7a89', - 'v2.1.20.470363d', - 'v2.1.19.eb7025f', - 'v2.1.18.de53280', - 'v2.1.17.7ca2e81', - 'v2.1.16.a2c5b92', - 'v2.1.15.cd78723', - 'v2.1.14.99a31c1', - 'v2.1.13.7475c86', - 'v2.1.12.7711b03', - 'v2.1.11.5ec624d', - 'v2.1.10.7ef12c7', - 'v2.1.9.d43ddc9', - 'v2.1.8.ee971e3', - 'v2.1.7.242f880', - 'v2.1.6.5679a82', - 'v2.1.5.23272da', - 'v2.1.4.958d2cf', - 'v2.1.3.8c68d88', - 'v2.1.2.6d20215', - 'v2.1.1.dc2ca9c', - 'v2.1.0.331a1af', - 'v2.0.23.7bb281d', - 'v2.0.22.fbfd0f1', - 'v2.0.21.83e6cea', - 'v2.0.20.7100416', - 'v2.0.19.3209aea', - 'v2.0.18.1a7991c', - 'v2.0.17.5d1c06b', - 'v2.0.16.2242b68', - 'v2.0.15.aafbde0', - 'v2.0.14.2baaad8', - 'v2.0.13.7e27729', - 'v2.0.12.2400dd4', - 'v2.0.11.8914d1a', - 'v2.0.10.e09b12c', - 'v2.0.9.6ea0963', - 'v2.0.8.090e166', - 'v2.0.7.91ff7b9', - 'v2.0.6.97fd5cf', - 'v2.0.5.65e8209', - 'v2.0.4.5417671', - 'v2.0.3.09fe616', - 'v2.0.2.8146e84', - 'v2.0.1.ad05b91', - 'v2.0.0.18ab874', - 'v1.3.48.82bcd39', - 'v1.3.47.05147c0', - 'v1.3.46.d4ea956', - 'v1.3.45.b0d0552', - 'v1.3.44.4fa8d02', - 'v1.3.43.aae9d2f', - 'v1.3.42.9bd9252', - 'v1.3.41.80ddb81', - 'v1.3.40.e87ecc2', - 'v1.3.39.ddc3727', - 'v1.3.38.1253abd', - 'v1.3.37.97712a9', - 'v1.3.36.dd720f2', - 'v1.3.36.64f852e', - 'v1.3.36.7e03019', - 'v1.3.35.3251cd5', - 'v1.3.34.401b5d9', - 'v1.3.33.ab0095c', - 'v1.3.32.7e6c22f', - 'v1.3.31.0084643', - 'v1.3.30.9fe2ddb', - 'v1.3.29.7afc149', - 'v1.3.28.41f9541', - 'v1.3.27.c88ba58', - 'v1.3.26.0010231', - 'v1.3.25.85f46d3', - 'v1.3.24.dff6915', - 'v1.3.23.5462d84', - 'v1.3.22.c725a6b', - 'v1.3.21.cf00ac5', - 'v1.3.20.9a5ff93', - 'v1.3.19.3c6a2f7', - 'v1.3.17.c9822de', - 'v1.3.16.97899ae', - 'v1.3.15.432d067', - 'v1.3.13.71a43a9', - 'v1.3.12.6306c53', - 'v1.3.11.0411401', - 'v1.3.10.cc2a84a', - 'v1.3.10.4df0e91', - 'v1.3.9.92185e7', - 'v1.3.8.90df7c2', - 'v1.3.7.bb22b6e', - 'v1.3.6.f511bab', - 'v1.3.5.e5b19fd', - 'v1.3.4.2b20bf3', - 'v1.3.3.2fe124e', - 'v1.2.testing1', - 'v1.2.65.0adc5ce', - 'v1.2.64.fc48fcd', - 'v1.2.63.9879494', - 'v1.2.62.3ddd74e', - 'v1.2.61.d551c17', - 'v1.2.60.ab959de', - 'v1.2.59.d81c1c0', - 'v1.2.58.6af1822', - 'v1.2.57.f7c6955', - 'v1.2.56.596a73c', - 'v1.2.55.9db7c62', - 'v1.2.54.288f2be', - 'v1.2.53.19c1f9f', - 'v1.2.52.b63802c', - 'v1.2.51.f9ff06b', - 'v1.2.50.41dcfdd', - 'v1.2.49.5354c49', - 'v1.2.48.371335e', - 'v1.2.47', - 'v1.2.46.dce2fe4', - 'v1.2.46.9d21e58', - 'v1.2.45.b674054', - 'v1.2.44.f2c9c55', - 'v1.2.43.a405d81', - 'v1.2.42.2759c8d', - 'v1.2.41.32f3682', - 'v1.2.39.06892c4', - 'v1.2.38.cf4e508', - 'v1.2.38.451b085', - 'v1.2.36', - 'v1.2.30.80e4bc6', - 'v1.2.29.6c95659', -] as const + "v2.7.15.567b8ea", + "v2.7.14.e959000", + "v2.7.13.597fa0b", + "v2.7.12.45f15b8", + "v2.7.11.ee68575", + "v2.7.10.94d4bdf", + "v2.7.9.70724be", + "v2.7.8.a0c0388", + "v2.7.7.5ae4ff9", + "v2.7.6.834c3c5", + "v2.7.5.ddd1499", + "v2.7.4.c1f4f79", + "v2.7.3.cf574c7", + "v2.7.2.f6d3782", + "v2.7.1.f35ca81", + "v2.7.0.705515a", + "v2.7.0.195b7cc", + "v2.6.13.0561f2c", + "v2.6.12.9861e82", + "v2.6.11.60ec05e", + "v2.6.10.9ce4455", + "v2.6.9.f223b8a", + "v2.6.8.ef9d0d7", + "v2.6.7.2d6181f", + "v2.6.6.54c1423", + "v2.6.5.fc3d9f2", + "v2.6.4.b89355f", + "v2.6.3.d28af68", + "v2.6.3.640e731", + "v2.6.2.31c0e8f", + "v2.6.1.7c3edde", + "v2.6.0.f7afa9a", + "v2.5.23.bf958ed", + "v2.5.22.d1fa27d", + "v2.5.21.447533a", + "v2.5.20.4c97351", + "v2.5.19.f9876cf", + "v2.5.19.d5cd6f8", + "v2.5.18.89ebafc", + "v2.5.17.b4b2fd6", + "v2.5.16.f81d3b0", + "v2.5.15.79da236", + "v2.5.14.f2ee0df", + "v2.5.13.295278b", + "v2.5.13.1a06f88", + "v2.5.12.aa184e6", + "v2.5.11.8e2a3e5", + "v2.5.10.0fc5c9b", + "v2.5.9.936260f", + "v2.5.8.6485f03", + "v2.5.7.f77c87d", + "v2.5.6.d55c08d", + "v2.5.5.e182ae7", + "v2.5.4.8d288d5", + "v2.5.3.a70d5ee", + "v2.5.2.771cb52", + "v2.5.1.c13b44b", + "v2.5.0.e470619", + "v2.5.0.d6dac17", + "v2.5.0.ab7de7f", + "v2.5.0.33eb073", + "v2.5.0.9e55e6b", + "v2.5.0.9ac0e26", + "v2.4.3.efc27f2", + "v2.4.3.91d6612", + "v2.4.2.5b45303", + "v2.4.1.394e0e1", + "v2.4.0.46d7b82", + "v2.3.15.deb7c27", + "v2.3.14.64531fa", + "v2.3.13.83f5ba0", + "v2.3.12.24458a7", + "v2.3.11.2740a56", + "v2.3.10.d19607b", + "v2.3.9.f06c56a", + "v2.3.8.d490a33", + "v2.3.7.30fbcab", + "v2.3.6.7a3570a", + "v2.3.5.2f9b68e", + "v2.3.4.ea61808", + "v2.3.3.8187fa7", + "v2.3.2.63df972", + "v2.3.1.4fa7f5a", + "v2.3.0.5f47ca1", + "v2.2.24.e6a2c06", + "v2.2.23.5672e68", + "v2.2.22.404d0dd", + "v2.2.21.7f7c5cb", + "v2.2.20.af5ac32", + "v2.2.19.8f6a283", + "v2.2.18.e9bde80", + "v2.2.17.dbac2b1", + "v2.2.16.1c6acfd", + "v2.2.15.31c4693", + "v2.2.14.57542ce", + "v2.2.13.f570204", + "v2.2.12.092e6f2", + "v2.2.11.10265aa", + "v2.2.10.7cebd79", + "v2.2.9.47301a5", + "v2.2.8.61f6fb2", + "v2.2.7.e8970ad", + "v2.2.6.b53cb38", + "v2.2.5.8255128", + "v2.2.4.3bcab0e", + "v2.2.3.282cc0b", + "v2.2.2.f35c7be", + "v2.2.1.fb5f2e4", + "v2.2.0.9f6584b", + "v2.1.23.04bbdc6", + "v2.1.22.191a69d", + "v2.1.21.97d7a89", + "v2.1.20.470363d", + "v2.1.19.eb7025f", + "v2.1.18.de53280", + "v2.1.17.7ca2e81", + "v2.1.16.a2c5b92", + "v2.1.15.cd78723", + "v2.1.14.99a31c1", + "v2.1.13.7475c86", + "v2.1.12.7711b03", + "v2.1.11.5ec624d", + "v2.1.10.7ef12c7", + "v2.1.9.d43ddc9", + "v2.1.8.ee971e3", + "v2.1.7.242f880", + "v2.1.6.5679a82", + "v2.1.5.23272da", + "v2.1.4.958d2cf", + "v2.1.3.8c68d88", + "v2.1.2.6d20215", + "v2.1.1.dc2ca9c", + "v2.1.0.331a1af", + "v2.0.23.7bb281d", + "v2.0.22.fbfd0f1", + "v2.0.21.83e6cea", + "v2.0.20.7100416", + "v2.0.19.3209aea", + "v2.0.18.1a7991c", + "v2.0.17.5d1c06b", + "v2.0.16.2242b68", + "v2.0.15.aafbde0", + "v2.0.14.2baaad8", + "v2.0.13.7e27729", + "v2.0.12.2400dd4", + "v2.0.11.8914d1a", + "v2.0.10.e09b12c", + "v2.0.9.6ea0963", + "v2.0.8.090e166", + "v2.0.7.91ff7b9", + "v2.0.6.97fd5cf", + "v2.0.5.65e8209", + "v2.0.4.5417671", + "v2.0.3.09fe616", + "v2.0.2.8146e84", + "v2.0.1.ad05b91", + "v2.0.0.18ab874", + "v1.3.48.82bcd39", + "v1.3.47.05147c0", + "v1.3.46.d4ea956", + "v1.3.45.b0d0552", + "v1.3.44.4fa8d02", + "v1.3.43.aae9d2f", + "v1.3.42.9bd9252", + "v1.3.41.80ddb81", + "v1.3.40.e87ecc2", + "v1.3.39.ddc3727", + "v1.3.38.1253abd", + "v1.3.37.97712a9", + "v1.3.36.dd720f2", + "v1.3.36.64f852e", + "v1.3.36.7e03019", + "v1.3.35.3251cd5", + "v1.3.34.401b5d9", + "v1.3.33.ab0095c", + "v1.3.32.7e6c22f", + "v1.3.31.0084643", + "v1.3.30.9fe2ddb", + "v1.3.29.7afc149", + "v1.3.28.41f9541", + "v1.3.27.c88ba58", + "v1.3.26.0010231", + "v1.3.25.85f46d3", + "v1.3.24.dff6915", + "v1.3.23.5462d84", + "v1.3.22.c725a6b", + "v1.3.21.cf00ac5", + "v1.3.20.9a5ff93", + "v1.3.19.3c6a2f7", + "v1.3.17.c9822de", + "v1.3.16.97899ae", + "v1.3.15.432d067", + "v1.3.13.71a43a9", + "v1.3.12.6306c53", + "v1.3.11.0411401", + "v1.3.10.cc2a84a", + "v1.3.10.4df0e91", + "v1.3.9.92185e7", + "v1.3.8.90df7c2", + "v1.3.7.bb22b6e", + "v1.3.6.f511bab", + "v1.3.5.e5b19fd", + "v1.3.4.2b20bf3", + "v1.3.3.2fe124e", + "v1.2.testing1", + "v1.2.65.0adc5ce", + "v1.2.64.fc48fcd", + "v1.2.63.9879494", + "v1.2.62.3ddd74e", + "v1.2.61.d551c17", + "v1.2.60.ab959de", + "v1.2.59.d81c1c0", + "v1.2.58.6af1822", + "v1.2.57.f7c6955", + "v1.2.56.596a73c", + "v1.2.55.9db7c62", + "v1.2.54.288f2be", + "v1.2.53.19c1f9f", + "v1.2.52.b63802c", + "v1.2.51.f9ff06b", + "v1.2.50.41dcfdd", + "v1.2.49.5354c49", + "v1.2.48.371335e", + "v1.2.47", + "v1.2.46.dce2fe4", + "v1.2.46.9d21e58", + "v1.2.45.b674054", + "v1.2.44.f2c9c55", + "v1.2.43.a405d81", + "v1.2.42.2759c8d", + "v1.2.41.32f3682", + "v1.2.39.06892c4", + "v1.2.38.cf4e508", + "v1.2.38.451b085", + "v1.2.36", + "v1.2.30.80e4bc6", + "v1.2.29.6c95659" +] as const; -export type FirmwareVersion = (typeof VERSIONS)[number] +export type FirmwareVersion = typeof VERSIONS[number]; diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 3217d9a..50106d3 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -13,9 +13,9 @@ export default function Dashboard() { const profiles = useQuery(api.profiles.list) const removeProfile = useMutation(api.profiles.remove) const [isEditing, setIsEditing] = useState(false) - const [editingProfile, setEditingProfile] = useState | null>( - null - ) + const [editingProfile, setEditingProfile] = useState< + Doc<'profiles'> | undefined + >(undefined) const handleEdit = (profile: Doc<'profiles'>) => { setEditingProfile(profile) @@ -23,7 +23,7 @@ export default function Dashboard() { } const handleCreate = () => { - setEditingProfile(null) + setEditingProfile(undefined) setIsEditing(true) } diff --git a/src/pages/ProfileDetail.tsx b/src/pages/ProfileDetail.tsx index a04f05b..e154501 100644 --- a/src/pages/ProfileDetail.tsx +++ b/src/pages/ProfileDetail.tsx @@ -12,7 +12,7 @@ import { TARGETS } from '../constants/targets' export default function ProfileDetail() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() - const triggerFlash = useMutation(api.builds.triggerFlash) + const triggerBuildViaProfile = useMutation(api.builds.triggerBuildViaProfile) const profile = useQuery( api.profiles.get, id ? { id: id as Id<'profiles'> } : 'skip' @@ -81,11 +81,11 @@ export default function ProfileDetail() { if (!selectedTarget || !id) return try { - const profileTargetId = await triggerFlash({ + await triggerBuildViaProfile({ profileId: id as Id<'profiles'>, target: selectedTarget, }) - navigate(`/profiles/${id}/flash/${profileTargetId}`) + navigate(`/profiles/${id}/flash/${selectedTarget}`) } catch (error) { toast.error('Failed to start flash', { description: String(error), diff --git a/src/pages/ProfileFlash.tsx b/src/pages/ProfileFlash.tsx index 76f02f7..be887df 100644 --- a/src/pages/ProfileFlash.tsx +++ b/src/pages/ProfileFlash.tsx @@ -13,16 +13,14 @@ import { api } from '../../convex/_generated/api' import type { Id } from '../../convex/_generated/dataModel' export default function ProfileFlash() { - const { id, profileTargetId } = useParams<{ + const { id, target } = useParams<{ id: string - profileTargetId: string + target: string }>() const data = useQuery( api.profiles.getProfileTarget, - profileTargetId - ? { profileTargetId: profileTargetId as Id<'profileTargets'> } - : 'skip' + id && target ? { profileId: id as Id<'profiles'>, target } : 'skip' ) if (data === undefined) { @@ -52,7 +50,6 @@ export default function ProfileFlash() { } const build = data.build - const profileTarget = data.profileTarget const getStatusColor = (status: string) => { if (status === 'success') return 'text-green-400' @@ -90,7 +87,7 @@ export default function ProfileFlash() {
{getStatusIcon(build.status)}
-

{profileTarget.target}

+

{target}

{humanizeStatus(build.status)}