From 39b80eb0181e65defa6d8891d2e7e81ab6c9840c Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Wed, 7 Sep 2022 20:04:13 -0700 Subject: [PATCH] job manager wip --- apps/mobile/src/types/bindings.ts | 208 +++++++++--------- core/index.ts | 208 +++++++++--------- core/src/job/job_manager.rs | 2 + docs/architecture/nodes.md | 4 + docs/architecture/preview-media.md | 2 +- packages/interface/package.json | 2 + .../src/components/jobs/JobManager.tsx | 99 +++++++++ .../src/components/jobs/RunningJobsWidget.tsx | 7 +- .../src/components/layout/Sidebar.tsx | 19 +- .../src/components/tooltip/Tooltip.tsx | 15 +- packages/ui/package.json | 1 + packages/ui/src/ContextMenu.tsx | 2 +- packages/ui/src/OverlayPanel.tsx | 37 ++++ packages/ui/src/index.ts | 1 + pnpm-lock.yaml | Bin 682966 -> 684107 bytes 15 files changed, 390 insertions(+), 217 deletions(-) create mode 100644 packages/interface/src/components/jobs/JobManager.tsx create mode 100644 packages/ui/src/OverlayPanel.tsx diff --git a/apps/mobile/src/types/bindings.ts b/apps/mobile/src/types/bindings.ts index c5d75d903..134f82081 100644 --- a/apps/mobile/src/types/bindings.ts +++ b/apps/mobile/src/types/bindings.ts @@ -2,136 +2,136 @@ export type Operations = { queries: - { key: ["tags.get", LibraryArgs], result: Tag | null } | + { key: ["library.getStatistics", LibraryArgs], result: Statistics } | + { key: ["locations.indexer_rulesget", LibraryArgs], result: IndexerRule } | + { key: ["jobs.getHistory", LibraryArgs], result: Array } | { key: ["tags.getForFile", LibraryArgs], result: Array } | { key: ["locations.getById", LibraryArgs], result: Location | null } | - { key: ["locations.indexer_rulesget", LibraryArgs], result: IndexerRule } | - { key: ["files.readMetadata", LibraryArgs], result: null } | { key: ["tags.getAll", LibraryArgs], result: Array } | - { key: ["jobs.getHistory", LibraryArgs], result: Array } | - { key: ["getNode"], result: NodeState } | - { key: ["library.getStatistics", LibraryArgs], result: Statistics } | - { key: ["version"], result: string } | - { key: ["jobs.getRunning", LibraryArgs], result: Array } | - { key: ["library.get"], result: Array } | - { key: ["locations.get", LibraryArgs], result: Array } | { key: ["volumes.get"], result: Array } | - { key: ["locations.indexer_ruleslist", LibraryArgs], result: Array } | + { key: ["locations.get", LibraryArgs], result: Array } | { key: ["locations.getExplorerData", LibraryArgs], result: ExplorerData } | - { key: ["tags.getExplorerData", LibraryArgs], result: ExplorerData }, + { key: ["tags.get", LibraryArgs], result: Tag | null } | + { key: ["library.get"], result: Array } | + { key: ["getNode"], result: NodeState } | + { key: ["tags.getExplorerData", LibraryArgs], result: ExplorerData } | + { key: ["version"], result: string } | + { key: ["files.readMetadata", LibraryArgs], result: null } | + { key: ["jobs.getRunning", LibraryArgs], result: Array } | + { key: ["locations.indexer_ruleslist", LibraryArgs], result: Array }, mutations: + { key: ["files.setFavorite", LibraryArgs], result: null } | + { key: ["library.edit", EditLibraryArgs], result: null } | + { key: ["tags.assign", LibraryArgs], result: null } | + { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | + { key: ["files.setNote", LibraryArgs], result: null } | + { key: ["locations.create", LibraryArgs], result: Location } | + { key: ["tags.update", LibraryArgs], result: null } | + { key: ["locations.fullRescan", LibraryArgs], result: null } | + { key: ["locations.quickRescan", LibraryArgs], result: null } | { key: ["locations.indexer_rulesdelete", LibraryArgs], result: null } | { key: ["library.create", string], result: null } | - { key: ["locations.quickRescan", LibraryArgs], result: null } | - { key: ["locations.update", LibraryArgs], result: null } | - { key: ["tags.assign", LibraryArgs], result: null } | - { key: ["locations.fullRescan", LibraryArgs], result: null } | - { key: ["files.delete", LibraryArgs], result: null } | - { key: ["files.setFavorite", LibraryArgs], result: null } | - { key: ["files.setNote", LibraryArgs], result: null } | - { key: ["tags.delete", LibraryArgs], result: null } | - { key: ["tags.update", LibraryArgs], result: null } | { key: ["locations.indexer_rulescreate", LibraryArgs], result: IndexerRule } | - { key: ["locations.create", LibraryArgs], result: Location } | - { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | - { key: ["library.edit", EditLibraryArgs], result: null } | - { key: ["library.delete", string], result: null } | + { key: ["locations.delete", LibraryArgs], result: null } | + { key: ["files.delete", LibraryArgs], result: null } | + { key: ["locations.update", LibraryArgs], result: null } | { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | + { key: ["library.delete", string], result: null } | { key: ["tags.create", LibraryArgs], result: Tag } | - { key: ["locations.delete", LibraryArgs], result: null }, + { key: ["tags.delete", LibraryArgs], result: null }, subscriptions: { key: ["invalidateQuery"], result: InvalidateOperationEvent } | { key: ["jobs.newThumbnail", LibraryArgs], result: string } }; -export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } - -export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } - -export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array } - -export interface LibraryConfig { version: string | null, name: string, description: string } - -export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array } - -export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" - -export interface TagCreateArgs { name: string, color: string } - export interface EditLibraryArgs { id: string, name: string | null, description: string | null } -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" - -export interface GenerateThumbsForLocationArgs { id: number, path: string } - -export interface IndexerRule { id: number, kind: number, name: string, parameters: Array, date_created: string, date_modified: string, locations: Array | null } - -export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } - -export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array | null, indexer_rules: Array | null } - -export interface TagUpdateArgs { id: number, name: string | null, color: string | null } - -export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } - -export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } - -export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } - -export interface LibraryArgs { library_id: string, arg: T } - -export interface InvalidateOperationEvent { key: string, arg: any } - -export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } - -export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } - -export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } - -export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } - -export interface IdentifyUniqueFilesArgs { id: number, path: string } - -export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } - -export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag - -export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } - -export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } - -export interface SetFavoriteArgs { id: number, favorite: boolean } - -export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } - -export interface SetNoteArgs { id: number, note: string | null } - -export interface LocationCreateArgs { path: string, indexer_rules_ids: Array } - -export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } - -export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } - -export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } - export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } -export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } +export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array } -export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File +export interface TagCreateArgs { name: string, color: string } -export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null } - -export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } - -export interface IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null } - -export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } +export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } +export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } + +export interface LibraryArgs { library_id: string, arg: T } + +export interface IndexerRule { id: number, kind: number, name: string, parameters: Array, date_created: string, date_modified: string, locations: Array | null } + +export interface ExplorerData { context: ExplorerContext, items: Array } + +export interface InvalidateOperationEvent { key: string, arg: any } + +export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } + +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } + +export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } + +export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } + +export interface IdentifyUniqueFilesArgs { id: number, path: string } + +export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } + +export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } + +export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } + +export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } + +export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" + +export interface SetFavoriteArgs { id: number, favorite: boolean } + +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } + +export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null } + +export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File + +export interface SetNoteArgs { id: number, note: string | null } + +export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag + +export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } + +export interface LibraryConfig { version: string | null, name: string, description: string } + +export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, metadata: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } + +export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } + +export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array | null, indexer_rules: Array | null } + +export interface IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null } + +export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array } + +export interface JobReport { id: string, name: string, data: Array | null, metadata: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } + +export interface GenerateThumbsForLocationArgs { id: number, path: string } + +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" + +export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } + export interface ConfigMetadata { version: string | null } +export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } + +export interface LocationCreateArgs { path: string, indexer_rules_ids: Array } + +export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } + export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean } -export interface ExplorerData { context: ExplorerContext, items: Array } +export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } + +export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } + +export interface TagUpdateArgs { id: number, name: string | null, color: string | null } diff --git a/core/index.ts b/core/index.ts index c5d75d903..134f82081 100644 --- a/core/index.ts +++ b/core/index.ts @@ -2,136 +2,136 @@ export type Operations = { queries: - { key: ["tags.get", LibraryArgs], result: Tag | null } | + { key: ["library.getStatistics", LibraryArgs], result: Statistics } | + { key: ["locations.indexer_rulesget", LibraryArgs], result: IndexerRule } | + { key: ["jobs.getHistory", LibraryArgs], result: Array } | { key: ["tags.getForFile", LibraryArgs], result: Array } | { key: ["locations.getById", LibraryArgs], result: Location | null } | - { key: ["locations.indexer_rulesget", LibraryArgs], result: IndexerRule } | - { key: ["files.readMetadata", LibraryArgs], result: null } | { key: ["tags.getAll", LibraryArgs], result: Array } | - { key: ["jobs.getHistory", LibraryArgs], result: Array } | - { key: ["getNode"], result: NodeState } | - { key: ["library.getStatistics", LibraryArgs], result: Statistics } | - { key: ["version"], result: string } | - { key: ["jobs.getRunning", LibraryArgs], result: Array } | - { key: ["library.get"], result: Array } | - { key: ["locations.get", LibraryArgs], result: Array } | { key: ["volumes.get"], result: Array } | - { key: ["locations.indexer_ruleslist", LibraryArgs], result: Array } | + { key: ["locations.get", LibraryArgs], result: Array } | { key: ["locations.getExplorerData", LibraryArgs], result: ExplorerData } | - { key: ["tags.getExplorerData", LibraryArgs], result: ExplorerData }, + { key: ["tags.get", LibraryArgs], result: Tag | null } | + { key: ["library.get"], result: Array } | + { key: ["getNode"], result: NodeState } | + { key: ["tags.getExplorerData", LibraryArgs], result: ExplorerData } | + { key: ["version"], result: string } | + { key: ["files.readMetadata", LibraryArgs], result: null } | + { key: ["jobs.getRunning", LibraryArgs], result: Array } | + { key: ["locations.indexer_ruleslist", LibraryArgs], result: Array }, mutations: + { key: ["files.setFavorite", LibraryArgs], result: null } | + { key: ["library.edit", EditLibraryArgs], result: null } | + { key: ["tags.assign", LibraryArgs], result: null } | + { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | + { key: ["files.setNote", LibraryArgs], result: null } | + { key: ["locations.create", LibraryArgs], result: Location } | + { key: ["tags.update", LibraryArgs], result: null } | + { key: ["locations.fullRescan", LibraryArgs], result: null } | + { key: ["locations.quickRescan", LibraryArgs], result: null } | { key: ["locations.indexer_rulesdelete", LibraryArgs], result: null } | { key: ["library.create", string], result: null } | - { key: ["locations.quickRescan", LibraryArgs], result: null } | - { key: ["locations.update", LibraryArgs], result: null } | - { key: ["tags.assign", LibraryArgs], result: null } | - { key: ["locations.fullRescan", LibraryArgs], result: null } | - { key: ["files.delete", LibraryArgs], result: null } | - { key: ["files.setFavorite", LibraryArgs], result: null } | - { key: ["files.setNote", LibraryArgs], result: null } | - { key: ["tags.delete", LibraryArgs], result: null } | - { key: ["tags.update", LibraryArgs], result: null } | { key: ["locations.indexer_rulescreate", LibraryArgs], result: IndexerRule } | - { key: ["locations.create", LibraryArgs], result: Location } | - { key: ["jobs.generateThumbsForLocation", LibraryArgs], result: null } | - { key: ["library.edit", EditLibraryArgs], result: null } | - { key: ["library.delete", string], result: null } | + { key: ["locations.delete", LibraryArgs], result: null } | + { key: ["files.delete", LibraryArgs], result: null } | + { key: ["locations.update", LibraryArgs], result: null } | { key: ["jobs.identifyUniqueFiles", LibraryArgs], result: null } | + { key: ["library.delete", string], result: null } | { key: ["tags.create", LibraryArgs], result: Tag } | - { key: ["locations.delete", LibraryArgs], result: null }, + { key: ["tags.delete", LibraryArgs], result: null }, subscriptions: { key: ["invalidateQuery"], result: InvalidateOperationEvent } | { key: ["jobs.newThumbnail", LibraryArgs], result: string } }; -export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } - -export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } - -export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array } - -export interface LibraryConfig { version: string | null, name: string, description: string } - -export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array } - -export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" - -export interface TagCreateArgs { name: string, color: string } - export interface EditLibraryArgs { id: string, name: string | null, description: string | null } -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" - -export interface GenerateThumbsForLocationArgs { id: number, path: string } - -export interface IndexerRule { id: number, kind: number, name: string, parameters: Array, date_created: string, date_modified: string, locations: Array | null } - -export interface JobReport { id: string, name: string, data: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } - -export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array | null, indexer_rules: Array | null } - -export interface TagUpdateArgs { id: number, name: string | null, color: string | null } - -export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } - -export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } - -export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } - -export interface LibraryArgs { library_id: string, arg: T } - -export interface InvalidateOperationEvent { key: string, arg: any } - -export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } - -export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } - -export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } - -export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } - -export interface IdentifyUniqueFilesArgs { id: number, path: string } - -export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } - -export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag - -export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } - -export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } - -export interface SetFavoriteArgs { id: number, favorite: boolean } - -export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } - -export interface SetNoteArgs { id: number, note: string | null } - -export interface LocationCreateArgs { path: string, indexer_rules_ids: Array } - -export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } - -export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } - -export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } - export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array, kind: number, column: string | null, value: string, node: Node | null } -export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } +export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array } -export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File +export interface TagCreateArgs { name: string, color: string } -export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null } - -export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } - -export interface IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null } - -export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } +export interface Album { id: number, pub_id: Array, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array | null } export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null } +export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean } + +export interface LibraryArgs { library_id: string, arg: T } + +export interface IndexerRule { id: number, kind: number, name: string, parameters: Array, date_created: string, date_modified: string, locations: Array | null } + +export interface ExplorerData { context: ExplorerContext, items: Array } + +export interface InvalidateOperationEvent { key: string, arg: any } + +export interface Label { id: number, pub_id: Array, name: string | null, date_created: string, date_modified: string, label_files: Array | null } + +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array | null } + +export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null } + +export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null } + +export interface IdentifyUniqueFilesArgs { id: number, path: string } + +export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null } + +export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array | null, file_paths: Array | null } + +export interface Comment { id: number, pub_id: Array, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null } + +export interface Node { id: number, pub_id: Array, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array | null, jobs: Array | null, Location: Array | null } + +export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" + +export interface SetFavoriteArgs { id: number, favorite: boolean } + +export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } + +export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null } + +export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File + +export interface SetNoteArgs { id: number, note: string | null } + +export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag + +export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } + +export interface LibraryConfig { version: string | null, name: string, description: string } + +export interface Job { id: Array, name: string, node_id: number, action: number, status: number, data: Array | null, metadata: Array | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null } + +export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null } + +export interface Location { id: number, pub_id: Array, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array | null, indexer_rules: Array | null } + +export interface IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null } + +export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array } + +export interface JobReport { id: string, name: string, data: Array | null, metadata: Array | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number } + +export interface GenerateThumbsForLocationArgs { id: number, path: string } + +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" + +export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null } + export interface ConfigMetadata { version: string | null } +export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig } + +export interface LocationCreateArgs { path: string, indexer_rules_ids: Array } + +export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null } + export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean } -export interface ExplorerData { context: ExplorerContext, items: Array } +export interface Space { id: number, pub_id: Array, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array | null } + +export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array | null, labels: Array | null, albums: Array | null, spaces: Array | null, paths: Array | null, comments: Array | null, media_data: MediaData | null | null, key: Key | null | null } + +export interface TagUpdateArgs { id: number, name: string | null, color: string | null } diff --git a/core/src/job/job_manager.rs b/core/src/job/job_manager.rs index 92e453353..5c81f6b9a 100644 --- a/core/src/job/job_manager.rs +++ b/core/src/job/job_manager.rs @@ -8,6 +8,7 @@ use crate::{ }; use int_enum::IntEnum; +use prisma_client_rust::Direction; use rspc::Type; use serde::{Deserialize, Serialize}; use std::{ @@ -127,6 +128,7 @@ impl JobManager { .db .job() .find_many(vec![job::status::not(JobStatus::Running.int_value())]) + .order_by(job::date_created::order(Direction::Desc)) .exec() .await?; diff --git a/docs/architecture/nodes.md b/docs/architecture/nodes.md index 33524444f..649b6b412 100644 --- a/docs/architecture/nodes.md +++ b/docs/architecture/nodes.md @@ -1,3 +1,7 @@ # Nodes +Nodes are instances of the Spacedrive core running on a device, they are able to connect to each other via a peer-to-peer network. A node is able to run many libraries simultaneously, but must be authorized per-library in order to synchronize. + + + p2p, connecting nodes, protocols. \ No newline at end of file diff --git a/docs/architecture/preview-media.md b/docs/architecture/preview-media.md index 271f663a1..9a32ec0a2 100644 --- a/docs/architecture/preview-media.md +++ b/docs/architecture/preview-media.md @@ -2,6 +2,6 @@ Spacedrive generates compressed preview media for images, videos and text files. - +Preview media is stored in the Node's data folder in a single directory. Images are stored as WEBP format with their CAS id as the name. ffmpeg, syncing, security \ No newline at end of file diff --git a/packages/interface/package.json b/packages/interface/package.json index 794d784db..3164ed86d 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-icons": "^1.1.1", "@radix-ui/react-progress": "^0.1.4", "@radix-ui/react-slider": "^0.1.4", + "@radix-ui/react-tabs": "^1.0.0", "@radix-ui/react-tooltip": "^1.0.0", "@sd/assets": "workspace:*", "@sd/client": "workspace:*", @@ -38,6 +39,7 @@ "autoprefixer": "^10.4.7", "byte-size": "^8.1.0", "clsx": "^1.2.1", + "date-fns": "^2.29.2", "immer": "^9.0.15", "jotai": "^1.7.6", "lodash": "^4.17.21", diff --git a/packages/interface/src/components/jobs/JobManager.tsx b/packages/interface/src/components/jobs/JobManager.tsx new file mode 100644 index 000000000..9f4c348ef --- /dev/null +++ b/packages/interface/src/components/jobs/JobManager.tsx @@ -0,0 +1,99 @@ +import { CheckBadgeIcon, KeyIcon, RssIcon } from '@heroicons/react/24/outline'; +import { EyeIcon, FolderIcon, PhotoIcon, XMarkIcon } from '@heroicons/react/24/outline'; +import { useLibraryQuery } from '@sd/client'; +import { JobReport } from '@sd/core'; +import { Button } from '@sd/ui'; +import clsx from 'clsx'; +import { format, formatDistance, formatDistanceToNow, formatDuration } from 'date-fns'; +import moment from 'moment'; +import { ArrowsClockwise } from 'phosphor-react'; +import React from 'react'; + +import { Tooltip } from '../tooltip/Tooltip'; + +interface JobNiceData { + name: string; + icon: React.FC>; +} + +const NiceData: Record = { + indexer: { + name: 'Location Indexer', + icon: FolderIcon + }, + thumbnailer: { + name: 'Thumbnail Generator', + icon: PhotoIcon + }, + file_identifier: { + name: 'File Identifier', + icon: EyeIcon + } +}; + +const StatusColors: Record = { + Running: 'text-blue-500', + Failed: 'text-red-500', + Completed: 'text-green-500', + Queued: 'text-yellow-500', + Canceled: 'text-gray-500', + Paused: 'text-gray-500' +}; + +function elapsed(seconds: number) { + return new Date(seconds * 1000).toUTCString().match(/(\d\d:\d\d:\d\d)/)?.[0]; +} + +export function JobsManager() { + const jobs = useLibraryQuery(['jobs.getHistory']); + return ( +
+
+
+
+ {jobs.data?.map((job) => { + const color = StatusColors[job.status]; + const niceData = NiceData[job.name]; + if (job.metadata) console.log({ job }); + + return ( +
+ + + +
+ {niceData.name} +
+ + {job.status === 'Failed' ? 'Failed after' : 'Took'}{' '} + {job.seconds_elapsed + ? formatDuration({ seconds: job.seconds_elapsed }) + : 'less than a second'} + + + + {formatDistanceToNow(new Date(job.date_created))} ago + +
+ {job.data} +
+
+
+ + +
+
+ ); + })} +
+
+
+ ); +} diff --git a/packages/interface/src/components/jobs/RunningJobsWidget.tsx b/packages/interface/src/components/jobs/RunningJobsWidget.tsx index 5c23d5821..565db52cf 100644 --- a/packages/interface/src/components/jobs/RunningJobsWidget.tsx +++ b/packages/interface/src/components/jobs/RunningJobsWidget.tsx @@ -19,7 +19,7 @@ const MiddleTruncatedText = ({ const endWidth = fontFaceScaleFactor * 4; return ( -
+
-
+
{/* {job.status} Job */} {job.message} diff --git a/packages/interface/src/components/layout/Sidebar.tsx b/packages/interface/src/components/layout/Sidebar.tsx index a4457acc4..e01e15dd7 100644 --- a/packages/interface/src/components/layout/Sidebar.tsx +++ b/packages/interface/src/components/layout/Sidebar.tsx @@ -9,15 +9,16 @@ import { useLibraryQuery } from '@sd/client'; import { LocationCreateArgs } from '@sd/core'; -import { Button, Dropdown } from '@sd/ui'; +import { Button, Dropdown, OverlayPanel } from '@sd/ui'; import clsx from 'clsx'; -import { CirclesFour, Planet, WaveTriangle } from 'phosphor-react'; +import { CheckCircle, CirclesFour, Planet, WaveTriangle } from 'phosphor-react'; import React, { useContext, useEffect } from 'react'; import { NavLink, NavLinkProps, useNavigate } from 'react-router-dom'; import { useSnapshot } from 'valtio'; import CreateLibraryDialog from '../dialog/CreateLibraryDialog'; import { Folder } from '../icons/Folder'; +import { JobsManager } from '../jobs/JobManager'; import RunningJobsWidget from '../jobs/RunningJobsWidget'; import { MacTrafficLights } from '../os/TrafficLights'; import { DefaultProps } from '../primitive/types'; @@ -261,7 +262,7 @@ export const Sidebar: React.FC = (props) => { )}
-
+
{({ isActive }) => ( )} + + + + } + > +
+ +
+
); diff --git a/packages/interface/src/components/tooltip/Tooltip.tsx b/packages/interface/src/components/tooltip/Tooltip.tsx index 1d69f7628..c4a5ba3ab 100644 --- a/packages/interface/src/components/tooltip/Tooltip.tsx +++ b/packages/interface/src/components/tooltip/Tooltip.tsx @@ -1,14 +1,25 @@ import * as TooltipPrimitive from '@radix-ui/react-tooltip'; import React from 'react'; -export const Tooltip = ({ children, label }: { children: React.ReactNode; label: string }) => { +export const Tooltip = ({ + children, + label, + position = 'bottom' +}: { + children: React.ReactNode; + label: string; + position?: 'top' | 'right' | 'bottom' | 'left'; +}) => { return ( {children} - + {label} diff --git a/packages/ui/package.json b/packages/ui/package.json index cc0279996..2241e948b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -20,6 +20,7 @@ "@headlessui/react": "^1.6.6", "@heroicons/react": "^2.0.10", "@radix-ui/react-context-menu": "^1.0.0", + "@radix-ui/react-dropdown-menu": "^1.0.0", "@tailwindcss/forms": "^0.5.2", "class-variance-authority": "^0.2.3", "clsx": "^1.2.1", diff --git a/packages/ui/src/ContextMenu.tsx b/packages/ui/src/ContextMenu.tsx index cdc29075f..838174534 100644 --- a/packages/ui/src/ContextMenu.tsx +++ b/packages/ui/src/ContextMenu.tsx @@ -26,7 +26,7 @@ export const ContextMenu = ({ }: PropsWithChildren) => { return ( - {trigger} + {trigger} {children} diff --git a/packages/ui/src/OverlayPanel.tsx b/packages/ui/src/OverlayPanel.tsx new file mode 100644 index 000000000..598282ede --- /dev/null +++ b/packages/ui/src/OverlayPanel.tsx @@ -0,0 +1,37 @@ +import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { VariantProps, cva } from 'class-variance-authority'; +import clsx from 'clsx'; +import { Icon } from 'phosphor-react'; +import React, { PropsWithChildren } from 'react'; + +interface Props extends DropdownMenu.MenuContentProps { + trigger: React.ReactNode; +} + +const MENU_CLASSES = ` + flex flex-col + min-w-[11rem] m-2 space-y-1 + text-left text-sm dark:text-gray-100 text-gray-800 + bg-gray-50 border-gray-200 dark:bg-gray-600 + border border-gray-300 dark:border-gray-500 + shadow-2xl shadow-gray-300 dark:shadow-gray-950 + select-none cursor-default rounded-lg +`; + +export const OverlayPanel = ({ + trigger, + children, + className, + ...props +}: PropsWithChildren) => { + return ( + + {trigger} + + + {children} + + + + ); +}; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index d437ca096..867c6ddfa 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,4 +1,5 @@ export * from './Button'; export * from './Dropdown'; export * as ContextMenu from './ContextMenu'; +export * from './OverlayPanel'; export * from './Input'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81a2ed2f59e494f01a439c4f499716e2f6411873..9f4eb7ae760b33cfe67a5be14fc1bf9aa3526eea 100644 GIT binary patch delta 474 zcmcb1UGww<%?)8n%!x_GlP4%DZVpvyWfn|HEJ@W(%PY20h%?eNveYx0d`4Pk^Ly2A ztRQvM1I(GUrVFSr32uI(?a3s7Pz_N$y(gVZdb6Wm8pq@diu}{_elzk;-lr(EdAS(} z>tt(fs0hEdPV;)}?dz=>Q+TJFc`_8x)mE(Ru!1$ z73G$iq^Ad%RTKtSSmtCIh6N|(HZ%XS*P25 zVbq^4#t!5bC^H#MKhD8)c6x#=6U+4Y_l&929hjKp+Z`2|fS7r^qausxMn=o&jzY}x z+oxS(x#|k!D5f#0hZuzUC6(ufd$|PbyBk*qm%9afIT|NcW+a6M`G$D;l$84#xr9`t zSvrU3=cPF3Wth2y_~rOpdZh;i7n)@zrJ1C<804h+dign3gr>MUdU#}3XitB*n_ZU8 rK+n)ny|!IKk`;*AfS4VKIkrnka+-gb>})JMz5YEL|Mp}~uHqyBxapeL delta 241 zcmX^8K=ayl%?)8no2!*tm^X{7akEXXR^r^8rDM-Dog;@!Wb+lhG>**~<{Ye(D=m4N zkJxNKV#650JKfHUQE|GI7o*blcrV5`*W1^sG66C3_O+@kCL0+orVEBMDQsuD&T_?d zdf|HxjS!0zW7CYVO!vqN{Xh?c%w&&9=Tx8EVxL^!(2P{y+>pvJOSj})v-C2*@|1F8 z!^(&V4{d)7(_()oFKvIHJfA>c@2LFpvI6fUGq-eK&%mtYg0jgIFUd~Nmt|$2o)W+) n)Ltda3dC$c%nrmH+pA