nice
This commit is contained in:
parent
df47775b0a
commit
f3a7cf6337
53
backend/drive/migrations/0011_normalize_empty_root.py
Normal file
53
backend/drive/migrations/0011_normalize_empty_root.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""Normalize fresh installations to the physical storage root.
|
||||||
|
|
||||||
|
Older databases with existing /root content MUST be converted explicitly using
|
||||||
|
`manage.py flatten_drive_root --apply`, after a backup. This migration does not
|
||||||
|
move, delete or rewrite any user file.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, transaction
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_unused_root(apps, schema_editor):
|
||||||
|
Space = apps.get_model('drive', 'DriveSpace')
|
||||||
|
Item = apps.get_model('drive', 'DriveItem')
|
||||||
|
Upload = apps.get_model('drive', 'UploadSession')
|
||||||
|
root = Space.objects.filter(parent__isnull=True, private_owner__isnull=True).order_by('pk').first()
|
||||||
|
if root is None or root.relative_path != 'root':
|
||||||
|
return
|
||||||
|
|
||||||
|
# The previous migrations create a logical "root" even for an empty DB.
|
||||||
|
# Existing installations must not be migrated behind the user's back:
|
||||||
|
# physical bytes must be moved consistently with DB paths.
|
||||||
|
old_dir = Path(settings.DRIVE_STORAGE_ROOT).resolve() / 'root'
|
||||||
|
if (old_dir.exists() and (not old_dir.is_dir() or any(old_dir.iterdir()))):
|
||||||
|
print('GoodDrive: root/ has data; run flatten_drive_root --apply after backup.')
|
||||||
|
return
|
||||||
|
if (Item.objects.filter(storage_location__startswith='root/').exists()
|
||||||
|
or Upload.objects.exists()):
|
||||||
|
print('GoodDrive: existing files/uploads; run flatten_drive_root --apply after backup.')
|
||||||
|
return
|
||||||
|
|
||||||
|
old = list(Space.objects.filter(Q(pk=root.pk) | Q(relative_path__startswith='root/')))
|
||||||
|
new = {s.pk: ('' if s.pk == root.pk else s.relative_path[5:]) for s in old}
|
||||||
|
if any(not path or path.startswith('/') for pk, path in new.items() if pk != root.pk):
|
||||||
|
print('GoodDrive: invalid old space paths; use flatten_drive_root after inspection.')
|
||||||
|
return
|
||||||
|
other_paths = set(Space.objects.exclude(pk__in=new).values_list('relative_path', flat=True))
|
||||||
|
if any(path in other_paths for path in new.values()):
|
||||||
|
print('GoodDrive: space paths conflict; use flatten_drive_root after inspection.')
|
||||||
|
return
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for space in old:
|
||||||
|
space.relative_path = new[space.pk]
|
||||||
|
Space.objects.bulk_update(old, ['relative_path'])
|
||||||
|
print('GoodDrive: new database root uses DRIVE_STORAGE_ROOT directly (no /root/).')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [('drive', '0009_managed_spaces')]
|
||||||
|
operations = [migrations.RunPython(normalize_unused_root, migrations.RunPython.noop)]
|
||||||
14
backend/drive/migrations/0012_merge_20261014_0129.py
Normal file
14
backend/drive/migrations/0012_merge_20261014_0129.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Generated by Django 5.2.18 on 2026-10-13 23:29
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('drive', '0010_merge_0008_alter_spacegrant_id_0009_managed_spaces'),
|
||||||
|
('drive', '0011_normalize_empty_root'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/png" href="/favicon.svg" />
|
<link rel="icon" type="image/png" href="/icon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>GooDrive</title>
|
<title>GooDrive</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="17" fill="#455ea3"/><path d="M19 20h11l6 6h11a5 5 0 0 1 5 5v15a5 5 0 0 1-5 5H17a5 5 0 0 1-5-5V25a5 5 0 0 1 7-5Z" fill="#fff" opacity=".92"/><path d="M12 32h40" stroke="#455ea3" stroke-width="3"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 295 B |
BIN
frontend/public/icon.png
Normal file
BIN
frontend/public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import ContextActions, {spaceOptions, mobileDots} from './ContextActions'
|
import ContextActions, {spaceOptions, mobileDots} from './ContextActions'
|
||||||
import { MoreHorizontal, Plus } from 'lucide-react'
|
import { MoreHorizontal, Plus } from 'lucide-react'
|
||||||
import { Database, FolderPlus, HardDrive, LogOut, FolderLock, FolderOpen, X } from 'lucide-react'
|
import { Database, FolderPlus, LogOut, FolderLock, FolderOpen, X } from 'lucide-react'
|
||||||
import { sections } from '@/config/navigation'
|
import { sections } from '@/config/navigation'
|
||||||
import { fmtSize } from '@/lib/drive-format'
|
import { fmtSize } from '@/lib/drive-format'
|
||||||
|
|
||||||
@ -58,10 +58,14 @@ export default function DriveSidebar({ controller, user, onLogout }) {
|
|||||||
<aside className="drive-sidebar flex flex-col" data-open={sidebar}>
|
<aside className="drive-sidebar flex flex-col" data-open={sidebar}>
|
||||||
<div className="flex h-20 items-center gap-3 px-5">
|
<div className="flex h-20 items-center gap-3 px-5">
|
||||||
<span className="drive-brand flex size-11 items-center justify-center rounded-2xl text-white shadow-sm">
|
<span className="drive-brand flex size-11 items-center justify-center rounded-2xl text-white shadow-sm">
|
||||||
<HardDrive className="size-6" />
|
<img
|
||||||
|
src="/icon.png"
|
||||||
|
alt="Logo de GooDrive"
|
||||||
|
className="block size-full object-contain"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg font-bold tracking-tight">GoodDrive</div>
|
<div className="text-lg font-bold tracking-tight">GooDrive</div>
|
||||||
<div className="text-xs text-muted-foreground">Un Drive qui se partage</div>
|
<div className="text-xs text-muted-foreground">Un Drive qui se partage</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Trash2 } from 'lucide-react'
|
|
||||||
|
|
||||||
export default function DeleteDialog({controller: c}) {
|
export default function DeleteDialog({controller: c}) {
|
||||||
const {modal, setModal, action, busy, handleAction} = c
|
const {modal, setModal, action, busy, handleAction} = c
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { ArrowRight, HardDrive, LoaderCircle } from 'lucide-react'
|
import { ArrowRight, LoaderCircle } from 'lucide-react'
|
||||||
import { drive, message } from '@/api/client'
|
import { drive, message } from '@/api/client'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@ -35,7 +35,11 @@ export default function InvitationPage({ token }) {
|
|||||||
|
|
||||||
return <div className="flex min-h-dvh items-center justify-center bg-background p-4">
|
return <div className="flex min-h-dvh items-center justify-center bg-background p-4">
|
||||||
<div className="w-full max-w-lg space-y-6 rounded-3xl border bg-card p-7 shadow-xl sm:p-10">
|
<div className="w-full max-w-lg space-y-6 rounded-3xl border bg-card p-7 shadow-xl sm:p-10">
|
||||||
<div className="flex items-center gap-3"><div className="drive-brand grid size-12 place-items-center rounded-2xl text-white"><HardDrive /></div><div><h1 className="text-xl font-bold">GoodDrive</h1><p className="text-xs text-muted-foreground">Un espace pour ceux qui en ont besoin.</p></div></div>
|
<div className="flex items-center gap-3"><div className="drive-brand grid size-12 place-items-center rounded-2xl text-white"><img
|
||||||
|
src="/icon.png"
|
||||||
|
alt="Logo de GooDrive"
|
||||||
|
className="block size-full object-contain"
|
||||||
|
/></div><div><h1 className="text-xl font-bold">GoodDrive</h1><p className="text-xs text-muted-foreground">Un espace pour ceux qui en ont besoin.</p></div></div>
|
||||||
{done ? <div className="space-y-4"><h2 className="text-2xl font-semibold">Bienvenue sur GoodDrive !</h2><p>Ton espace personnel et ton espace partagé sont prêts. Tu peux maintenant te connecter.</p><Button onClick={() => window.location.assign('/')} className="w-full">Se connecter <ArrowRight /></Button></div>
|
{done ? <div className="space-y-4"><h2 className="text-2xl font-semibold">Bienvenue sur GoodDrive !</h2><p>Ton espace personnel et ton espace partagé sont prêts. Tu peux maintenant te connecter.</p><Button onClick={() => window.location.assign('/')} className="w-full">Se connecter <ArrowRight /></Button></div>
|
||||||
: info ? <><div><h2 className="text-2xl font-semibold">Créer mon espace</h2><p className="mt-2 text-sm text-muted-foreground">Tu es invité dans l'arborescence de « {info.parent_name} ». Tu disposeras de « Mes fichiers », privé, et d’un accès à l’espace partagé attribué.</p>{info.quota_bytes != null && <p className="mt-2 text-sm font-medium">Espace attribué : {fmtSize(info.quota_bytes)}</p>}</div>
|
: info ? <><div><h2 className="text-2xl font-semibold">Créer mon espace</h2><p className="mt-2 text-sm text-muted-foreground">Tu es invité dans l'arborescence de « {info.parent_name} ». Tu disposeras de « Mes fichiers », privé, et d’un accès à l’espace partagé attribué.</p>{info.quota_bytes != null && <p className="mt-2 text-sm font-medium">Espace attribué : {fmtSize(info.quota_bytes)}</p>}</div>
|
||||||
<form onSubmit={submit} className="space-y-3">
|
<form onSubmit={submit} className="space-y-3">
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useState } from 'react'
|
|||||||
import { api, logoutTokens, message, storeTokens } from '@/api/client'
|
import { api, logoutTokens, message, storeTokens } from '@/api/client'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { ArrowUpRight, HardDrive, LoaderCircle, ShieldCheck } from 'lucide-react'
|
import { ArrowUpRight, LoaderCircle, ShieldCheck } from 'lucide-react'
|
||||||
|
|
||||||
export default function LoginPage({ onSigned }) {
|
export default function LoginPage({ onSigned }) {
|
||||||
const [username, setUsername] = useState('')
|
const [username, setUsername] = useState('')
|
||||||
@ -35,7 +35,11 @@ export default function LoginPage({ onSigned }) {
|
|||||||
<div className="absolute -left-28 bottom-16 size-72 rounded-full bg-white/5" />
|
<div className="absolute -left-28 bottom-16 size-72 rounded-full bg-white/5" />
|
||||||
<div className="relative flex items-center gap-3">
|
<div className="relative flex items-center gap-3">
|
||||||
<div className="flex size-11 items-center justify-center rounded-2xl bg-white/15">
|
<div className="flex size-11 items-center justify-center rounded-2xl bg-white/15">
|
||||||
<HardDrive className="size-6" />
|
<img
|
||||||
|
src="/icon.png"
|
||||||
|
alt="Logo de GooDrive"
|
||||||
|
className="block size-full object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-semibold tracking-tight">GoodDrive</span>
|
<span className="text-xl font-semibold tracking-tight">GoodDrive</span>
|
||||||
</div>
|
</div>
|
||||||
@ -54,7 +58,11 @@ export default function LoginPage({ onSigned }) {
|
|||||||
<div className="flex min-h-[480px] flex-col justify-center p-7 sm:p-12">
|
<div className="flex min-h-[480px] flex-col justify-center p-7 sm:p-12">
|
||||||
<div className="mb-8 flex items-center gap-3 md:hidden">
|
<div className="mb-8 flex items-center gap-3 md:hidden">
|
||||||
<span className="drive-brand flex size-10 items-center justify-center rounded-xl text-white">
|
<span className="drive-brand flex size-10 items-center justify-center rounded-xl text-white">
|
||||||
<HardDrive />
|
<img
|
||||||
|
src="/icon.png"
|
||||||
|
alt="Logo de GooDrive"
|
||||||
|
className="block size-full object-contain"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<b className="text-xl">GoodDrive</b>
|
<b className="text-xl">GoodDrive</b>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user