Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Omnihance A3 Agent is a full-stack application consisting of:
- Cannot edit files or upload data
- **Permission Actions**:
- `view_files`: View file system and file contents (super_admin, admin, viewer)
- `download_files`: Create and use one-day user-bound download links for file-browser and backup output files through `POST /api/file-tree/download-link` and `GET /api/file-tree/download/{token}` (super_admin, admin)
- `edit_files`: Edit files (super_admin, admin)
- `revert_files`: Revert files to previous revisions (super_admin, admin)
- `upload_game_data`: Upload MON.ull and MC.ull files (super_admin, admin)
Expand Down Expand Up @@ -576,6 +577,8 @@ Only stable GitHub releases are considered because GitHub's latest release endpo
- `POST /api/file-tree/revert-file` - Revert file to previous revision
- `POST /api/file-tree/duplicate-file` - Duplicate a file in the same directory
- `GET /api/file-tree/revision-summary` - Get revision count for a file
- `POST /api/file-tree/download-link` - Create or reuse a one-day user-bound download link for a file (requires `download_files` permission)
- `GET /api/file-tree/download/{token}` - Download a file through a valid user-bound temp link and record the download event
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Metrics

Expand Down Expand Up @@ -659,7 +662,8 @@ Only stable GitHub releases are considered because GitHub's latest release endpo
- `POST /api/backups/jobs/{id}/cancel` - Cancel a running backup job
- `GET /api/backups/jobs/{id}/runs` - List paginated run history for a backup job
- `GET /api/backups/runs/{run_id}` - Get run details, logs, errors, and output file metadata
- `GET /api/backups/runs/{run_id}/files/{file_id}/download` - Download a backup output file
- `POST /api/backups/runs/{run_id}/files/{file_id}/download-link` - Create or reuse a one-day user-bound download link for a successful backup output file (requires `download_files` permission)
- `GET /api/backups/runs/{run_id}/files/{file_id}/download` - Compatibility route that redirects successful backup output downloads through the temp-link flow
- `GET /api/backups/path-search` - Search local source or destination paths
- `GET /api/backups/defaults/sql-server` - Get SQL Server backup defaults and local server status

Expand All @@ -686,6 +690,8 @@ The application uses SQLite with the following main tables:
- **backup_jobs**: Backup job definitions, scheduling metadata, paths, SQL settings, and statuses
- **backup_runs**: Backup run history, trigger type, status, output logs, errors, and cancellation timestamps
- **backup_run_files**: Output archive files created by each backup run
- **file_download_links**: One-day user-bound file download links, file fingerprints, source context, and per-link download counts
- **file_download_events**: Per-request file download audit events with user, link, source context, file fingerprint, IP address, and user agent
- **server_view_svr_info_rows**: Raw `SvrInfo.ini` rows for Main, Account, Zone, and Battle servers
- **server_view_map_zones**: Raw Main and Zone Server map-to-zone references
- **server_view_spawn_rows**: Raw monster spawn rows from zone map `.n_ndt` files
Expand Down Expand Up @@ -716,7 +722,7 @@ The application uses SQLite with the following main tables:

5. **Upload Game Client Data**: Navigate to the Client Data section and upload MON.ull, MC.ull, and IT0.ull through IT3.ull files to populate monster, map, and item databases (requires admin or super admin role).

6. **Navigate Files**: Use the file tree sidebar to browse your server's file system (all authenticated users can view). Pin frequently used directories as shortcuts and right-click files to duplicate them quickly.
6. **Navigate Files**: Use the file tree sidebar to browse your server's file system (all authenticated users can view). Pin frequently used directories as shortcuts, right-click files to duplicate them, and download files with admin or super admin access.

7. **Edit Files**: Click on editable files (NPC files, quest files, spawn files, drop files (monster drop configurations), item files, item combination data files, or text files) to view and edit them (requires admin or super admin role).
- **Quest Files**: Edit quest configurations with type-aware objectives, add/remove controls for optional slots, and binary-safe padding preservation
Expand Down
197 changes: 190 additions & 7 deletions cmd/omnihance-a3-agent/docs/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,110 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/file-tree/download-link:
post:
tags:
- file-system
summary: Create file download link
description: Creates or reuses a one-day temp download link for the authenticated user when the file fingerprint is unchanged and the existing link has not expired.
security:
- ApiKeyAuth: []
parameters:
- in: query
name: path
required: true
schema:
type: string
description: File path to download. Directories are rejected.
responses:
'200':
description: Download link created or reused
content:
application/json:
schema:
$ref: '#/components/schemas/DownloadLinkResponse'
'400':
description: Bad Request - Missing path or path is a directory
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden - Download files permission is required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: File not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/file-tree/download/{token}:
get:
tags:
- file-system
summary: Download file from temp link
description: Streams the file as an attachment when the signed token belongs to the authenticated user, has not expired, and the file fingerprint is unchanged. A successful request increments the link download count and records a download event.
security:
- ApiKeyAuth: []
parameters:
- name: token
in: path
required: true
schema:
type: string
responses:
'200':
description: File download
content:
application/octet-stream:
schema:
type: string
format: binary
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden - Invalid token, wrong user, or missing download permission
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Download link or file not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'410':
description: Download link expired or invalidated because the file changed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/file-tree/npc-file:
get:
tags:
Expand Down Expand Up @@ -3985,7 +4089,66 @@ paths:
tags:
- backups
summary: Download a backup output file
description: Downloads one output file recorded for a backup run. The file ID must belong to the requested run.
description: Compatibility route that validates the requested successful backup output file, creates or reuses a user-bound temp link, and redirects to the shared file download endpoint.
security:
- ApiKeyAuth: []
parameters:
- name: run_id
in: path
required: true
schema:
type: integer
format: int64
- name: file_id
in: path
required: true
schema:
type: integer
format: int64
responses:
'302':
description: Redirect to temp download link
headers:
Location:
schema:
type: string
description: Shared temp download URL under /api/file-tree/download/{token}
'400':
description: Bad Request - Invalid backup run or file ID, failed run, or backup output is a directory
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden - Download files permission is required
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Backup run file not found or output file is missing
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/backups/runs/{run_id}/files/{file_id}/download-link:
post:
tags:
- backups
summary: Create backup output download link
description: Creates or reuses a one-day user-bound temp download link for a file recorded on a successful backup run. The output file must still exist and must not be a directory.
security:
- ApiKeyAuth: []
parameters:
Expand All @@ -4003,14 +4166,13 @@ paths:
format: int64
responses:
'200':
description: Backup file download
description: Download link created or reused
content:
application/octet-stream:
application/json:
schema:
type: string
format: binary
$ref: '#/components/schemas/DownloadLinkResponse'
'400':
description: Bad Request - Invalid backup run or file ID, or backup output is a directory
description: Bad Request - Invalid backup run or file ID, failed run, or backup output is a directory
content:
application/json:
schema:
Expand All @@ -4022,7 +4184,7 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden - Manage server permission is required
description: Forbidden - Download files permission is required
content:
application/json:
schema:
Expand Down Expand Up @@ -4341,6 +4503,24 @@ components:
file_tree:
$ref: '#/components/schemas/FileNode'
description: The file system tree structure
DownloadLinkResponse:
type: object
properties:
download_url:
type: string
description: Shared temp download URL for the authenticated user
example: '/api/file-tree/download/01HZX.temp-token'
expires_at:
type: string
format: date-time
description: Link expiration timestamp
reused:
type: boolean
description: Whether an existing unexpired link was reused
download_count:
type: integer
format: int64
description: Number of successful download requests already recorded for this link
NPCFileAPIData:
type: object
description: Parsed binary data from an NPC file (API request/response format). All fields are required when used as a request body.
Expand Down Expand Up @@ -6285,6 +6465,9 @@ components:
created_at:
type: string
format: date-time
download_available:
type: boolean
description: True when the run succeeded and the recorded output file still exists as a file
BackupRunDetails:
type: object
properties:
Expand Down
2 changes: 2 additions & 0 deletions cmd/omnihance-a3-agent/omnihance-a3-agent-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Omnihance A3 Agent frontend

File-browser and backup output downloads use the backend temp-link flow. Admin and super admin users can create one-day user-bound links; viewers see file-browser download actions but receive the denial toast.

## Adding Shadcn Components

Run `pnpm dlx shadcn@latest add {component-name}`
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ import {
APIError,
cancelBackupJob,
createBackupJob,
createBackupRunFileDownloadLink,
deleteBackupJob,
getBackupJob,
getBackupJobs,
getBackupRunDetails,
getBackupRunFileDownloadUrl,
getBackupRuns,
getBackupSQLServerDefaults,
runBackupJob,
Expand Down Expand Up @@ -1180,6 +1180,29 @@ function RunDetailsDialog({
open: boolean;
onOpenChange: (open: boolean) => void;
}) {
const downloadMutation = useMutation({
mutationFn: ({
runId,
fileId,
}: {
runId: number;
fileId: number;
itemName: string;
}) => createBackupRunFileDownloadLink(runId, fileId),
onSuccess: (response) => {
window.location.assign(response.download_url);
},
onError: (error) => {
const errorMessage =
error instanceof APIError
? error.getErrorMessage()
: error instanceof Error
? error.message
: 'Failed to create download link';
toast.error(errorMessage);
},
});

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-h-[90vh] w-[calc(100vw-2rem)] max-w-3xl overflow-hidden">
Expand Down Expand Up @@ -1229,22 +1252,32 @@ function RunDetailsDialog({
<div className="text-sm text-muted-foreground">
{formatBytes(file.file_size, 1)}
</div>
<Button
variant="outline"
size="sm"
className="w-fit sm:justify-self-end"
asChild
>
<a
href={getBackupRunFileDownloadUrl(
details.run.id,
file.id,
)}
{file.download_available && (
<Button
variant="outline"
size="sm"
className="w-fit sm:justify-self-end"
onClick={() =>
downloadMutation.mutate({
runId: details.run.id,
fileId: file.id,
itemName: file.item_name,
})
}
disabled={
downloadMutation.isPending &&
downloadMutation.variables?.fileId === file.id
}
aria-label={`Download ${file.item_name}`}
>
<Download className="h-4 w-4" />
</a>
</Button>
{downloadMutation.isPending &&
downloadMutation.variables?.fileId === file.id ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Download className="h-4 w-4" />
)}
</Button>
)}
</div>
))}
</div>
Expand Down
Loading
Loading