progress in file storage
This commit is contained in:
parent
754d61e622
commit
bee18daf4a
|
|
@ -2,9 +2,10 @@ from typing import List, Optional
|
|||
from uuid import UUID
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
|
||||
from adapters.db.session import get_db
|
||||
from app.core.auth_dependency import get_current_user
|
||||
from app.core.auth_dependency import get_current_user, AuthContext
|
||||
from app.core.permissions import require_permission
|
||||
from app.core.responses import success_response
|
||||
from app.core.responses import ApiError
|
||||
|
|
@ -12,6 +13,7 @@ from app.core.i18n import locale_dependency
|
|||
from app.services.file_storage_service import FileStorageService
|
||||
from adapters.db.repositories.file_storage_repository import StorageConfigRepository, FileStorageRepository
|
||||
from adapters.db.models.user import User
|
||||
from adapters.db.models.file_storage import StorageConfig
|
||||
from adapters.api.v1.schema_models.file_storage import (
|
||||
StorageConfigCreateRequest,
|
||||
StorageConfigUpdateRequest,
|
||||
|
|
@ -36,19 +38,85 @@ async def list_all_files(
|
|||
is_temporary: Optional[bool] = Query(None),
|
||||
is_verified: Optional[bool] = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.file.view")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""لیست تمام فایلها با فیلتر"""
|
||||
try:
|
||||
file_service = FileStorageService(db)
|
||||
# Check permission
|
||||
if not current_user.has_app_permission("admin.file.view"):
|
||||
raise ApiError(
|
||||
code="FORBIDDEN",
|
||||
message=translator.t("FORBIDDEN", "دسترسی غیرمجاز"),
|
||||
http_status=403,
|
||||
translator=translator
|
||||
)
|
||||
|
||||
# TODO: پیادهسازی pagination و فیلترها
|
||||
statistics = await file_service.get_storage_statistics()
|
||||
file_repo = FileStorageRepository(db)
|
||||
|
||||
# محاسبه offset برای pagination
|
||||
offset = (page - 1) * size
|
||||
|
||||
# ساخت فیلترها
|
||||
filters = []
|
||||
if module_context:
|
||||
filters.append(FileStorage.module_context == module_context)
|
||||
if is_temporary is not None:
|
||||
filters.append(FileStorage.is_temporary == is_temporary)
|
||||
if is_verified is not None:
|
||||
filters.append(FileStorage.is_verified == is_verified)
|
||||
|
||||
# اضافه کردن فیلتر حذف نشده
|
||||
filters.append(FileStorage.deleted_at.is_(None))
|
||||
|
||||
# دریافت فایلها با فیلتر و pagination
|
||||
files_query = db.query(FileStorage).filter(and_(*filters))
|
||||
total_count = files_query.count()
|
||||
|
||||
files = files_query.order_by(FileStorage.created_at.desc()).offset(offset).limit(size).all()
|
||||
|
||||
# تبدیل به فرمت مناسب
|
||||
files_data = []
|
||||
for file in files:
|
||||
files_data.append({
|
||||
"id": str(file.id),
|
||||
"original_name": file.original_name,
|
||||
"stored_name": file.stored_name,
|
||||
"file_size": file.file_size,
|
||||
"mime_type": file.mime_type,
|
||||
"storage_type": file.storage_type,
|
||||
"module_context": file.module_context,
|
||||
"context_id": str(file.context_id) if file.context_id else None,
|
||||
"is_temporary": file.is_temporary,
|
||||
"is_verified": file.is_verified,
|
||||
"is_active": file.is_active,
|
||||
"created_at": file.created_at.isoformat(),
|
||||
"updated_at": file.updated_at.isoformat(),
|
||||
"expires_at": file.expires_at.isoformat() if file.expires_at else None,
|
||||
"uploaded_by": file.uploaded_by,
|
||||
"checksum": file.checksum
|
||||
})
|
||||
|
||||
# محاسبه pagination info
|
||||
total_pages = (total_count + size - 1) // size
|
||||
has_next = page < total_pages
|
||||
has_prev = page > 1
|
||||
|
||||
data = {
|
||||
"statistics": statistics,
|
||||
"message": translator.t("FILE_LIST_NOT_IMPLEMENTED", "File list endpoint - to be implemented")
|
||||
"files": files_data,
|
||||
"pagination": {
|
||||
"page": page,
|
||||
"size": size,
|
||||
"total_count": total_count,
|
||||
"total_pages": total_pages,
|
||||
"has_next": has_next,
|
||||
"has_prev": has_prev
|
||||
},
|
||||
"filters": {
|
||||
"module_context": module_context,
|
||||
"is_temporary": is_temporary,
|
||||
"is_verified": is_verified
|
||||
}
|
||||
}
|
||||
|
||||
return success_response(data, request)
|
||||
|
|
@ -65,7 +133,7 @@ async def list_all_files(
|
|||
async def get_unverified_files(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.file.view")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""فایلهای تایید نشده"""
|
||||
|
|
@ -102,7 +170,7 @@ async def get_unverified_files(
|
|||
async def cleanup_temporary_files(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.file.cleanup")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""پاکسازی فایلهای موقت"""
|
||||
|
|
@ -130,7 +198,7 @@ async def force_delete_file(
|
|||
file_id: UUID,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.file.delete")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""حذف اجباری فایل"""
|
||||
|
|
@ -164,7 +232,7 @@ async def restore_file(
|
|||
file_id: UUID,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.file.restore")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""بازیابی فایل حذف شده"""
|
||||
|
|
@ -197,7 +265,7 @@ async def restore_file(
|
|||
async def get_file_statistics(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.file.view")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""آمار استفاده از فضای ذخیرهسازی"""
|
||||
|
|
@ -220,13 +288,22 @@ async def get_file_statistics(
|
|||
async def get_storage_configs(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.storage.view")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""لیست تنظیمات ذخیرهسازی"""
|
||||
try:
|
||||
# Check permission
|
||||
if not current_user.has_app_permission("admin.storage.view"):
|
||||
raise ApiError(
|
||||
code="FORBIDDEN",
|
||||
message=translator.t("FORBIDDEN", "دسترسی غیرمجاز"),
|
||||
http_status=403,
|
||||
translator=translator
|
||||
)
|
||||
|
||||
config_repo = StorageConfigRepository(db)
|
||||
configs = await config_repo.get_all_configs()
|
||||
configs = config_repo.get_all_configs()
|
||||
|
||||
data = {
|
||||
"configs": [
|
||||
|
|
@ -236,6 +313,7 @@ async def get_storage_configs(
|
|||
"storage_type": config.storage_type,
|
||||
"is_default": config.is_default,
|
||||
"is_active": config.is_active,
|
||||
"config_data": config.config_data,
|
||||
"created_at": config.created_at.isoformat()
|
||||
}
|
||||
for config in configs
|
||||
|
|
@ -257,7 +335,7 @@ async def create_storage_config(
|
|||
request: Request,
|
||||
config_request: StorageConfigCreateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.storage.create")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""ایجاد تنظیمات ذخیرهسازی جدید"""
|
||||
|
|
@ -268,8 +346,9 @@ async def create_storage_config(
|
|||
name=config_request.name,
|
||||
storage_type=config_request.storage_type,
|
||||
config_data=config_request.config_data,
|
||||
created_by=current_user.id,
|
||||
is_default=config_request.is_default
|
||||
created_by=current_user.get_user_id(),
|
||||
is_default=config_request.is_default,
|
||||
is_active=config_request.is_active
|
||||
)
|
||||
|
||||
data = {
|
||||
|
|
@ -293,7 +372,7 @@ async def update_storage_config(
|
|||
request: Request,
|
||||
config_request: StorageConfigUpdateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.storage.update")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""بروزرسانی تنظیمات ذخیرهسازی"""
|
||||
|
|
@ -317,7 +396,7 @@ async def set_default_storage_config(
|
|||
config_id: UUID,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.storage.update")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""تنظیم به عنوان پیشفرض"""
|
||||
|
|
@ -351,7 +430,7 @@ async def delete_storage_config(
|
|||
config_id: UUID,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.storage.delete")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""حذف تنظیمات ذخیرهسازی"""
|
||||
|
|
@ -382,17 +461,42 @@ async def delete_storage_config(
|
|||
|
||||
@router.post("/storage-configs/{config_id}/test", response_model=dict)
|
||||
async def test_storage_config(
|
||||
config_id: UUID,
|
||||
config_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_permission("admin.storage.test")),
|
||||
current_user: AuthContext = Depends(get_current_user),
|
||||
translator = Depends(locale_dependency)
|
||||
):
|
||||
"""تست اتصال به storage"""
|
||||
try:
|
||||
# TODO: پیادهسازی تست اتصال
|
||||
data = {"message": translator.t("STORAGE_CONNECTION_TEST_NOT_IMPLEMENTED", "Storage connection test - to be implemented")}
|
||||
config_repo = StorageConfigRepository(db)
|
||||
config = db.query(StorageConfig).filter(StorageConfig.id == config_id).first()
|
||||
|
||||
if not config:
|
||||
raise ApiError(
|
||||
code="STORAGE_CONFIG_NOT_FOUND",
|
||||
message=translator.t("STORAGE_CONFIG_NOT_FOUND", "تنظیمات ذخیرهسازی یافت نشد"),
|
||||
http_status=404,
|
||||
translator=translator
|
||||
)
|
||||
|
||||
# تست اتصال بر اساس نوع storage
|
||||
test_result = await _test_storage_connection(config)
|
||||
|
||||
if test_result["success"]:
|
||||
data = {
|
||||
"message": translator.t("STORAGE_CONNECTION_SUCCESS", "اتصال به storage موفقیتآمیز بود"),
|
||||
"test_result": test_result
|
||||
}
|
||||
else:
|
||||
data = {
|
||||
"message": translator.t("STORAGE_CONNECTION_FAILED", "اتصال به storage ناموفق بود"),
|
||||
"test_result": test_result
|
||||
}
|
||||
|
||||
return success_response(data, request)
|
||||
except ApiError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ApiError(
|
||||
code="TEST_STORAGE_CONFIG_ERROR",
|
||||
|
|
@ -400,3 +504,107 @@ async def test_storage_config(
|
|||
http_status=500,
|
||||
translator=translator
|
||||
)
|
||||
|
||||
|
||||
# Helper function for testing storage connections
|
||||
async def _test_storage_connection(config: StorageConfig) -> dict:
|
||||
"""تست اتصال به storage بر اساس نوع آن"""
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
if config.storage_type == "local":
|
||||
return await _test_local_storage(config)
|
||||
elif config.storage_type == "ftp":
|
||||
return await _test_ftp_storage(config)
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"نوع storage پشتیبانی نشده: {config.storage_type}",
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
|
||||
async def _test_local_storage(config: StorageConfig) -> dict:
|
||||
"""تست اتصال به local storage"""
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
base_path = config.config_data.get("base_path", "/tmp/hesabix_files")
|
||||
|
||||
# بررسی وجود مسیر
|
||||
if not os.path.exists(base_path):
|
||||
# تلاش برای ایجاد مسیر
|
||||
os.makedirs(base_path, exist_ok=True)
|
||||
|
||||
# بررسی دسترسی نوشتن
|
||||
test_file_path = os.path.join(base_path, f"test_connection_{datetime.utcnow().timestamp()}.txt")
|
||||
|
||||
# نوشتن فایل تست
|
||||
with open(test_file_path, "w") as f:
|
||||
f.write("Test connection file")
|
||||
|
||||
# خواندن فایل تست
|
||||
with open(test_file_path, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# حذف فایل تست
|
||||
os.remove(test_file_path)
|
||||
|
||||
if content == "Test connection file":
|
||||
return {
|
||||
"success": True,
|
||||
"message": "اتصال به local storage موفقیتآمیز بود",
|
||||
"storage_type": "local",
|
||||
"base_path": base_path,
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "خطا در خواندن فایل تست",
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except PermissionError:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "دسترسی به مسیر ذخیرهسازی وجود ندارد",
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"خطا در تست local storage: {str(e)}",
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
|
||||
async def _test_ftp_storage(config: StorageConfig) -> dict:
|
||||
"""تست اتصال به FTP storage"""
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
# TODO: پیادهسازی تست FTP
|
||||
# فعلاً فقط ساختار کلی را برمیگردانیم
|
||||
return {
|
||||
"success": False,
|
||||
"error": "تست FTP هنوز پیادهسازی نشده است",
|
||||
"storage_type": "ftp",
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"خطا در تست FTP storage: {str(e)}",
|
||||
"tested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class StorageConfigCreateRequest(BaseModel):
|
|||
storage_type: str = Field(..., description="نوع ذخیرهسازی")
|
||||
config_data: Dict[str, Any] = Field(..., description="دادههای پیکربندی")
|
||||
is_default: bool = Field(default=False, description="آیا پیشفرض است")
|
||||
is_active: bool = Field(default=True, description="آیا فعال است")
|
||||
|
||||
|
||||
class StorageConfigUpdateRequest(BaseModel):
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from adapters.db.repositories.base_repo import BaseRepository
|
|||
|
||||
class FileStorageRepository(BaseRepository[FileStorage]):
|
||||
def __init__(self, db: Session):
|
||||
super().__init__(FileStorage, db)
|
||||
super().__init__(db, FileStorage)
|
||||
|
||||
async def create_file(
|
||||
self,
|
||||
|
|
@ -177,15 +177,16 @@ class FileStorageRepository(BaseRepository[FileStorage]):
|
|||
|
||||
class StorageConfigRepository(BaseRepository[StorageConfig]):
|
||||
def __init__(self, db: Session):
|
||||
super().__init__(StorageConfig, db)
|
||||
super().__init__(db, StorageConfig)
|
||||
|
||||
async def create_config(
|
||||
self,
|
||||
name: str,
|
||||
storage_type: str,
|
||||
config_data: Dict,
|
||||
created_by: UUID,
|
||||
is_default: bool = False
|
||||
created_by: int,
|
||||
is_default: bool = False,
|
||||
is_active: bool = True
|
||||
) -> StorageConfig:
|
||||
# اگر این config به عنوان پیشفرض تنظیم میشود، بقیه را غیرفعال کن
|
||||
if is_default:
|
||||
|
|
@ -196,7 +197,8 @@ class StorageConfigRepository(BaseRepository[StorageConfig]):
|
|||
storage_type=storage_type,
|
||||
config_data=config_data,
|
||||
created_by=created_by,
|
||||
is_default=is_default
|
||||
is_default=is_default,
|
||||
is_active=is_active
|
||||
)
|
||||
|
||||
self.db.add(storage_config)
|
||||
|
|
@ -212,7 +214,7 @@ class StorageConfigRepository(BaseRepository[StorageConfig]):
|
|||
)
|
||||
).first()
|
||||
|
||||
async def get_all_configs(self) -> List[StorageConfig]:
|
||||
def get_all_configs(self) -> List[StorageConfig]:
|
||||
return self.db.query(StorageConfig).filter(
|
||||
StorageConfig.is_active == True
|
||||
).order_by(desc(StorageConfig.created_at)).all()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
||||
import '../../../core/api_client.dart';
|
||||
|
||||
class FileManagementWidget extends StatefulWidget {
|
||||
const FileManagementWidget({super.key});
|
||||
|
|
@ -36,49 +37,26 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
});
|
||||
|
||||
try {
|
||||
// TODO: Call API to load files
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
|
||||
setState(() {
|
||||
_allFiles = [
|
||||
{
|
||||
'id': '1',
|
||||
'original_name': 'document.pdf',
|
||||
'file_size': 1024000,
|
||||
'mime_type': 'application/pdf',
|
||||
'module_context': 'tickets',
|
||||
'created_at': '2024-01-01T10:00:00Z',
|
||||
'expires_at': null,
|
||||
'is_temporary': false,
|
||||
'is_verified': true,
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'original_name': 'image.jpg',
|
||||
'file_size': 512000,
|
||||
'mime_type': 'image/jpeg',
|
||||
'module_context': 'accounting',
|
||||
'created_at': '2024-01-02T11:00:00Z',
|
||||
'expires_at': '2024-01-09T11:00:00Z',
|
||||
'is_temporary': true,
|
||||
'is_verified': false,
|
||||
},
|
||||
{
|
||||
'id': '3',
|
||||
'original_name': 'spreadsheet.xlsx',
|
||||
'file_size': 256000,
|
||||
'mime_type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'module_context': 'reports',
|
||||
'created_at': '2024-01-03T12:00:00Z',
|
||||
'expires_at': null,
|
||||
'is_temporary': false,
|
||||
'is_verified': true,
|
||||
},
|
||||
];
|
||||
// Call API to load files
|
||||
final response = await api.get('/api/v1/admin/files/');
|
||||
final unverifiedResponse = await api.get('/api/v1/admin/files/unverified');
|
||||
|
||||
_unverifiedFiles = _allFiles.where((file) => file['is_verified'] == false).toList();
|
||||
_isLoading = false;
|
||||
});
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
final files = response.data['data']['files'] as List<dynamic>;
|
||||
final unverifiedFiles = unverifiedResponse.data != null && unverifiedResponse.data['success'] == true
|
||||
? unverifiedResponse.data['data']['unverified_files'] as List<dynamic>
|
||||
: <dynamic>[];
|
||||
|
||||
setState(() {
|
||||
_allFiles = files.cast<Map<String, dynamic>>();
|
||||
_unverifiedFiles = unverifiedFiles.cast<Map<String, dynamic>>();
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در دریافت فایلها');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = e.toString();
|
||||
|
|
@ -87,8 +65,9 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _forceDeleteFile(String fileId) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
|
|
@ -112,17 +91,21 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
// TODO: Call API to force delete file
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
final response = await api.delete('/api/v1/admin/files/$fileId');
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.fileDeleted),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.fileDeleted),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
_loadFiles();
|
||||
_loadFiles();
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در حذف فایل');
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
|
@ -135,7 +118,7 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
}
|
||||
|
||||
Future<void> _restoreFile(String fileId) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
|
|
@ -156,17 +139,21 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
// TODO: Call API to restore file
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
final response = await api.put('/api/v1/admin/files/$fileId/restore');
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.fileRestored),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.fileRestored),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
_loadFiles();
|
||||
_loadFiles();
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در بازیابی فایل');
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
|
@ -192,8 +179,7 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
|
|
@ -224,7 +210,7 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
}
|
||||
|
||||
Widget _buildFilesList(List<Map<String, dynamic>> files) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (_isLoading) {
|
||||
|
|
@ -337,7 +323,7 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
children: [
|
||||
Icon(Icons.delete, color: theme.colorScheme.error),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.forceDelete),
|
||||
Text(AppLocalizations.of(context).forceDelete),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -347,7 +333,7 @@ class _FileManagementWidgetState extends State<FileManagementWidget>
|
|||
children: [
|
||||
Icon(Icons.restore, color: theme.colorScheme.primary),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.restoreFile),
|
||||
Text(AppLocalizations.of(context).restoreFile),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
||||
import '../../../core/api_client.dart';
|
||||
|
||||
class FileStatisticsWidget extends StatefulWidget {
|
||||
const FileStatisticsWidget({super.key});
|
||||
|
|
@ -26,18 +27,17 @@ class _FileStatisticsWidgetState extends State<FileStatisticsWidget> {
|
|||
});
|
||||
|
||||
try {
|
||||
// TODO: Call API to load statistics
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
final response = await api.get('/api/v1/admin/files/statistics');
|
||||
|
||||
setState(() {
|
||||
_statistics = {
|
||||
'total_files': 1250,
|
||||
'total_size': 2048576000, // 2GB in bytes
|
||||
'temporary_files': 45,
|
||||
'unverified_files': 12,
|
||||
};
|
||||
_isLoading = false;
|
||||
});
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
setState(() {
|
||||
_statistics = response.data['data'];
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در دریافت آمار');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = e.toString();
|
||||
|
|
@ -46,8 +46,9 @@ class _FileStatisticsWidgetState extends State<FileStatisticsWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _cleanupTemporaryFiles() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
|
|
@ -68,17 +69,21 @@ class _FileStatisticsWidgetState extends State<FileStatisticsWidget> {
|
|||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
// TODO: Call API to cleanup temporary files
|
||||
await Future.delayed(const Duration(seconds: 2)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
final response = await api.post('/api/v1/admin/files/cleanup-temporary');
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.cleanupCompleted),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.cleanupCompleted),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
_loadStatistics();
|
||||
_loadStatistics();
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در پاکسازی فایلهای موقت');
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
|
@ -90,6 +95,7 @@ class _FileStatisticsWidgetState extends State<FileStatisticsWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
String _formatFileSize(int bytes) {
|
||||
if (bytes < 1024) return '$bytes B';
|
||||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||
|
|
@ -99,7 +105,7 @@ class _FileStatisticsWidgetState extends State<FileStatisticsWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (_isLoading) {
|
||||
|
|
|
|||
|
|
@ -19,165 +19,129 @@ class StorageConfigCard extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final isDefault = config['is_default'] == true;
|
||||
final isActive = config['is_active'] == true;
|
||||
final storageType = config['storage_type'] as String;
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
storageType == 'local' ? Icons.storage : Icons.cloud_upload,
|
||||
_getStorageIcon(config['storage_type']),
|
||||
color: theme.colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
config['name'] ?? '',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isDefault)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
l10n.isDefault,
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
config['name'] ?? 'Unknown',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isActive)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.error,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'Inactive',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onError,
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_getStorageTypeName(config['storage_type']),
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Status badges
|
||||
Row(
|
||||
children: [
|
||||
if (isDefault)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
l10n.isDefault,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? Colors.green : Colors.red,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
isActive ? l10n.isActive : 'غیرفعال',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
l10n.storageType,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
storageType == 'local' ? l10n.localStorage : l10n.ftpStorage,
|
||||
style: theme.textTheme.bodySmall,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (storageType == 'local') ...[
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.folder_outlined,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
l10n.basePath,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
config['config_data']?['base_path'] ?? '',
|
||||
style: theme.textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
] else if (storageType == 'ftp') ...[
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cloud_outlined,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${l10n.ftpHost}: ${config['config_data']?['host'] ?? ''}',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${l10n.ftpUsername}: ${config['config_data']?['username'] ?? ''}',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Configuration details
|
||||
_buildConfigDetails(context, config),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Actions
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (onEdit != null)
|
||||
TextButton.icon(
|
||||
onPressed: onEdit,
|
||||
icon: const Icon(Icons.edit, size: 16),
|
||||
label: Text(l10n.edit),
|
||||
),
|
||||
if (onTestConnection != null)
|
||||
TextButton.icon(
|
||||
onPressed: onTestConnection,
|
||||
icon: const Icon(Icons.wifi_protected_setup, size: 16),
|
||||
label: Text(l10n.testConnection),
|
||||
),
|
||||
if (onSetDefault != null)
|
||||
if (onEdit != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
TextButton.icon(
|
||||
onPressed: onEdit,
|
||||
icon: const Icon(Icons.edit, size: 16),
|
||||
label: Text(l10n.edit),
|
||||
),
|
||||
],
|
||||
if (onSetDefault != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
TextButton.icon(
|
||||
onPressed: onSetDefault,
|
||||
icon: const Icon(Icons.star, size: 16),
|
||||
label: Text(l10n.setAsDefault),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
if (onDelete != null)
|
||||
],
|
||||
if (onDelete != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
TextButton.icon(
|
||||
onPressed: onDelete,
|
||||
icon: const Icon(Icons.delete, size: 16),
|
||||
|
|
@ -186,6 +150,7 @@ class StorageConfigCard extends StatelessWidget {
|
|||
foregroundColor: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
@ -193,4 +158,116 @@ class StorageConfigCard extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConfigDetails(BuildContext context, Map<String, dynamic> config) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final configData = config['config_data'] ?? {};
|
||||
final storageType = config['storage_type'];
|
||||
|
||||
if (storageType == 'local') {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
context,
|
||||
l10n.basePath,
|
||||
configData['base_path'] ?? 'N/A',
|
||||
Icons.folder,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (storageType == 'ftp') {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
context,
|
||||
l10n.ftpHost,
|
||||
configData['host'] ?? 'N/A',
|
||||
Icons.dns,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDetailRow(
|
||||
context,
|
||||
l10n.ftpPort,
|
||||
configData['port']?.toString() ?? 'N/A',
|
||||
Icons.settings_ethernet,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDetailRow(
|
||||
context,
|
||||
l10n.ftpUsername,
|
||||
configData['username'] ?? 'N/A',
|
||||
Icons.person,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDetailRow(
|
||||
context,
|
||||
l10n.ftpDirectory,
|
||||
configData['directory'] ?? 'N/A',
|
||||
Icons.folder,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(
|
||||
BuildContext context,
|
||||
String label,
|
||||
String value,
|
||||
IconData icon,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$label: ',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.8),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getStorageIcon(String storageType) {
|
||||
switch (storageType) {
|
||||
case 'local':
|
||||
return Icons.storage;
|
||||
case 'ftp':
|
||||
return Icons.cloud_upload;
|
||||
default:
|
||||
return Icons.storage;
|
||||
}
|
||||
}
|
||||
|
||||
String _getStorageTypeName(String storageType) {
|
||||
switch (storageType) {
|
||||
case 'local':
|
||||
return 'Local Storage';
|
||||
case 'ftp':
|
||||
return 'FTP Storage';
|
||||
default:
|
||||
return 'Unknown Storage';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
||||
import '../../../core/api_client.dart';
|
||||
|
||||
class StorageConfigFormDialog extends StatefulWidget {
|
||||
final Map<String, dynamic>? config;
|
||||
|
|
@ -94,19 +95,24 @@ class _StorageConfigFormDialogState extends State<StorageConfigFormDialog> {
|
|||
});
|
||||
|
||||
try {
|
||||
// TODO: Call API to save configuration
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
final response = await api.post(
|
||||
'/api/v1/admin/files/storage-configs/',
|
||||
data: {
|
||||
'name': _nameController.text,
|
||||
'storage_type': _selectedStorageType,
|
||||
'is_default': _isDefault,
|
||||
'is_active': _isActive,
|
||||
'config_data': _buildConfigData(),
|
||||
},
|
||||
);
|
||||
|
||||
final configData = {
|
||||
'name': _nameController.text,
|
||||
'storage_type': _selectedStorageType,
|
||||
'is_default': _isDefault,
|
||||
'is_active': _isActive,
|
||||
'config_data': _buildConfigData(),
|
||||
};
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop(configData);
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop(response.data['data']);
|
||||
}
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در ذخیره تنظیمات');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
|
|
@ -128,7 +134,7 @@ class _StorageConfigFormDialogState extends State<StorageConfigFormDialog> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final isEditing = widget.config != null;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
||||
import 'package:hesabix_ui/widgets/admin/file_storage/storage_config_form_dialog.dart';
|
||||
import 'package:hesabix_ui/widgets/admin/file_storage/storage_config_card.dart';
|
||||
import '../../../core/api_client.dart';
|
||||
|
||||
class StorageConfigListWidget extends StatefulWidget {
|
||||
const StorageConfigListWidget({super.key});
|
||||
|
|
@ -28,41 +29,18 @@ class _StorageConfigListWidgetState extends State<StorageConfigListWidget> {
|
|||
});
|
||||
|
||||
try {
|
||||
// TODO: Call API to load storage configurations
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
final response = await api.get('/api/v1/admin/files/storage-configs/');
|
||||
|
||||
// Mock data for now
|
||||
setState(() {
|
||||
_storageConfigs = [
|
||||
{
|
||||
'id': '1',
|
||||
'name': 'Local Storage Default',
|
||||
'storage_type': 'local',
|
||||
'is_default': true,
|
||||
'is_active': true,
|
||||
'config_data': {
|
||||
'base_path': '/var/hesabix/files'
|
||||
},
|
||||
'created_at': '2024-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'name': 'FTP Backup',
|
||||
'storage_type': 'ftp',
|
||||
'is_default': false,
|
||||
'is_active': true,
|
||||
'config_data': {
|
||||
'host': 'ftp.example.com',
|
||||
'port': 21,
|
||||
'username': 'hesabix',
|
||||
'password': '***',
|
||||
'directory': '/hesabix/files'
|
||||
},
|
||||
'created_at': '2024-01-02T00:00:00Z',
|
||||
},
|
||||
];
|
||||
_isLoading = false;
|
||||
});
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
setState(() {
|
||||
_storageConfigs = (response.data['data']['configs'] as List<dynamic>)
|
||||
.cast<Map<String, dynamic>>();
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در دریافت تنظیمات ذخیرهسازی');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = e.toString();
|
||||
|
|
@ -71,6 +49,7 @@ class _StorageConfigListWidgetState extends State<StorageConfigListWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _addStorageConfig() async {
|
||||
final result = await showDialog<Map<String, dynamic>>(
|
||||
context: context,
|
||||
|
|
@ -94,7 +73,7 @@ class _StorageConfigListWidgetState extends State<StorageConfigListWidget> {
|
|||
}
|
||||
|
||||
Future<void> _setAsDefault(String configId) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
try {
|
||||
// TODO: Call API to set as default
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
|
|
@ -118,29 +97,44 @@ class _StorageConfigListWidgetState extends State<StorageConfigListWidget> {
|
|||
}
|
||||
|
||||
Future<void> _testConnection(String configId) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
try {
|
||||
// TODO: Call API to test connection
|
||||
await Future.delayed(const Duration(seconds: 2)); // Simulate API call
|
||||
final api = ApiClient();
|
||||
final response = await api.post('/api/v1/admin/files/storage-configs/$configId/test');
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.connectionSuccessful),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
if (response.data != null && response.data['success'] == true) {
|
||||
final testResult = response.data['data']['test_result'];
|
||||
if (testResult['success'] == true) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.connectionSuccessful),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${l10n.connectionFailed}: ${testResult['error']}'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw Exception(response.data?['message'] ?? 'خطا در تست اتصال');
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.connectionFailed),
|
||||
content: Text('${l10n.connectionFailed}: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _deleteConfig(String configId) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
|
|
@ -185,7 +179,7 @@ class _StorageConfigListWidgetState extends State<StorageConfigListWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (_isLoading) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue