Skip to content

Asset tools #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
35 changes: 35 additions & 0 deletions backend/app/api/frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,41 @@ async def api_frame_assets_upload(

return {"path": path_without_combined, "size": len(contents), "mtime": int(datetime.now().timestamp())}

@api_with_auth.post("/frames/{id:int}/assets/mkdir")
async def api_frame_assets_mkdir(
id: int,
path: str = Form(..., description="Folder to make"),
db: Session = Depends(get_db), redis: Redis = Depends(get_redis)
):
frame = db.get(Frame, id)
if frame is None:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Frame not found")
if not path:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Path parameter is required")
if "*" in path:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid character * in path")
assets_path = frame.assets_path or "/srv/assets"
combined_path = os.path.normpath(os.path.join(assets_path, path))
if not combined_path.startswith(os.path.normpath(assets_path) + '/'):
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid asset path")

# TODO: stream and reuse connections
ssh = await get_ssh_connection(db, redis, frame)
try:
await exec_command(
db, redis, frame, ssh,
f"mkdir -p {shlex.quote(combined_path)}",
)
except Exception as e:
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))

finally:
await remove_ssh_connection(db, redis, ssh, frame)

path_without_combined = os.path.relpath(combined_path, assets_path)

return {"path": path_without_combined, "mtime": int(datetime.now().timestamp())}

@api_with_auth.post("/frames/{id:int}/clear_build_cache")
async def api_frame_clear_build_cache(id: int, redis: Redis = Depends(get_redis), db: Session = Depends(get_db)):
frame = db.get(Frame, id)
Expand Down
42 changes: 27 additions & 15 deletions frontend/src/scenes/frame/panels/Assets/Assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,21 @@ interface AssetNode {
children: Record<string, AssetNode>
}

interface AssetUtils {
openAsset: (path: string) => void
uploadAssets: (path: string) => void
mkdir: (path: string) => void
}

/** A recursive component that renders a folder or a file */
function TreeNode({
node,
frameId,
openAsset,
uploadAssets,
assetUtils,
}: {
node: AssetNode
frameId: number
openAsset: (path: string) => void
uploadAssets: (path: string) => void
assetUtils: AssetUtils
}): JSX.Element {
const [expanded, setExpanded] = useState(node.path === '')
const [isDownloading, setIsDownloading] = useState(false)
Expand All @@ -60,21 +64,25 @@ function TreeNode({
{
label: 'Upload files',
icon: <DocumentArrowUpIcon className="w-5 h-5" />,
onClick: () => uploadAssets(node.path),
onClick: () => assetUtils.uploadAssets(node.path),
},
{
label: 'New folder',
icon: <DocumentArrowUpIcon className="w-5 h-5" />,
onClick: () => {
const newFolder = prompt('Enter the name of the new folder')
if (newFolder) {
assetUtils.mkdir(node.path ? node.path + '/' + newFolder : newFolder)
}
},
},
]}
/>
</div>
{expanded && (
<div className="ml-2 border-l border-gray-600 pl-2">
{Object.values(node.children).map((child) => (
<TreeNode
key={child.path}
node={child}
frameId={frameId}
openAsset={openAsset}
uploadAssets={uploadAssets}
/>
<TreeNode key={child.path} node={child} frameId={frameId} assetUtils={assetUtils} />
))}
</div>
)}
Expand All @@ -85,7 +93,7 @@ function TreeNode({
return (
<div className="ml-1 flex items-center space-x-2">
<div className="flex-1">
<span className="cursor-pointer hover:underline text-white" onClick={() => openAsset(node.path)}>
<span className="cursor-pointer hover:underline text-white" onClick={() => assetUtils.openAsset(node.path)}>
{node.name}
</span>
</div>
Expand Down Expand Up @@ -132,7 +140,7 @@ export function Assets(): JSX.Element {
const { frame } = useValues(frameLogic)
const { openLogs } = useActions(panelsLogic)
const { assetsLoading, assetTree } = useValues(assetsLogic({ frameId: frame.id }))
const { syncAssets, uploadAssets } = useActions(assetsLogic({ frameId: frame.id }))
const { syncAssets, uploadAssets, mkdir } = useActions(assetsLogic({ frameId: frame.id }))
const { openAsset } = useActions(panelsLogic({ frameId: frame.id }))

return (
Expand All @@ -157,7 +165,11 @@ export function Assets(): JSX.Element {
<div>Loading assets...</div>
) : (
<div>
<TreeNode node={assetTree} frameId={frame.id} openAsset={openAsset} uploadAssets={uploadAssets} />
<TreeNode
node={assetTree}
frameId={frame.id}
assetUtils={{ openAsset: openAsset, uploadAssets: uploadAssets, mkdir: mkdir }}
/>
</div>
)}
</div>
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/scenes/frame/panels/Assets/assetsLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export const assetsLogic = kea<assetsLogicType>([
filesToUpload: (files: string[]) => ({ files }),
uploadFailure: (path: string) => ({ path }),
syncAssets: true,
mkdir: (path: string) => ({ path }),
mkdirComplete: (path: string) => ({ path }),
mkdirFailure: (path: string) => ({ path }),
}),
loaders(({ props }) => ({
assets: [
Expand Down Expand Up @@ -156,6 +159,20 @@ export const assetsLogic = kea<assetsLogicType>([
}
input.click()
},
mkdir: async ({ path }) => {
try {
const formData = new FormData()
formData.append('path', path)
const response = await apiFetch(`/api/frames/${props.frameId}/assets/mkdir`, {
method: 'POST',
body: formData,
})
await response.json()
actions.mkdirComplete(path)
} catch (error) {
actions.mkdirFailure(path)
}
},
})),
reducers({
assets: {
Expand All @@ -181,6 +198,9 @@ export const assetsLogic = kea<assetsLogicType>([
},
uploadFailure: (state, { path }) =>
state.map((asset) => (asset.path === path ? { ...asset, size: -2, mtime: -2 } : asset)),
mkdirComplete: (state, { path }) => {
return [...state, { path: path + '/.', size: 0, mtime: Date.now() }]
},
},
}),
afterMount(({ actions }) => {
Expand Down