From 8472bf9a1f51717cb1301bbd134875d4b6fdb2a4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 31 Oct 2024 23:58:14 +0100 Subject: [PATCH] Filtering in assets table (#101) * Added transcodeOutcome job * Added assets page * feat: Added table for assets * Added filtering to assets page --- packages/api/src/db/index.ts | 6 +- .../api/src/db/migrations/2024_10_26_init.ts | 42 +++++-- packages/api/src/db/types.ts | 21 +++- packages/api/src/errors.ts | 9 +- packages/api/src/index.ts | 11 +- packages/api/src/repositories/assets.ts | 54 ++++++++ packages/api/src/repositories/groups.ts | 21 ++++ packages/api/src/repositories/jobs.ts | 16 +-- packages/api/src/repositories/playables.ts | 9 ++ packages/api/src/routes/assets.ts | 55 ++++++++ packages/api/src/routes/jobs.ts | 23 ++-- packages/api/src/routes/storage.ts | 4 +- packages/api/src/routes/user.ts | 6 +- packages/api/src/types.ts | 27 ++++ packages/api/src/workers/index.ts | 9 ++ packages/api/src/workers/outcome.ts | 45 +++++++ packages/app/src/App.tsx | 7 +- packages/app/src/components/AssetsTable.tsx | 102 +++++++++++++++ .../components/{JobTag.tsx => ColorTag.tsx} | 16 +-- packages/app/src/components/JobOverview.tsx | 4 - packages/app/src/components/JobsFilter.tsx | 25 ---- packages/app/src/components/JobsList.tsx | 2 - packages/app/src/components/Sidebar.tsx | 9 +- .../app/src/components/TableHeadSorter.tsx | 51 ++++++++ .../app/src/components/TablePagination.tsx | 63 ++++++++++ .../app/src/components/TransitionNavLink.tsx | 16 +-- packages/app/src/components/ui/pagination.tsx | 117 ++++++++++++++++++ packages/app/src/hooks/useLoadTransition.ts | 19 +++ packages/app/src/hooks/useTableFilter.ts | 49 ++++++++ packages/app/src/lib/helpers.ts | 2 +- packages/app/src/pages/AssetsPage.tsx | 93 ++++++++++++++ packages/artisan/src/index.ts | 26 +--- packages/artisan/src/workers/index.ts | 24 ++++ packages/artisan/src/workers/package.ts | 75 ++++++++--- packages/artisan/src/workers/transcode.ts | 111 ++++++++--------- packages/bolt/src/queue.ts | 20 ++- packages/bolt/src/types.ts | 4 + packages/bolt/src/worker.ts | 26 +++- packages/stitcher/src/vast.ts | 2 +- 39 files changed, 1028 insertions(+), 193 deletions(-) create mode 100644 packages/api/src/repositories/groups.ts create mode 100644 packages/api/src/repositories/playables.ts create mode 100644 packages/api/src/routes/assets.ts create mode 100644 packages/api/src/workers/index.ts create mode 100644 packages/api/src/workers/outcome.ts create mode 100644 packages/app/src/components/AssetsTable.tsx rename packages/app/src/components/{JobTag.tsx => ColorTag.tsx} (60%) create mode 100644 packages/app/src/components/TableHeadSorter.tsx create mode 100644 packages/app/src/components/TablePagination.tsx create mode 100644 packages/app/src/components/ui/pagination.tsx create mode 100644 packages/app/src/hooks/useLoadTransition.ts create mode 100644 packages/app/src/hooks/useTableFilter.ts create mode 100644 packages/app/src/pages/AssetsPage.tsx create mode 100644 packages/artisan/src/workers/index.ts diff --git a/packages/api/src/db/index.ts b/packages/api/src/db/index.ts index 34cbe760..15f53f75 100644 --- a/packages/api/src/db/index.ts +++ b/packages/api/src/db/index.ts @@ -1,5 +1,5 @@ import { Kysely, PostgresDialect } from "kysely"; -import { Pool } from "pg"; +import { Pool, types } from "pg"; import { env } from "../env"; import type { KyselyDatabase } from "./types.ts"; @@ -8,3 +8,7 @@ export const db = new Kysely({ pool: new Pool({ connectionString: env.DATABASE }), }), }); + +types.setTypeParser(/* INT8_TYPE_ID= */ 20, (val) => { + return parseInt(val, 10); +}); diff --git a/packages/api/src/db/migrations/2024_10_26_init.ts b/packages/api/src/db/migrations/2024_10_26_init.ts index 9d604bd3..8d79aea2 100644 --- a/packages/api/src/db/migrations/2024_10_26_init.ts +++ b/packages/api/src/db/migrations/2024_10_26_init.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Kysely } from "kysely"; +import { Kysely, sql } from "kysely"; export async function up(db: Kysely) { await db.schema @@ -10,7 +10,36 @@ export async function up(db: Kysely) { .addColumn("username", "text", (col) => col.notNull()) .addColumn("password", "text", (col) => col.notNull()) // Settings - .addColumn("autoRefresh", "boolean", (col) => col.defaultTo(true)) + .addColumn("autoRefresh", "boolean", (col) => col.notNull().defaultTo(true)) + .execute(); + + await db.schema + .createTable("groups") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("name", "text", (col) => col.notNull().unique()) + .execute(); + + await db.schema + .createTable("assets") + .addColumn("id", "uuid", (col) => col.primaryKey()) + .addColumn("groupId", "integer", (col) => + col.references("groups.id").onDelete("cascade"), + ) + .addColumn("createdAt", "timestamp", (col) => + col.notNull().defaultTo(sql`now()`), + ) + .execute(); + + await db.schema + .createTable("playables") + .addColumn("assetId", "uuid", (col) => + col.references("assets.id").onDelete("cascade"), + ) + .addColumn("name", "text", (col) => col.notNull()) + .addColumn("createdAt", "timestamp", (col) => + col.notNull().defaultTo(sql`now()`), + ) + .addPrimaryKeyConstraint("id", ["assetId", "name"]) .execute(); await db @@ -20,14 +49,11 @@ export async function up(db: Kysely) { password: await Bun.password.hash("admin"), }) .execute(); - - await db.schema - .createTable("assets") - .addColumn("id", "uuid", (col) => col.primaryKey()) - .execute(); } export async function down(db: Kysely) { await db.schema.dropTable("users").execute(); - await db.schema.dropTable("assets"); + await db.schema.dropTable("groups").execute(); + await db.schema.dropTable("assets").execute(); + await db.schema.dropTable("playables").execute(); } diff --git a/packages/api/src/db/types.ts b/packages/api/src/db/types.ts index f39da297..16c028eb 100644 --- a/packages/api/src/db/types.ts +++ b/packages/api/src/db/types.ts @@ -1,8 +1,10 @@ -import type { Generated, Insertable, Updateable } from "kysely"; +import type { Generated, Insertable, Updateable, ColumnType } from "kysely"; export interface KyselyDatabase { users: UsersTable; + groups: GroupsTable; assets: AssetsTable; + playables: PlayablesTable; } export interface UsersTable { @@ -14,8 +16,25 @@ export interface UsersTable { export type UserUpdate = Updateable; +export interface GroupsTable { + id: Generated; + name: string; +} + +export type GroupInsert = Insertable; + export interface AssetsTable { id: string; + groupId: number | null; + createdAt: ColumnType; } export type AssetInsert = Insertable; + +export interface PlayablesTable { + assetId: string; + name: string; + createdAt: ColumnType; +} + +export type PlayableInsert = Insertable; diff --git a/packages/api/src/errors.ts b/packages/api/src/errors.ts index c86bd242..5ab0a705 100644 --- a/packages/api/src/errors.ts +++ b/packages/api/src/errors.ts @@ -77,7 +77,14 @@ export const errors = () => function mapValidationError( error: ValidationError, ): ApiError<"ERR_VALIDATION"> { - const first = error.validator.Errors(error.value).First(); + const first = error.validator?.Errors(error.value).First(); + if (!first) { + return { + type: "ERR_VALIDATION", + path: "/", + fail: error.message, + }; + } return { type: "ERR_VALIDATION", path: first.path, diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 0b5df21a..771faab9 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -6,6 +6,7 @@ import { token } from "./routes/token"; import { user } from "./routes/user"; import { jobs } from "./routes/jobs"; import { storage } from "./routes/storage"; +import { assets } from "./routes/assets"; import { errors } from "./errors"; import { LangCodeSchema, @@ -18,8 +19,13 @@ import { JobSchema, StorageFolderSchema, StorageFileSchema, + AssetSchema, + GroupSchema, } from "./types"; +// Import workers and they'll start running immediately. +import "./workers"; + export type App = typeof app; const app = new Elysia() @@ -90,11 +96,14 @@ const app = new Elysia() Job: JobSchema, StorageFolder: StorageFolderSchema, StorageFile: StorageFileSchema, + Asset: AssetSchema, + Group: GroupSchema, }) .use(token) .use(user) .use(jobs) - .use(storage); + .use(storage) + .use(assets); app.on("stop", () => { process.exit(0); diff --git a/packages/api/src/repositories/assets.ts b/packages/api/src/repositories/assets.ts index dc9a2daa..176ee7d6 100644 --- a/packages/api/src/repositories/assets.ts +++ b/packages/api/src/repositories/assets.ts @@ -4,3 +4,57 @@ import type { AssetInsert } from "../db/types"; export async function createAsset(fields: AssetInsert) { return await db.insertInto("assets").values(fields).executeTakeFirstOrThrow(); } + +export async function getAssetsCount() { + const { count } = await db + .selectFrom("assets") + .select((eb) => eb.fn.count("id").as("count")) + .executeTakeFirstOrThrow(); + return count; +} + +export async function getAssets(filter: { + page: number; + perPage: number; + orderBy: string; + direction: string; +}) { + const orderBy = mapOrderBy(filter.orderBy); + const direction = mapDirection(filter.direction); + + const assets = await db + .selectFrom("assets") + .leftJoin("playables", "playables.assetId", "assets.id") + .select(({ fn }) => [ + "assets.id", + "assets.groupId", + "assets.createdAt", + fn.count("playables.assetId").as("playablesCount"), + ]) + .groupBy("assets.id") + .limit(filter.perPage) + .offset((filter.page - 1) * filter.perPage) + .orderBy(orderBy, direction) + .execute(); + + return assets.map((asset) => { + return { + ...asset, + name: asset.id, + }; + }); +} + +function mapOrderBy(orderBy: string) { + if (orderBy === "name") { + return "id"; + } + if (orderBy === "createdAt") { + return "createdAt"; + } + return "createdAt"; +} + +function mapDirection(direction: string) { + return direction === "asc" || direction === "desc" ? direction : "desc"; +} diff --git a/packages/api/src/repositories/groups.ts b/packages/api/src/repositories/groups.ts new file mode 100644 index 00000000..312148ef --- /dev/null +++ b/packages/api/src/repositories/groups.ts @@ -0,0 +1,21 @@ +import { db } from "../db"; + +export async function getGroups() { + return await db.selectFrom("groups").select(["id", "name"]).execute(); +} + +export async function getOrCreateGroup(name: string) { + let group = await db + .selectFrom("groups") + .select(["id", "name"]) + .where("name", "=", name) + .executeTakeFirst(); + if (!group) { + group = await db + .insertInto("groups") + .values({ name }) + .returning(["id", "name"]) + .executeTakeFirstOrThrow(); + } + return group; +} diff --git a/packages/api/src/repositories/jobs.ts b/packages/api/src/repositories/jobs.ts index ad9da59a..08ef3edb 100644 --- a/packages/api/src/repositories/jobs.ts +++ b/packages/api/src/repositories/jobs.ts @@ -3,6 +3,7 @@ import { packageQueue, ffmpegQueue, ffprobeQueue, + outcomeQueue, flowProducer, } from "bolt"; import { Job as RawJob } from "bullmq"; @@ -10,7 +11,13 @@ import { isRecordWithNumbers } from "../utils/type-guard"; import type { JobNode, JobState, Queue } from "bullmq"; import type { Job } from "../types"; -const allQueus = [transcodeQueue, packageQueue, ffmpegQueue, ffprobeQueue]; +const allQueus = [ + transcodeQueue, + packageQueue, + ffmpegQueue, + ffprobeQueue, + outcomeQueue, +]; function findQueueByName(name: string): Queue { const queue = allQueus.find((queue) => queue.name === name); @@ -152,12 +159,6 @@ async function formatJobNode(node: JobNode): Promise { ? job.finishedOn - processedOn : undefined; - let tag: string | undefined; - const potentialTag = job.data?.tag; - if (typeof potentialTag === "string") { - tag = potentialTag; - } - let progress: Record | undefined; if (isRecordWithNumbers(job.progress)) { progress = job.progress; @@ -175,7 +176,6 @@ async function formatJobNode(node: JobNode): Promise { inputData: JSON.stringify(job.data), outputData: job.returnvalue ? JSON.stringify(job.returnvalue) : undefined, failedReason, - tag, children: jobChildren, }; } diff --git a/packages/api/src/repositories/playables.ts b/packages/api/src/repositories/playables.ts new file mode 100644 index 00000000..011a7450 --- /dev/null +++ b/packages/api/src/repositories/playables.ts @@ -0,0 +1,9 @@ +import { db } from "../db"; +import type { PlayableInsert } from "../db/types"; + +export async function createPlayable(fields: PlayableInsert) { + return await db + .insertInto("playables") + .values(fields) + .executeTakeFirstOrThrow(); +} diff --git a/packages/api/src/routes/assets.ts b/packages/api/src/routes/assets.ts new file mode 100644 index 00000000..70ce8789 --- /dev/null +++ b/packages/api/src/routes/assets.ts @@ -0,0 +1,55 @@ +import { Elysia, t } from "elysia"; +import { authUser } from "./token"; +import { getAssets, getAssetsCount } from "../repositories/assets"; +import { getGroups } from "../repositories/groups"; +import { AssetSchema } from "../types"; + +export const assets = new Elysia() + .use(authUser) + .get( + "/assets", + async ({ query }) => { + const assets = await getAssets(query); + + const count = await getAssetsCount(); + const totalPages = Math.ceil(count / query.perPage); + + return { + page: query.page, + totalPages, + assets, + }; + }, + { + detail: { + summary: "Get all assets", + tags: ["Assets"], + }, + + query: t.Object({ + page: t.Number(), + perPage: t.Number(), + orderBy: t.String(), + direction: t.String(), + }), + response: { + 200: t.Object({ + page: t.Number(), + totalPages: t.Number(), + assets: t.Array(AssetSchema), + }), + }, + }, + ) + .get( + "/groups", + async () => { + return await getGroups(); + }, + { + detail: { + summary: "Get all groups", + tags: ["Assets"], + }, + }, + ); diff --git a/packages/api/src/routes/jobs.ts b/packages/api/src/routes/jobs.ts index 743555d4..c465d31d 100644 --- a/packages/api/src/routes/jobs.ts +++ b/packages/api/src/routes/jobs.ts @@ -1,7 +1,13 @@ import { Elysia, t } from "elysia"; import { randomUUID } from "crypto"; import { DeliberateError } from "../errors"; -import { addToQueue, packageQueue, transcodeQueue } from "bolt"; +import { + addToQueue, + packageQueue, + transcodeQueue, + DEFAULT_PACKAGE_NAME, + DEFAULT_SEGMENT_SIZE, +} from "bolt"; import { LangCodeSchema, VideoCodecSchema, @@ -18,7 +24,7 @@ export const jobs = new Elysia() async ({ body }) => { const data = { assetId: randomUUID(), - segmentSize: 2.24, + segmentSize: DEFAULT_SEGMENT_SIZE, ...body, }; const jobId = await addToQueue(transcodeQueue, data, { @@ -112,10 +118,10 @@ export const jobs = new Elysia() "Starts a default package job after a succesful transcode.", }), ), - tag: t.Optional( + group: t.Optional( t.String({ description: - 'Tag a job for a particular purpose, such as "ad". Arbitrary value.', + 'Groups the asset with an arbitrary value, such as "ad"', }), ), }), @@ -130,7 +136,7 @@ export const jobs = new Elysia() "/package", async ({ body }) => { const data = { - name: "hls", + name: DEFAULT_PACKAGE_NAME, ...body, }; const jobId = await addToQueue(packageQueue, data, { @@ -147,8 +153,7 @@ export const jobs = new Elysia() assetId: t.String({ format: "uuid", }), - defaultLanguage: t.Optional(LangCodeSchema), - defaultTextLanguage: t.Optional(LangCodeSchema), + language: t.Optional(LangCodeSchema), segmentSize: t.Optional( t.Number({ description: @@ -186,7 +191,7 @@ export const jobs = new Elysia() tags: ["Jobs"], }, response: { - 200: t.Array(t.Ref(JobSchema)), + 200: t.Array(JobSchema), }, }, ) @@ -211,7 +216,7 @@ export const jobs = new Elysia() fromRoot: t.Optional(t.Boolean()), }), response: { - 200: t.Ref(JobSchema), + 200: JobSchema, }, }, ) diff --git a/packages/api/src/routes/storage.ts b/packages/api/src/routes/storage.ts index 189dd252..70d87e27 100644 --- a/packages/api/src/routes/storage.ts +++ b/packages/api/src/routes/storage.ts @@ -23,7 +23,7 @@ export const storage = new Elysia() take: t.Optional(t.Number()), }), response: { - 200: t.Ref(StorageFolderSchema), + 200: StorageFolderSchema, }, }, ) @@ -42,7 +42,7 @@ export const storage = new Elysia() path: t.String(), }), response: { - 200: t.Ref(StorageFileSchema), + 200: StorageFileSchema, }, }, ); diff --git a/packages/api/src/routes/user.ts b/packages/api/src/routes/user.ts index 7524848b..68c53abc 100644 --- a/packages/api/src/routes/user.ts +++ b/packages/api/src/routes/user.ts @@ -24,7 +24,7 @@ export const user = new Elysia() tags: ["User"], }, response: { - 200: t.Ref(UserSchema), + 200: UserSchema, }, }, ) @@ -45,7 +45,7 @@ export const user = new Elysia() tags: ["User"], }, response: { - 200: t.Ref(UserSettingsSchema), + 200: UserSettingsSchema, }, }, ) @@ -70,7 +70,7 @@ export const user = new Elysia() autoRefresh: t.Optional(t.Boolean()), }), response: { - 200: t.Ref(UserSettingsSchema), + 200: UserSettingsSchema, }, }, ); diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 2b454351..414d725e 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -87,3 +87,30 @@ export const UserSettingsSchema = t.Object( ); export type UserSettings = Static; + +export const AssetSchema = t.Object( + { + id: t.String({ format: "uuid" }), + groupId: t.Nullable(t.Number()), + name: t.String(), + createdAt: t.Date(), + playablesCount: t.Number(), + }, + { + $id: "#/components/schemas/Asset", + }, +); + +export type Asset = Static; + +export const GroupSchema = t.Object( + { + id: t.Number(), + name: t.String(), + }, + { + $id: "#/components/schemas/Group", + }, +); + +export type Group = Static; diff --git a/packages/api/src/workers/index.ts b/packages/api/src/workers/index.ts new file mode 100644 index 00000000..febeac88 --- /dev/null +++ b/packages/api/src/workers/index.ts @@ -0,0 +1,9 @@ +import { runWorkers } from "bolt"; +import { outcomeCallback } from "./outcome"; + +runWorkers([ + { + name: "outcome", + callback: outcomeCallback, + }, +]); diff --git a/packages/api/src/workers/outcome.ts b/packages/api/src/workers/outcome.ts new file mode 100644 index 00000000..fa34fe43 --- /dev/null +++ b/packages/api/src/workers/outcome.ts @@ -0,0 +1,45 @@ +import { addToQueue, packageQueue, DEFAULT_PACKAGE_NAME } from "bolt"; +import { createAsset } from "../repositories/assets"; +import { getOrCreateGroup } from "../repositories/groups"; +import { createPlayable } from "../repositories/playables"; +import type { WorkerCallback, OutcomeData } from "bolt"; + +export const outcomeCallback: WorkerCallback = async ({ job }) => { + switch (job.data.type) { + case "transcode": { + const { data } = job.data; + let groupId: number | undefined; + if (data.group) { + const group = await getOrCreateGroup(data.group); + groupId = group.id; + } + + await createAsset({ + id: data.assetId, + groupId, + }); + + if (data.packageAfter) { + await addToQueue( + packageQueue, + { + assetId: data.assetId, + name: DEFAULT_PACKAGE_NAME, + }, + { + id: [data.assetId, DEFAULT_PACKAGE_NAME], + }, + ); + } + break; + } + case "package": { + const { data } = job.data; + await createPlayable({ + assetId: data.assetId, + name: data.name, + }); + break; + } + } +}; diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 04d2dec8..924a5ed8 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -15,6 +15,7 @@ import { PlayerPage } from "./pages/PlayerPage"; import { StoragePage } from "./pages/StoragePage"; import { LoginPage } from "./pages/LoginPage"; import { SettingsPage } from "./pages/SettingsPage"; +import { AssetsPage } from "./pages/AssetsPage"; import { Loader } from "@/components/Loader"; const queryClient = new QueryClient({ @@ -49,7 +50,11 @@ const router = createBrowserRouter([ children: [ { index: true, - element: , + element: , + }, + { + path: "/assets", + element: , }, { path: "/jobs", diff --git a/packages/app/src/components/AssetsTable.tsx b/packages/app/src/components/AssetsTable.tsx new file mode 100644 index 00000000..f28ab8b4 --- /dev/null +++ b/packages/app/src/components/AssetsTable.tsx @@ -0,0 +1,102 @@ +import { Asset, Group } from "@superstreamer/api/client"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { ColorTag } from "@/components/ColorTag"; +import { Button } from "./ui/button"; +import Ellipsis from "lucide-react/icons/ellipsis"; +import CircleSlash from "lucide-react/icons/circle-slash"; +import FileVideo from "lucide-react/icons/file-video"; +import GroupIcon from "lucide-react/icons/group"; +import { getTimeAgo } from "@/lib/helpers"; +import { TableHeadSorter } from "./TableHeadSorter"; +import { TableFilterValue } from "@/hooks/useTableFilter"; + +type AssetsTableProps = { + assets: Asset[]; + groups: Group[]; + filter: TableFilterValue; + onSort(orderBy: string, direction: string): void; +}; + +export function AssetsTable({ + assets, + groups, + filter, + onSort, +}: AssetsTableProps) { + return ( + + + + + Name + + +
+ +
+
+ +
+ +
+
+ + Created + + +
+
+ + {assets.map((asset) => { + const group = groups.find((group) => group.id === asset.groupId); + return ( + + {asset.name} + +
+ {asset.playablesCount > 0 ? ( + {asset.playablesCount} + ) : ( + + )} +
+
+ +
+ +
+
+ + {getTimeAgo(asset.createdAt)} + + +
+ +
+
+
+ ); + })} +
+
+ ); +} diff --git a/packages/app/src/components/JobTag.tsx b/packages/app/src/components/ColorTag.tsx similarity index 60% rename from packages/app/src/components/JobTag.tsx rename to packages/app/src/components/ColorTag.tsx index c4ba6767..82d27f79 100644 --- a/packages/app/src/components/JobTag.tsx +++ b/packages/app/src/components/ColorTag.tsx @@ -1,22 +1,22 @@ import uniqolor from "uniqolor"; type JobTagProps = { - tag?: string; + value: string; }; -export function JobTag({ tag }: JobTagProps) { - if (!tag) { - return null; - } +export function ColorTag({ value }: JobTagProps) { + let { color } = uniqolor(value, {}); - const { color } = uniqolor(tag, {}); + if (value === "none") { + color = "#cccccc"; + } return ( - {tag} + {value} ); } diff --git a/packages/app/src/components/JobOverview.tsx b/packages/app/src/components/JobOverview.tsx index bf697cbd..848cde94 100644 --- a/packages/app/src/components/JobOverview.tsx +++ b/packages/app/src/components/JobOverview.tsx @@ -2,7 +2,6 @@ import { JobTree } from "@/components/JobTree"; import { JobView } from "@/components/JobView"; import { getShortId } from "@/lib/helpers"; import { useJob } from "@/hooks/useJob"; -import { JobTag } from "@/components/JobTag"; import { Breadcrumb, BreadcrumbItem, @@ -38,9 +37,6 @@ export function JobOverview({ id }: JobOverviewProps) { -
- -
diff --git a/packages/app/src/components/JobsFilter.tsx b/packages/app/src/components/JobsFilter.tsx index 46361966..75782336 100644 --- a/packages/app/src/components/JobsFilter.tsx +++ b/packages/app/src/components/JobsFilter.tsx @@ -1,4 +1,3 @@ -import { JobTag } from "@/components/JobTag"; import { SelectObject } from "./SelectObject"; import type { Job } from "@superstreamer/api/client"; import type { JobsFilterData } from "./types"; @@ -11,16 +10,6 @@ type JobsFilterProps = { }; export function JobsFilter({ allJobs, filter, onChange }: JobsFilterProps) { - const tags = getTags(allJobs).map((tag) => ({ - value: tag, - label: , - })); - - tags.unshift( - { value: undefined, label: "All tags" }, - { value: "none", label: "No tags" }, - ); - const names = getNames(allJobs).map((name) => ({ value: name, label: name, @@ -35,24 +24,10 @@ export function JobsFilter({ allJobs, filter, onChange }: JobsFilterProps) { value={filter.name} onChange={(name) => onChange({ name })} /> - onChange({ tag })} - /> ); } -function getTags(jobs: Job[]) { - return jobs.reduce((acc, job) => { - if (job.tag && !acc.includes(job.tag)) { - acc.push(job.tag); - } - return acc; - }, []); -} - function getNames(jobs: Job[]) { return jobs.reduce((acc, job) => { if (!acc.includes(job.name)) { diff --git a/packages/app/src/components/JobsList.tsx b/packages/app/src/components/JobsList.tsx index 4de36b14..1876cd2d 100644 --- a/packages/app/src/components/JobsList.tsx +++ b/packages/app/src/components/JobsList.tsx @@ -1,7 +1,6 @@ import { TransitionNavLink } from "./TransitionNavLink"; import { JobState } from "./JobState"; import { getDurationStr, getShortId, getTimeAgo } from "@/lib/helpers"; -import { JobTag } from "./JobTag"; import type { Job } from "@superstreamer/api/client"; type JobsListProps = { @@ -31,7 +30,6 @@ export function JobsList({ jobs }: JobsListProps) {
- {job.tag === "default" ? null : }
{getTimeAgo(job.createdOn)}
diff --git a/packages/app/src/components/Sidebar.tsx b/packages/app/src/components/Sidebar.tsx index 397f0327..6007ae5c 100644 --- a/packages/app/src/components/Sidebar.tsx +++ b/packages/app/src/components/Sidebar.tsx @@ -1,11 +1,12 @@ import logo from "../assets/logo-mascotte.png"; import { SidebarTitle } from "./SidebarTitle"; import { TransitionNavLink } from "./TransitionNavLink"; -import Rows3 from "lucide-react/icons/rows-3"; +import Workflow from "lucide-react/icons/workflow"; import Sailboat from "lucide-react/icons/sailboat"; import Play from "lucide-react/icons/play"; import Box from "lucide-react/icons/box"; import Settings2 from "lucide-react/icons/settings-2"; +import Layers from "lucide-react/icons/layers"; import { SidebarNavLink } from "./SidebarNavLink"; import { AccountBadge } from "./AccountBadge"; @@ -26,8 +27,12 @@ export function Sidebar() {
Manage