From 0437503b752e43aa34a25daa0ff9903a59e1602f Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Sun, 29 Dec 2024 00:31:52 +0000 Subject: [PATCH 01/59] feat(servers content): file upload + extra mod info + misc (#3055) * feat: only scroll up if scrolled down * feat: no query results message * feat: content files support, mobile fixes * fix(drag & drop): type of file prop * chore: show number of mods in searchbar Signed-off-by: Evan Song * chore: adjust btn styles Signed-off-by: Evan Song * feat: prepare for mod author in backend response Signed-off-by: Evan Song * fix: external mods & mobile * chore: adjust edit mod version modal copy Signed-off-by: Evan Song * chore: add tooltips for version/filename Signed-off-by: Evan Song * chore: swap delete/change version btn Signed-off-by: Evan Song * fix: dont allow mod link to be dragged Signed-off-by: Evan Song * fix: oops Signed-off-by: Evan Song * chore: remove author field Signed-off-by: Evan Song * chore: drill down tooltip Signed-off-by: Evan Song * fix: fighting types Signed-off-by: Evan Song * prepare for owner field Signed-off-by: Evan Song --------- Signed-off-by: Evan Song Co-authored-by: Evan Song Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com> --- .../ui/servers/FilesUploadDragAndDrop.vue | 75 +++ .../ui/servers/FilesUploadDropdown.vue | 306 ++++++++++++ apps/frontend/src/composables/pyroServers.ts | 16 +- .../servers/manage/[id]/content/index.vue | 445 +++++++++++------- .../src/pages/servers/manage/[id]/files.vue | 291 +----------- .../servers/manage/[id]/options/loader.vue | 4 +- apps/frontend/src/types/servers.ts | 16 +- 7 files changed, 696 insertions(+), 457 deletions(-) create mode 100644 apps/frontend/src/components/ui/servers/FilesUploadDragAndDrop.vue create mode 100644 apps/frontend/src/components/ui/servers/FilesUploadDropdown.vue diff --git a/apps/frontend/src/components/ui/servers/FilesUploadDragAndDrop.vue b/apps/frontend/src/components/ui/servers/FilesUploadDragAndDrop.vue new file mode 100644 index 00000000..68fd5c92 --- /dev/null +++ b/apps/frontend/src/components/ui/servers/FilesUploadDragAndDrop.vue @@ -0,0 +1,75 @@ + + + diff --git a/apps/frontend/src/components/ui/servers/FilesUploadDropdown.vue b/apps/frontend/src/components/ui/servers/FilesUploadDropdown.vue new file mode 100644 index 00000000..4841ea53 --- /dev/null +++ b/apps/frontend/src/components/ui/servers/FilesUploadDropdown.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/apps/frontend/src/composables/pyroServers.ts b/apps/frontend/src/composables/pyroServers.ts index 95ca9d5b..943ccb0b 100644 --- a/apps/frontend/src/composables/pyroServers.ts +++ b/apps/frontend/src/composables/pyroServers.ts @@ -198,14 +198,16 @@ interface Startup { jdk_build: "corretto" | "temurin" | "graal"; } -interface Mod { +export interface Mod { filename: string; - project_id: string; - version_id: string; - name: string; - version_number: string; - icon_url: string; + project_id: string | undefined; + version_id: string | undefined; + name: string | undefined; + version_number: string | undefined; + icon_url: string | undefined; + owner: string | undefined; disabled: boolean; + installing: boolean; } interface Backup { @@ -1364,7 +1366,7 @@ type ContentModule = { data: Mod[] } & ContentFunctions; type BackupsModule = { data: Backup[] } & BackupFunctions; type NetworkModule = { allocations: Allocation[] } & NetworkFunctions; type StartupModule = Startup & StartupFunctions; -type FSModule = { auth: JWTAuth } & FSFunctions; +export type FSModule = { auth: JWTAuth } & FSFunctions; type ModulesMap = { general: GeneralModule; diff --git a/apps/frontend/src/pages/servers/manage/[id]/content/index.vue b/apps/frontend/src/pages/servers/manage/[id]/content/index.vue index d2f24957..8161fa97 100644 --- a/apps/frontend/src/pages/servers/manage/[id]/content/index.vue +++ b/apps/frontend/src/pages/servers/manage/[id]/content/index.vue @@ -15,12 +15,11 @@
-
+
@@ -57,9 +56,9 @@
-
-
-
+
+
+
@@ -88,7 +87,7 @@ { id: 'disabled', action: () => (filterMethod = 'disabled') }, ]" > - +
- - - - Add {{ type.toLocaleLowerCase() }} - - +
+ + + + + + + Add {{ type.toLocaleLowerCase() }} + + +
-
-
-
-
- +
-
- -
- -

No {{ type.toLocaleLowerCase() }}s found!

-

- Add some {{ type.toLocaleLowerCase() }}s to your server to manage them here. -

- - - - Add {{ type.toLocaleLowerCase() }} - - -
-
- -

Your server is running Vanilla Minecraft

-

- Add content to your server by installing a modpack or choosing a different platform that - supports {{ type }}s. -

-
- - - - Find a modpack - - -
or
- - - - Change platform - - + +
+
+ +

+ No {{ type.toLocaleLowerCase() }}s found for your query! +

+

Try another query, or show everything.

+ + + +
+
+ +

No {{ type.toLocaleLowerCase() }}s found!

+

+ Add some {{ type.toLocaleLowerCase() }}s to your server to manage them here. +

+
+ + + + + + + Add {{ type.toLocaleLowerCase() }} + + +
+
-
+
+ +

Your server is running Vanilla Minecraft

+

+ Add content to your server by installing a modpack or choosing a different platform that + supports {{ type }}s. +

+
+ + + + Find a modpack + + +
or
+ + + + Change platform + + +
+
+
@@ -290,10 +363,15 @@ import { MoreVerticalIcon, CompassIcon, WrenchIcon, + ListIcon, + FileIcon, } from "@modrinth/assets"; import { ButtonStyled, NewModal } from "@modrinth/ui"; import { ref, computed, watch, onMounted, onUnmounted } from "vue"; +import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue"; +import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue"; import type { Server } from "~/composables/pyroServers"; +import { acceptFileFromProjectType } from "~/helpers/fileUtils.js"; const props = defineProps<{ server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>; @@ -304,14 +382,7 @@ const type = computed(() => { return loader === "paper" || loader === "purpur" ? "Plugin" : "Mod"; }); -interface Mod { - name?: string; - filename: string; - project_id?: string; - version_id?: string; - version_number?: string; - icon_url?: string; - disabled: boolean; +interface ContentItem extends Mod { changing?: boolean; } @@ -322,12 +393,41 @@ const listContainer = ref(null); const windowScrollY = ref(0); const windowHeight = ref(0); -const localMods = ref([]); +const localMods = ref([]); const searchInput = ref(""); const modSearchInput = ref(""); const filterMethod = ref("all"); +const uploadDropdownRef = ref(); + +const handleDroppedFiles = (files: File[]) => { + files.forEach((file) => { + uploadDropdownRef.value?.uploadFile(file); + }); +}; + +const initiateFileUpload = () => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = acceptFileFromProjectType(type.value.toLowerCase()); + input.multiple = true; + input.onchange = () => { + if (input.files) { + Array.from(input.files).forEach((file) => { + uploadDropdownRef.value?.uploadFile(file); + }); + } + }; + input.click(); +}; + +const showAll = () => { + searchInput.value = ""; + modSearchInput.value = ""; + filterMethod.value = "all"; +}; + const filterMethodLabel = computed(() => { switch (filterMethod.value) { case "disabled": @@ -419,14 +519,17 @@ const debouncedSearch = debounce(() => { modSearchInput.value = searchInput.value; if (pyroContentSentinel.value) { - pyroContentSentinel.value.scrollIntoView({ - behavior: "smooth", - block: "start", - }); + const sentinelRect = pyroContentSentinel.value.getBoundingClientRect(); + if (sentinelRect.top < 0 || sentinelRect.bottom > window.innerHeight) { + pyroContentSentinel.value.scrollIntoView({ + // behavior: "smooth", + block: "start", + }); + } } }, 300); -async function toggleMod(mod: Mod) { +async function toggleMod(mod: ContentItem) { mod.changing = true; const originalFilename = mod.filename; @@ -458,7 +561,7 @@ async function toggleMod(mod: Mod) { mod.changing = false; } -async function removeMod(mod: Mod) { +async function removeMod(mod: ContentItem) { mod.changing = true; try { @@ -515,6 +618,10 @@ async function changeModVersion() { } const hasMods = computed(() => { + return localMods.value?.length > 0; +}); + +const hasFilteredMods = computed(() => { return filteredMods.value?.length > 0; }); diff --git a/apps/frontend/src/pages/servers/manage/[id]/files.vue b/apps/frontend/src/pages/servers/manage/[id]/files.vue index de29ffcf..e61e2d6a 100644 --- a/apps/frontend/src/pages/servers/manage/[id]/files.vue +++ b/apps/frontend/src/pages/servers/manage/[id]/files.vue @@ -25,12 +25,9 @@ @delete="handleDeleteItem" /> -
@@ -44,94 +41,14 @@ @upload="initiateFileUpload" @update:search-query="searchQuery = $event" /> - -
-
-
-
- - - File Uploads{{ - activeUploads.length > 0 ? ` - ${activeUploads.length} left` : "" - }} - -
-
- -
-
-
- - - - - - {{ item.file.name }} - {{ item.size }} -
-
- - - -
-
-
-
-
-
+
Drop files here to upload

-
+ import { useInfiniteScroll } from "@vueuse/core"; -import { UploadIcon, FolderOpenIcon, CheckCircleIcon, XCircleIcon } from "@modrinth/assets"; -import { ButtonStyled } from "@modrinth/ui"; +import { UploadIcon, FolderOpenIcon } from "@modrinth/assets"; import type { DirectoryResponse, DirectoryItem, Server } from "~/composables/pyroServers"; +import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue"; +import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue"; interface BaseOperation { type: "move" | "rename"; @@ -263,14 +181,6 @@ interface RenameOperation extends BaseOperation { type Operation = MoveOperation | RenameOperation; -interface UploadItem { - file: File; - progress: number; - status: "pending" | "uploading" | "completed" | "error" | "cancelled"; - size: string; - uploader?: any; -} - const props = defineProps<{ server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>; }>(); @@ -312,46 +222,8 @@ const isEditingImage = ref(false); const imagePreview = ref(); const isDragging = ref(false); -const dragCounter = ref(0); -const uploadStatusRef = ref(null); -const isUploading = computed(() => uploadQueue.value.length > 0); -const uploadQueue = ref([]); - -const activeUploads = computed(() => - uploadQueue.value.filter((item) => item.status === "pending" || item.status === "uploading"), -); - -const onUploadStatusEnter = (el: Element) => { - const height = (el as HTMLElement).scrollHeight; - (el as HTMLElement).style.height = "0"; - // eslint-disable-next-line no-void - void (el as HTMLElement).offsetHeight; - (el as HTMLElement).style.height = `${height}px`; -}; - -const onUploadStatusLeave = (el: Element) => { - const height = (el as HTMLElement).scrollHeight; - (el as HTMLElement).style.height = `${height}px`; - // eslint-disable-next-line no-void - void (el as HTMLElement).offsetHeight; - (el as HTMLElement).style.height = "0"; -}; - -watch( - uploadQueue, - () => { - if (!uploadStatusRef.value) return; - const el = uploadStatusRef.value; - const itemsHeight = uploadQueue.value.length * 32; - const headerHeight = 12; - const gap = 8; - const padding = 32; - const totalHeight = padding + headerHeight + gap + itemsHeight; - el.style.height = `${totalHeight}px`; - }, - { deep: true }, -); +const uploadDropdownRef = ref(); const data = computed(() => props.server.general); @@ -917,135 +789,12 @@ const requestShareLink = async () => { } }; -const handleDragEnter = (event: DragEvent) => { +const handleDroppedFiles = (files: File[]) => { if (isEditing.value) return; - event.preventDefault(); - if (!event.dataTransfer?.types.includes("application/pyro-file-move")) { - dragCounter.value++; - isDragging.value = true; - } -}; -const handleDragOver = (event: DragEvent) => { - if (isEditing.value) return; - event.preventDefault(); -}; - -const handleDragLeave = (event: DragEvent) => { - if (isEditing.value) return; - event.preventDefault(); - dragCounter.value--; - if (dragCounter.value === 0) { - isDragging.value = false; - } -}; - -// eslint-disable-next-line require-await -const handleDrop = async (event: DragEvent) => { - if (isEditing.value) return; - event.preventDefault(); - isDragging.value = false; - dragCounter.value = 0; - - const isInternalMove = event.dataTransfer?.types.includes("application/pyro-file-move"); - if (isInternalMove) return; - - const files = event.dataTransfer?.files; - if (files) { - Array.from(files).forEach((file) => { - uploadFile(file); - }); - } -}; - -const formatFileSize = (bytes: number): string => { - if (bytes < 1024) return bytes + " B"; - if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; - return (bytes / (1024 * 1024)).toFixed(1) + " MB"; -}; - -const cancelUpload = (item: UploadItem) => { - if (item.uploader && item.status === "uploading") { - item.uploader.cancel(); - item.status = "cancelled"; - - setTimeout(async () => { - const index = uploadQueue.value.findIndex((qItem) => qItem.file.name === item.file.name); - if (index !== -1) { - uploadQueue.value.splice(index, 1); - await nextTick(); - } - }, 5000); - } -}; - -const uploadFile = async (file: File) => { - const uploadItem: UploadItem = { - file, - progress: 0, - status: "pending", - size: formatFileSize(file.size), - }; - - uploadQueue.value.push(uploadItem); - - try { - uploadItem.status = "uploading"; - const filePath = `${currentPath.value}/${file.name}`.replace("//", "/"); - const uploader = await props.server.fs?.uploadFile(filePath, file); - uploadItem.uploader = uploader; - - if (uploader?.onProgress) { - uploader.onProgress(({ progress }: { progress: number }) => { - const index = uploadQueue.value.findIndex((item) => item.file.name === file.name); - if (index !== -1) { - uploadQueue.value[index].progress = Math.round(progress); - } - }); - } - - await uploader?.promise; - const index = uploadQueue.value.findIndex((item) => item.file.name === file.name); - if (index !== -1 && uploadQueue.value[index].status !== "cancelled") { - uploadQueue.value[index].status = "completed"; - uploadQueue.value[index].progress = 100; - } - - await nextTick(); - - setTimeout(async () => { - const removeIndex = uploadQueue.value.findIndex((item) => item.file.name === file.name); - if (removeIndex !== -1) { - uploadQueue.value.splice(removeIndex, 1); - await nextTick(); - } - }, 5000); - - await refreshList(); - } catch (error) { - console.error("Error uploading file:", error); - const index = uploadQueue.value.findIndex((item) => item.file.name === file.name); - if (index !== -1 && uploadQueue.value[index].status !== "cancelled") { - uploadQueue.value[index].status = "error"; - } - - setTimeout(async () => { - const removeIndex = uploadQueue.value.findIndex((item) => item.file.name === file.name); - if (removeIndex !== -1) { - uploadQueue.value.splice(removeIndex, 1); - await nextTick(); - } - }, 5000); - - if (error instanceof Error && error.message !== "Upload cancelled") { - addNotification({ - group: "files", - title: "Upload failed", - text: `Failed to upload ${file.name}`, - type: "error", - }); - } - } + files.forEach((file) => { + uploadDropdownRef.value?.uploadFile(file); + }); }; const initiateFileUpload = () => { @@ -1055,7 +804,7 @@ const initiateFileUpload = () => { input.onchange = () => { if (input.files) { Array.from(input.files).forEach((file) => { - uploadFile(file); + uploadDropdownRef.value?.uploadFile(file); }); } }; diff --git a/apps/frontend/src/pages/servers/manage/[id]/options/loader.vue b/apps/frontend/src/pages/servers/manage/[id]/options/loader.vue index 6eeba49b..35a863db 100644 --- a/apps/frontend/src/pages/servers/manage/[id]/options/loader.vue +++ b/apps/frontend/src/pages/servers/manage/[id]/options/loader.vue @@ -330,11 +330,11 @@ Upload .mrpack file - + - +
diff --git a/apps/frontend/src/types/servers.ts b/apps/frontend/src/types/servers.ts index 7ba96f79..8f76f594 100644 --- a/apps/frontend/src/types/servers.ts +++ b/apps/frontend/src/types/servers.ts @@ -1,11 +1,11 @@ -export interface Mod { - id: string; - filename: string; - modrinth_ids: { - project_id: string; - version_id: string; - }; -} +// export interface Mod { +// id: string; +// filename: string; +// modrinth_ids: { +// project_id: string; +// version_id: string; +// }; +// } interface License { id: string; From 8b7547ae38a48c2522cddfbaed8d012ba638c9e2 Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Sun, 29 Dec 2024 02:19:24 +0000 Subject: [PATCH 02/59] fix(server backup settings): number input -> dropdown (#3099) * feat(backup settings): number input -> dropdown * fix(servers teleport dropdown): round last element * fix index --- .../ui/servers/BackupSettingsModal.vue | 91 ++++++------------- .../ui/servers/TeleportDropdownMenu.vue | 2 + 2 files changed, 31 insertions(+), 62 deletions(-) diff --git a/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue b/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue index 52892bdf..2a6c6267 100644 --- a/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue +++ b/apps/frontend/src/components/ui/servers/BackupSettingsModal.vue @@ -4,8 +4,8 @@
Auto backup

- Automatically create a backup of your server every - {{ autoBackupInterval == 1 ? "hour" : `${autoBackupInterval} hours` }} + Automatically create a backup of your server + {{ backupIntervalsLabel.toLowerCase() }}

@@ -22,54 +22,19 @@
Interval

- The amount of hours between each backup. This will only backup your server if it has - been modified since the last backup. + The amount of time between each backup. This will only backup your server if it has been + modified since the last backup.

-
-
- - - - -
- {{ autoBackupInterval == 1 ? "hour" : "hours" }} -
+
@@ -92,7 +57,7 @@ From 0d7934e3b8e38ca426c61e9f2f504c7530f58b9f Mon Sep 17 00:00:00 2001 From: Josiah Glosson Date: Wed, 15 Jan 2025 17:34:21 -0600 Subject: [PATCH 25/59] Fix importing newer Prism instances (#3129) * Fix importing newer Prism instances and clean up import code a bit * cargo fmt --------- Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com> --- packages/app-lib/src/api/pack/import/mmc.rs | 40 ++++++++++----------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/app-lib/src/api/pack/import/mmc.rs b/packages/app-lib/src/api/pack/import/mmc.rs index 8d718bbc..dcc15d30 100644 --- a/packages/app-lib/src/api/pack/import/mmc.rs +++ b/packages/app-lib/src/api/pack/import/mmc.rs @@ -180,9 +180,8 @@ pub async fn import_mmc( instance_folder: String, // instance folder in mmc_base_path profile_path: &str, // path to profile ) -> crate::Result<()> { - let mmc_instance_path = mmc_base_path - .join("instances") - .join(instance_folder.clone()); + let mmc_instance_path = + mmc_base_path.join("instances").join(instance_folder); let mmc_pack = io::read_to_string(&mmc_instance_path.join("mmc-pack.json")).await?; @@ -209,9 +208,18 @@ pub async fn import_mmc( profile_path: profile_path.to_string(), }; - // Managed pack - let backup_name = "Imported Modpack".to_string(); + let mut minecraft_folder = mmc_instance_path.join("minecraft"); + if !minecraft_folder.is_dir() { + minecraft_folder = mmc_instance_path.join(".minecraft"); + if !minecraft_folder.is_dir() { + return Err(crate::ErrorKind::InputError( + "Instance is missing Minecraft directory".to_string(), + ) + .into()); + } + } + // Managed pack if instance_cfg.managed_pack.unwrap_or(false) { match instance_cfg.managed_pack_type { Some(MMCManagedPackType::Modrinth) => { @@ -220,38 +228,26 @@ pub async fn import_mmc( // Modrinth Managed Pack // Kept separate as we may in the future want to add special handling for modrinth managed packs - let backup_name = "Imported Modrinth Modpack".to_string(); - let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); - import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?; + import_mmc_unmanaged(profile_path, minecraft_folder, "Imported Modrinth Modpack".to_string(), description, mmc_pack).await?; } Some(MMCManagedPackType::Flame) | Some(MMCManagedPackType::ATLauncher) => { // For flame/atlauncher managed packs // Treat as unmanaged, but with 'minecraft' folder instead of '.minecraft' - let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join("minecraft"); - import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?; + import_mmc_unmanaged(profile_path, minecraft_folder, "Imported Modpack".to_string(), description, mmc_pack).await?; }, Some(_) => { // For managed packs that aren't modrinth, flame, atlauncher // Treat as unmanaged - let backup_name = "ImportedModpack".to_string(); - let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); - import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?; + import_mmc_unmanaged(profile_path, minecraft_folder, "ImportedModpack".to_string(), description, mmc_pack).await?; }, - _ => return Err(crate::ErrorKind::InputError({ - "Instance is managed, but managed pack type not specified in instance.cfg".to_string() - }).into()) + _ => return Err(crate::ErrorKind::InputError("Instance is managed, but managed pack type not specified in instance.cfg".to_string()).into()) } } else { // Direclty import unmanaged pack - let backup_name = "Imported Modpack".to_string(); - let minecraft_folder = mmc_base_path - .join("instances") - .join(instance_folder) - .join(".minecraft"); import_mmc_unmanaged( profile_path, minecraft_folder, - backup_name, + "Imported Modpack".to_string(), description, mmc_pack, ) From 5c8e7a8b3898250451dafba9fe80cf6b563b71c1 Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 16 Jan 2025 16:40:13 -0800 Subject: [PATCH 26/59] Support new delphi response type --- .../labrinth/src/database/models/team_item.rs | 2 +- apps/labrinth/src/routes/internal/admin.rs | 34 +++++++++++++------ apps/labrinth/src/routes/internal/billing.rs | 2 +- apps/labrinth/src/routes/v3/oauth_clients.rs | 4 +-- apps/labrinth/src/util/guards.rs | 2 +- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/labrinth/src/database/models/team_item.rs b/apps/labrinth/src/database/models/team_item.rs index 8f6f811e..d343596c 100644 --- a/apps/labrinth/src/database/models/team_item.rs +++ b/apps/labrinth/src/database/models/team_item.rs @@ -405,7 +405,7 @@ impl TeamMember { Ok(()) } - pub async fn delete<'a, 'b>( + pub async fn delete( id: TeamId, user_id: UserId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, diff --git a/apps/labrinth/src/routes/internal/admin.rs b/apps/labrinth/src/routes/internal/admin.rs index e983bae8..f52667d9 100644 --- a/apps/labrinth/src/routes/internal/admin.rs +++ b/apps/labrinth/src/routes/internal/admin.rs @@ -189,7 +189,7 @@ pub struct DelphiIngest { pub url: String, pub project_id: crate::models::ids::ProjectId, pub version_id: crate::models::ids::VersionId, - pub issues: Vec, + pub issues: HashMap>, } #[post("/_delphi", guard = "admin_key_guard")] @@ -218,29 +218,41 @@ pub async fn delphi_result_ingest( )) })?; + let mut header = format!("Suspicious traces found at {}", body.url); + + for (issue, trace) in &body.issues { + for (path, code) in trace { + header.push_str(&format!( + "\n issue {issue} found at file {}: \n ```\n{}\n```", + path, code + )); + } + } + crate::util::webhook::send_slack_webhook( body.project_id, &pool, &redis, webhook_url, - Some(format!( - "Suspicious traces found at {}. Traces: {}", - body.url, - body.issues.join(", ") - )), + Some(header), ) .await .ok(); + let mut thread_header = format!("Suspicious traces found at [version {}](https://modrinth.com/project/{}/version/{})", body.version_id, body.project_id, body.version_id); + + for (issue, trace) in &body.issues { + for path in trace.keys() { + thread_header + .push_str(&format!("\n issue {issue} found at file {}", path)); + } + } + let mut transaction = pool.begin().await?; ThreadMessageBuilder { author_id: Some(crate::database::models::UserId(AUTOMOD_ID)), body: MessageBody::Text { - body: format!( - "WSR; Suspicious traces found for version_id {}. Traces: {}", - body.version_id, - body.issues.join(", ") - ), + body: thread_header, private: true, replying_to: None, associated_images: vec![], diff --git a/apps/labrinth/src/routes/internal/billing.rs b/apps/labrinth/src/routes/internal/billing.rs index 38a130c5..8d313a4d 100644 --- a/apps/labrinth/src/routes/internal/billing.rs +++ b/apps/labrinth/src/routes/internal/billing.rs @@ -884,7 +884,7 @@ pub async fn active_servers( .head() .headers() .get("X-Master-Key") - .map_or(false, |it| it.as_bytes() == master_key.as_bytes()) + .is_some_and(|it| it.as_bytes() == master_key.as_bytes()) { return Err(ApiError::CustomAuthentication( "Invalid master key".to_string(), diff --git a/apps/labrinth/src/routes/v3/oauth_clients.rs b/apps/labrinth/src/routes/v3/oauth_clients.rs index a65dcc75..3a9648f9 100644 --- a/apps/labrinth/src/routes/v3/oauth_clients.rs +++ b/apps/labrinth/src/routes/v3/oauth_clients.rs @@ -160,7 +160,7 @@ pub struct NewOAuthApp { } #[post("app")] -pub async fn oauth_client_create<'a>( +pub async fn oauth_client_create( req: HttpRequest, new_oauth_app: web::Json, pool: web::Data, @@ -221,7 +221,7 @@ pub async fn oauth_client_create<'a>( } #[delete("app/{id}")] -pub async fn oauth_client_delete<'a>( +pub async fn oauth_client_delete( req: HttpRequest, client_id: web::Path, pool: web::Data, diff --git a/apps/labrinth/src/util/guards.rs b/apps/labrinth/src/util/guards.rs index f7ad43cc..e6401fa4 100644 --- a/apps/labrinth/src/util/guards.rs +++ b/apps/labrinth/src/util/guards.rs @@ -8,5 +8,5 @@ pub fn admin_key_guard(ctx: &GuardContext) -> bool { ctx.head() .headers() .get(ADMIN_KEY_HEADER) - .map_or(false, |it| it.as_bytes() == admin_key.as_bytes()) + .is_some_and(|it| it.as_bytes() == admin_key.as_bytes()) } From 9e97c068d8d62d037ea3c958a80fdf01b45d288c Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 16 Jan 2025 17:41:41 -0800 Subject: [PATCH 27/59] Fix version_fields, loader_fields_loaders missing primary keys --- .../20250117013050_missing-primary-keys.sql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/labrinth/migrations/20250117013050_missing-primary-keys.sql diff --git a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql new file mode 100644 index 00000000..1bcadd67 --- /dev/null +++ b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql @@ -0,0 +1,17 @@ +WITH CTE AS ( + SELECT ctid, + ROW_NUMBER() OVER (PARTITION BY version_id, field_id, enum_value ORDER BY ctid) AS row_num + FROM version_fields +) +DELETE FROM version_fields +WHERE ctid IN ( + SELECT ctid + FROM CTE + WHERE row_num > 1 +); + +ALTER TABLE version_fields +ADD PRIMARY KEY (version_id, field_id, enum_value); + +ALTER TABLE loader_fields_loaders +ADD PRIMARY KEY (loader_id, loader_field_id); \ No newline at end of file From 8abe2283d766b0e3b89c0c49a566d65ceec81e03 Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 16 Jan 2025 17:49:26 -0800 Subject: [PATCH 28/59] Fix clippy --- apps/labrinth/src/routes/internal/billing.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/labrinth/src/routes/internal/billing.rs b/apps/labrinth/src/routes/internal/billing.rs index 8d313a4d..d6dc71bb 100644 --- a/apps/labrinth/src/routes/internal/billing.rs +++ b/apps/labrinth/src/routes/internal/billing.rs @@ -880,11 +880,11 @@ pub async fn active_servers( ) -> Result { let master_key = dotenvy::var("PYRO_API_KEY")?; - if !req + if req .head() .headers() .get("X-Master-Key") - .is_some_and(|it| it.as_bytes() == master_key.as_bytes()) + .is_none_or(|it| it.as_bytes() != master_key.as_bytes()) { return Err(ApiError::CustomAuthentication( "Invalid master key".to_string(), From 208015a911dc53d678c81857e1b05377cefa81db Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 16 Jan 2025 18:21:11 -0800 Subject: [PATCH 29/59] Bump rust version --- apps/labrinth/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labrinth/Dockerfile b/apps/labrinth/Dockerfile index e7126e6d..851579f7 100644 --- a/apps/labrinth/Dockerfile +++ b/apps/labrinth/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.81.0 as build +FROM rust:1.84.0 as build ENV PKG_CONFIG_ALLOW_CROSS=1 WORKDIR /usr/src/labrinth From 497b0bca0b85287b0542e7a19012c3d7c720fd01 Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 16 Jan 2025 23:00:11 -0800 Subject: [PATCH 30/59] Fix broken migration on labrinth --- ...63153f6879d507dc1d1bb38846e16d9fa6cbd6cceea2efbfd51.json | 2 +- ...ac6d0c83fec4117d340e5970b27edc76f21b903f362329a6542.json | 2 +- ...f58b86083b659cb647498fcc20e38265b9d316ca8c0a2cbc02a.json | 2 +- .../migrations/20250117013050_missing-primary-keys.sql | 6 ++++++ apps/labrinth/src/database/models/project_item.rs | 6 +++--- apps/labrinth/src/database/models/version_item.rs | 6 +++--- apps/labrinth/src/search/indexing/local_import.rs | 6 +++++- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/apps/labrinth/.sqlx/query-10f81e605c9ef63153f6879d507dc1d1bb38846e16d9fa6cbd6cceea2efbfd51.json b/apps/labrinth/.sqlx/query-10f81e605c9ef63153f6879d507dc1d1bb38846e16d9fa6cbd6cceea2efbfd51.json index 4caa1739..b2cf2a4c 100644 --- a/apps/labrinth/.sqlx/query-10f81e605c9ef63153f6879d507dc1d1bb38846e16d9fa6cbd6cceea2efbfd51.json +++ b/apps/labrinth/.sqlx/query-10f81e605c9ef63153f6879d507dc1d1bb38846e16d9fa6cbd6cceea2efbfd51.json @@ -38,7 +38,7 @@ false, false, true, - true, + false, true ] }, diff --git a/apps/labrinth/.sqlx/query-53c50911a9e98ac6d0c83fec4117d340e5970b27edc76f21b903f362329a6542.json b/apps/labrinth/.sqlx/query-53c50911a9e98ac6d0c83fec4117d340e5970b27edc76f21b903f362329a6542.json index f932bd38..55180325 100644 --- a/apps/labrinth/.sqlx/query-53c50911a9e98ac6d0c83fec4117d340e5970b27edc76f21b903f362329a6542.json +++ b/apps/labrinth/.sqlx/query-53c50911a9e98ac6d0c83fec4117d340e5970b27edc76f21b903f362329a6542.json @@ -38,7 +38,7 @@ false, false, true, - true, + false, true ] }, diff --git a/apps/labrinth/.sqlx/query-7fa5098b1083af58b86083b659cb647498fcc20e38265b9d316ca8c0a2cbc02a.json b/apps/labrinth/.sqlx/query-7fa5098b1083af58b86083b659cb647498fcc20e38265b9d316ca8c0a2cbc02a.json index da471b1d..3610b34c 100644 --- a/apps/labrinth/.sqlx/query-7fa5098b1083af58b86083b659cb647498fcc20e38265b9d316ca8c0a2cbc02a.json +++ b/apps/labrinth/.sqlx/query-7fa5098b1083af58b86083b659cb647498fcc20e38265b9d316ca8c0a2cbc02a.json @@ -44,7 +44,7 @@ false, false, true, - true, + false, true ] }, diff --git a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql index 1bcadd67..b39039b8 100644 --- a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql +++ b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql @@ -1,3 +1,9 @@ +ALTER TABLE version_fields + ALTER COLUMN enum_value SET DEFAULT -1; + +ALTER TABLE version_fields + ALTER COLUMN enum_value SET NOT NULL; + WITH CTE AS ( SELECT ctid, ROW_NUMBER() OVER (PARTITION BY version_id, field_id, enum_value ORDER BY ctid) AS row_num diff --git a/apps/labrinth/src/database/models/project_item.rs b/apps/labrinth/src/database/models/project_item.rs index 1bd07d22..f7f3ab9c 100644 --- a/apps/labrinth/src/database/models/project_item.rs +++ b/apps/labrinth/src/database/models/project_item.rs @@ -595,12 +595,12 @@ impl Project { version_id: VersionId(m.version_id), field_id: LoaderFieldId(m.field_id), int_value: m.int_value, - enum_value: m.enum_value.map(LoaderFieldEnumValueId), + enum_value: if m.enum_value == -1 { None } else { Some(LoaderFieldEnumValueId(m.enum_value)) }, string_value: m.string_value, }; - if let Some(enum_value) = m.enum_value { - loader_field_enum_value_ids.insert(LoaderFieldEnumValueId(enum_value)); + if m.enum_value != -1 { + loader_field_enum_value_ids.insert(LoaderFieldEnumValueId(m.enum_value)); } acc.entry(ProjectId(m.mod_id)).or_default().push(qvf); diff --git a/apps/labrinth/src/database/models/version_item.rs b/apps/labrinth/src/database/models/version_item.rs index 792c9ac0..f8de51f6 100644 --- a/apps/labrinth/src/database/models/version_item.rs +++ b/apps/labrinth/src/database/models/version_item.rs @@ -499,12 +499,12 @@ impl Version { version_id: VersionId(m.version_id), field_id: LoaderFieldId(m.field_id), int_value: m.int_value, - enum_value: m.enum_value.map(LoaderFieldEnumValueId), + enum_value: if m.enum_value == -1 { None } else { Some(LoaderFieldEnumValueId(m.enum_value)) }, string_value: m.string_value, }; - if let Some(enum_value) = m.enum_value { - loader_field_enum_value_ids.insert(LoaderFieldEnumValueId(enum_value)); + if m.enum_value != -1 { + loader_field_enum_value_ids.insert(LoaderFieldEnumValueId(m.enum_value)); } acc.entry(VersionId(m.version_id)).or_default().push(qvf); diff --git a/apps/labrinth/src/search/indexing/local_import.rs b/apps/labrinth/src/search/indexing/local_import.rs index f24af8e2..4306888f 100644 --- a/apps/labrinth/src/search/indexing/local_import.rs +++ b/apps/labrinth/src/search/indexing/local_import.rs @@ -505,7 +505,11 @@ async fn index_versions( version_id: VersionId(m.version_id), field_id: LoaderFieldId(m.field_id), int_value: m.int_value, - enum_value: m.enum_value.map(LoaderFieldEnumValueId), + enum_value: if m.enum_value == -1 { + None + } else { + Some(LoaderFieldEnumValueId(m.enum_value)) + }, string_value: m.string_value, }; From 7fd3d737b817b3c973bb381833eca4a0201aa75b Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 16 Jan 2025 23:32:26 -0800 Subject: [PATCH 31/59] Fix broken migration on labrinth (again) --- .../labrinth/migrations/20250117013050_missing-primary-keys.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql index b39039b8..4ec551ff 100644 --- a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql +++ b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql @@ -1,6 +1,8 @@ ALTER TABLE version_fields ALTER COLUMN enum_value SET DEFAULT -1; +UPDATE version_fields SET enum_value = -1 WHERE enum_value IS NULL; + ALTER TABLE version_fields ALTER COLUMN enum_value SET NOT NULL; From 701bf853d54c7ed5643b2d21ae3042efc84f8a45 Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 16 Jan 2025 23:59:07 -0800 Subject: [PATCH 32/59] Fix broken migration on labrinth (againx2) --- .../migrations/20250117013050_missing-primary-keys.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql index 4ec551ff..1a8588e2 100644 --- a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql +++ b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql @@ -1,3 +1,6 @@ +ALTER TABLE version_fields +DROP CONSTRAINT version_fields_enum_value_fkey; + ALTER TABLE version_fields ALTER COLUMN enum_value SET DEFAULT -1; From 24295ea482ed95856beca1002c489fd204040c12 Mon Sep 17 00:00:00 2001 From: Jai A Date: Fri, 17 Jan 2025 08:53:54 -0800 Subject: [PATCH 33/59] fix version uploading --- .../migrations/20250117013050_missing-primary-keys.sql | 2 +- apps/labrinth/src/database/models/loader_fields.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql index 1a8588e2..2054cf49 100644 --- a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql +++ b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql @@ -20,7 +20,7 @@ WHERE ctid IN ( FROM CTE WHERE row_num > 1 ); - +x ALTER TABLE version_fields ADD PRIMARY KEY (version_id, field_id, enum_value); diff --git a/apps/labrinth/src/database/models/loader_fields.rs b/apps/labrinth/src/database/models/loader_fields.rs index 70c74150..292c6e12 100644 --- a/apps/labrinth/src/database/models/loader_fields.rs +++ b/apps/labrinth/src/database/models/loader_fields.rs @@ -757,7 +757,12 @@ impl VersionField { l.field_id.0, l.version_id.0, l.int_value, - l.enum_value.as_ref().map(|e| e.0), + if let Some(enum_value) = l.enum_value.as_ref().map(|e| e.0) + { + enum_value + } else { + -1 + }, l.string_value.clone(), ) }) @@ -772,7 +777,7 @@ impl VersionField { &version_ids[..], &int_values[..] as &[Option], &string_values[..] as &[Option], - &enum_values[..] as &[Option] + &enum_values[..] as &[i32] ) .execute(&mut **transaction) .await?; From d7814e115d5324ee59a9ee19becbb8de4da932f1 Mon Sep 17 00:00:00 2001 From: Jai A Date: Fri, 17 Jan 2025 09:05:42 -0800 Subject: [PATCH 34/59] fix migration typo --- .../labrinth/migrations/20250117013050_missing-primary-keys.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql index 2054cf49..1a8588e2 100644 --- a/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql +++ b/apps/labrinth/migrations/20250117013050_missing-primary-keys.sql @@ -20,7 +20,7 @@ WHERE ctid IN ( FROM CTE WHERE row_num > 1 ); -x + ALTER TABLE version_fields ADD PRIMARY KEY (version_id, field_id, enum_value); From 75b357a0694601ab2c94f5bf96272ec9dc04aead Mon Sep 17 00:00:00 2001 From: Jai Agrawal <18202329+Geometrically@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:41:49 -0800 Subject: [PATCH 35/59] Staff support dashboard routes (#3160) * Staff support dashboard routes * Fix clippy --- .../frontend/src/pages/admin/billing/[id].vue | 210 ++++++++++++++++++ .../src/pages/settings/billing/charges.vue | 18 +- apps/frontend/src/pages/user/[id].vue | 89 +++++++- .../src/database/models/loader_fields.rs | 7 +- apps/labrinth/src/routes/internal/billing.rs | 38 +++- apps/labrinth/src/routes/v2/users.rs | 4 +- .../src/routes/v3/project_creation.rs | 4 - apps/labrinth/src/routes/v3/users.rs | 21 +- .../src/routes/v3/version_creation.rs | 16 +- 9 files changed, 371 insertions(+), 36 deletions(-) create mode 100644 apps/frontend/src/pages/admin/billing/[id].vue diff --git a/apps/frontend/src/pages/admin/billing/[id].vue b/apps/frontend/src/pages/admin/billing/[id].vue new file mode 100644 index 00000000..c5c72b5d --- /dev/null +++ b/apps/frontend/src/pages/admin/billing/[id].vue @@ -0,0 +1,210 @@ + + diff --git a/apps/frontend/src/pages/settings/billing/charges.vue b/apps/frontend/src/pages/settings/billing/charges.vue index c1195d2a..a4c8eddc 100644 --- a/apps/frontend/src/pages/settings/billing/charges.vue +++ b/apps/frontend/src/pages/settings/billing/charges.vue @@ -25,7 +25,7 @@ â‹… - {{ formatPrice(charge.amount, charge.currency_code) }} + {{ formatPrice(vintl.locale, charge.amount, charge.currency_code) }}
@@ -39,6 +39,7 @@ diff --git a/apps/frontend/src/pages/user/[id].vue b/apps/frontend/src/pages/user/[id].vue index fe48a848..14c1af88 100644 --- a/apps/frontend/src/pages/user/[id].vue +++ b/apps/frontend/src/pages/user/[id].vue @@ -2,6 +2,57 @@
+ +
+
+ Email +
+ + {{ user.email }} + + + +
+
+ +
+ Auth providers + {{ user.auth_providers.join(", ") }} +
+ +
+ Payment methods + + + + + +
+ +
+ Has password + + {{ user.has_password ? "Yes" : "No" }} + +
+ +
+ Has TOTP + + {{ user.has_totp ? "Yes" : "No" }} + +
+
+
- @@ -89,13 +92,15 @@ :type="charge.status" /> â‹… + {{ charge.type }} + â‹… {{ $dayjs(charge.due).format("YYYY-MM-DD") }} â‹… {{ formatPrice(vintl.locale, charge.amount, charge.currency_code) }}
@@ -322,7 +326,7 @@ const props = withDefaults( * @deprecated Use `ranges` instead */ resoloutions?: Record; - ranges?: Record; + ranges?: RangeObject[]; personal?: boolean; }>(), { @@ -335,12 +339,6 @@ const props = withDefaults( const projects = ref(props.projects || []); -const selectableRanges = Object.entries(props.ranges).map(([duration, extra]) => ({ - label: typeof extra === "string" ? extra : extra[0], - value: Number(duration), - res: typeof extra === "string" ? Number(duration) : extra[1], -})); - // const selectedChart = ref('downloads') const selectedChart = computed({ get: () => { @@ -413,33 +411,78 @@ const isUsingProjectColors = computed({ }, }); +const startDate = ref(dayjs().startOf("day")); +const endDate = ref(dayjs().endOf("day")); +const timeResolution = ref(30); + +onBeforeMount(() => { + // Load cached data and range from localStorage - cache. + if (import.meta.client) { + const rangeLabel = localStorage.getItem("analyticsSelectedRange"); + if (rangeLabel) { + const range = props.ranges.find((r) => r.getLabel([dayjs(), dayjs()]) === rangeLabel)!; + + if (range !== undefined) { + internalRange.value = range; + const ranges = range.getDates(dayjs()); + timeResolution.value = range.timeResolution; + startDate.value = ranges.startDate; + endDate.value = ranges.endDate; + } + } + } +}); + +onMounted(() => { + if (internalRange.value === null) { + internalRange.value = props.ranges.find( + (r) => r.getLabel([dayjs(), dayjs()]) === "Previous 30 days", + )!; + } + + const ranges = selectedRange.value.getDates(dayjs()); + startDate.value = ranges.startDate; + endDate.value = ranges.endDate; + timeResolution.value = selectedRange.value.timeResolution; +}); + +const internalRange: Ref = ref(null as unknown as RangeObject); + +const selectedRange = computed({ + get: () => { + return internalRange.value; + }, + set: (newRange) => { + const ranges = newRange.getDates(dayjs()); + startDate.value = ranges.startDate; + endDate.value = ranges.endDate; + timeResolution.value = newRange.timeResolution; + + internalRange.value = newRange; + + if (import.meta.client) { + localStorage.setItem( + "analyticsSelectedRange", + internalRange.value?.getLabel([dayjs(), dayjs()]) ?? "Previous 30 days", + ); + } + }, +}); + const analytics = useFetchAllAnalytics( resetCharts, projects, selectedDisplayProjects, props.personal, + startDate, + endDate, + timeResolution, ); -const { startDate, endDate, timeRange, timeResolution } = analytics; - -const selectedRange = computed({ - get: () => { - return ( - selectableRanges.find((option) => option.value === timeRange.value) || { - label: "Custom", - value: timeRange.value, - } - ); - }, - set: (newRange: { label: string; value: number; res?: number }) => { - timeRange.value = newRange.value; - startDate.value = Date.now() - timeRange.value * 60 * 1000; - endDate.value = Date.now(); - - if (newRange?.res) { - timeResolution.value = newRange.res; - } - }, +const formattedCategorySubtitle = computed(() => { + return ( + selectedRange.value?.getLabel([dayjs(startDate.value), dayjs(endDate.value)]) ?? "Loading..." + ); }); const selectedDataSet = computed(() => { @@ -484,6 +527,9 @@ const onToggleColors = () => { diff --git a/packages/ui/src/components/base/MarkdownEditor.vue b/packages/ui/src/components/base/MarkdownEditor.vue index 9c6d5703..59a8746a 100644 --- a/packages/ui/src/components/base/MarkdownEditor.vue +++ b/packages/ui/src/components/base/MarkdownEditor.vue @@ -300,8 +300,8 @@ import Chips from './Chips.vue' const props = withDefaults( defineProps<{ modelValue: string - disabled: boolean - headingButtons: boolean + disabled?: boolean + headingButtons?: boolean /** * @param file The file to upload * @throws If the file is invalid or the upload fails @@ -948,4 +948,8 @@ function openVideoModal() { pointer-events: none; cursor: not-allowed; } + +:deep(.cm-content) { + overflow: auto; +} diff --git a/packages/ui/src/components/base/RadialHeader.vue b/packages/ui/src/components/base/RadialHeader.vue new file mode 100644 index 00000000..89e80b67 --- /dev/null +++ b/packages/ui/src/components/base/RadialHeader.vue @@ -0,0 +1,49 @@ + + + diff --git a/packages/ui/src/components/base/RadioButtons.vue b/packages/ui/src/components/base/RadioButtons.vue new file mode 100644 index 00000000..fd384c4a --- /dev/null +++ b/packages/ui/src/components/base/RadioButtons.vue @@ -0,0 +1,48 @@ + + diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 8fe546bc..bf53d7aa 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -25,6 +25,8 @@ export { default as Pagination } from './base/Pagination.vue' export { default as PopoutMenu } from './base/PopoutMenu.vue' export { default as PreviewSelectButton } from './base/PreviewSelectButton.vue' export { default as ProjectCard } from './base/ProjectCard.vue' +export { default as RadialHeader } from './base/RadialHeader.vue' +export { default as RadioButtons } from './base/RadioButtons.vue' export { default as ScrollablePanel } from './base/ScrollablePanel.vue' export { default as SimpleBadge } from './base/SimpleBadge.vue' export { default as Slider } from './base/Slider.vue' diff --git a/packages/utils/types.ts b/packages/utils/types.ts index 3fe5dbec..76962e9e 100644 --- a/packages/utils/types.ts +++ b/packages/utils/types.ts @@ -1,7 +1,7 @@ export const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' export type Base62Char = (typeof BASE62_CHARS)[number] -export type ModrinthId = `${Base62Char}`[] +export type ModrinthId = string export type Environment = 'required' | 'optional' | 'unsupported' | 'unknown' @@ -241,3 +241,15 @@ export interface TeamMember { payouts_split: number ordering: number } + +export type Report = { + id: ModrinthId + item_id: ModrinthId + item_type: 'project' | 'version' | 'user' + report_type: string + reporter: ModrinthId + thread_id: ModrinthId + closed: boolean + created: string + body: string +} From 5bc63a5ad31508bdb1dff94eefe303b9df30ff2f Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 6 Feb 2025 10:02:44 -0800 Subject: [PATCH 56/59] Ads window system blocker --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- apps/app-frontend/package.json | 2 +- apps/app/Cargo.toml | 2 +- apps/app/tauri.conf.json | 2 +- packages/app-lib/Cargo.toml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8dffdfd5..02385145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8870,7 +8870,7 @@ dependencies = [ [[package]] name = "theseus" -version = "0.9.2" +version = "0.9.3" dependencies = [ "async-recursion", "async-tungstenite", @@ -8921,7 +8921,7 @@ dependencies = [ [[package]] name = "theseus_gui" -version = "0.9.2" +version = "0.9.3" dependencies = [ "chrono", "cocoa 0.25.0", @@ -10780,7 +10780,7 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" version = "0.47.2" -source = "git+https://github.com/modrinth/wry?rev=cdbf938#cdbf9384263db4e692e22dcf9bf6085e334a10f3" +source = "git+https://github.com/modrinth/wry?rev=51907c6#51907c61541f2e389856f07773a32bac04f9a843" dependencies = [ "base64 0.22.1", "block2", diff --git a/Cargo.toml b/Cargo.toml index 06487ac1..c55a6fc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,4 @@ strip = true # Remove debug symbols opt-level = 3 [patch.crates-io] -wry = { git = "https://github.com/modrinth/wry", rev ="cdbf938" } \ No newline at end of file +wry = { git = "https://github.com/modrinth/wry", rev = "51907c6" } \ No newline at end of file diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index f3938063..fe9390f9 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -1,7 +1,7 @@ { "name": "@modrinth/app-frontend", "private": true, - "version": "0.9.2", + "version": "0.9.3", "type": "module", "scripts": { "dev": "vite", diff --git a/apps/app/Cargo.toml b/apps/app/Cargo.toml index d4c4d1e9..0e3bc20b 100644 --- a/apps/app/Cargo.toml +++ b/apps/app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "theseus_gui" -version = "0.9.2" +version = "0.9.3" description = "The Modrinth App is a desktop application for managing your Minecraft mods" license = "GPL-3.0-only" repository = "https://github.com/modrinth/code/apps/app/" diff --git a/apps/app/tauri.conf.json b/apps/app/tauri.conf.json index 37c5c20e..24eb3bd4 100644 --- a/apps/app/tauri.conf.json +++ b/apps/app/tauri.conf.json @@ -44,7 +44,7 @@ ] }, "productName": "Modrinth App", - "version": "0.9.2", + "version": "0.9.3", "mainBinaryName": "Modrinth App", "identifier": "ModrinthApp", "plugins": { diff --git a/packages/app-lib/Cargo.toml b/packages/app-lib/Cargo.toml index dcf0b6ae..4e2d8375 100644 --- a/packages/app-lib/Cargo.toml +++ b/packages/app-lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "theseus" -version = "0.9.2" +version = "0.9.3" authors = ["Jai A "] edition = "2021" From acc379d14d71d492b32c983ffbe75abefe1ee41d Mon Sep 17 00:00:00 2001 From: Jai A Date: Thu, 6 Feb 2025 10:20:58 -0800 Subject: [PATCH 57/59] Add clean.io to app frames --- apps/frontend/src/public/promo-frame.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/frontend/src/public/promo-frame.html b/apps/frontend/src/public/promo-frame.html index 43adfb77..2dc4fc32 100644 --- a/apps/frontend/src/public/promo-frame.html +++ b/apps/frontend/src/public/promo-frame.html @@ -5,6 +5,7 @@ Modrinth App Ad + diff --git a/apps/frontend/src/components/ui/servers/TeleportDropdownMenu.vue b/apps/frontend/src/components/ui/servers/TeleportDropdownMenu.vue index 4c799454..13af59e5 100644 --- a/apps/frontend/src/components/ui/servers/TeleportDropdownMenu.vue +++ b/apps/frontend/src/components/ui/servers/TeleportDropdownMenu.vue @@ -227,7 +227,7 @@ const radioValue = computed({ }); const triggerClasses = computed(() => ({ - "cursor-not-allowed opacity-50 grayscale": props.disabled, + "!cursor-not-allowed opacity-50 grayscale": props.disabled, "rounded-b-none": dropdownVisible.value && !isRenderingUp.value && !props.disabled, "rounded-t-none": dropdownVisible.value && isRenderingUp.value && !props.disabled, })); diff --git a/apps/frontend/src/composables/pyroServers.ts b/apps/frontend/src/composables/pyroServers.ts index 74f76e3a..930a2227 100644 --- a/apps/frontend/src/composables/pyroServers.ts +++ b/apps/frontend/src/composables/pyroServers.ts @@ -252,7 +252,7 @@ export interface DirectoryResponse { current?: number; } -type ContentType = "Mod" | "Plugin"; +type ContentType = "mod" | "plugin"; const constructServerProperties = (properties: any): string => { let fileContent = `#Minecraft server properties\n#${new Date().toUTCString()}\n`; @@ -519,8 +519,8 @@ const installContent = async (contentType: ContentType, projectId: string, versi await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods`, { method: "POST", body: { - install_as: contentType, rinth_ids: { project_id: projectId, version_id: versionId }, + install_as: contentType, }, }); } catch (error) { @@ -529,13 +529,12 @@ const installContent = async (contentType: ContentType, projectId: string, versi } }; -const removeContent = async (contentType: ContentType, contentId: string) => { +const removeContent = async (path: string) => { try { await PyroFetch(`servers/${internalServerRefrence.value.serverId}/deleteMod`, { method: "POST", body: { - install_as: contentType, - path: contentId, + path, }, }); } catch (error) { @@ -544,15 +543,11 @@ const removeContent = async (contentType: ContentType, contentId: string) => { } }; -const reinstallContent = async ( - contentType: ContentType, - contentId: string, - newContentId: string, -) => { +const reinstallContent = async (replace: string, projectId: string, versionId: string) => { try { - await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods/${contentId}`, { - method: "PUT", - body: { install_as: contentType, version_id: newContentId }, + await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods/update`, { + method: "POST", + body: { replace, project_id: projectId, version_id: versionId }, }); } catch (error) { console.error("Error reinstalling mod:", error); @@ -1160,18 +1155,17 @@ type ContentFunctions = { /** * Removes a mod from a server. - * @param contentType - The type of content to remove. - * @param contentId - The ID of the content. + * @param path - The path of the mod file. */ - remove: (contentType: ContentType, contentId: string) => Promise; + remove: (path: string) => Promise; /** * Reinstalls a mod to a server. - * @param contentType - The type of content to reinstall. - * @param contentId - The ID of the content. - * @param newContentId - The ID of the new version. + * @param replace - The path of the mod to replace. + * @param projectId - The ID of the content. + * @param versionId - The ID of the new version. */ - reinstall: (contentType: ContentType, contentId: string, newContentId: string) => Promise; + reinstall: (replace: string, projectId: string, versionId: string) => Promise; }; type BackupFunctions = { diff --git a/apps/frontend/src/pages/servers/manage/[id]/content/index.vue b/apps/frontend/src/pages/servers/manage/[id]/content/index.vue index 8161fa97..1dc947f3 100644 --- a/apps/frontend/src/pages/servers/manage/[id]/content/index.vue +++ b/apps/frontend/src/pages/servers/manage/[id]/content/index.vue @@ -1,57 +1,14 @@