Compare commits
12 Commits
8d950cf042
...
c59f14f7d3
| Author | SHA1 | Date | |
|---|---|---|---|
| c59f14f7d3 | |||
|
|
1880548e16 | ||
|
|
a6d0143d19 | ||
|
|
9190a159b5 | ||
|
|
9684406295 | ||
|
|
fab814e179 | ||
|
|
d625879dd2 | ||
|
|
ad69872828 | ||
|
|
96716653a2 | ||
|
|
a6bee5be15 | ||
|
|
fdaa7232a1 | ||
|
|
409c9950de |
2
.env
2
.env
@@ -27,7 +27,7 @@ APP_SECRET=6aa2ea989e29de27bc42a77db9849b87
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
DATABASE_URL="mysql://vultradmin:AVNS_jn444_0nHCHAvnZkTFN@vultr-prod-a6de266e-e9c6-477c-abf3-7ec2e7a7bfc8-vultr-prod-3195.vultrdb.com:18140/defaultdb?serverVersion=8.0.32&charset=utf8mb4"
|
||||
DATABASE_URL=""
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
# Console login credentials
|
||||
|
||||
@@ -1,39 +1,100 @@
|
||||
# Console API Routes
|
||||
console_api_credentials:
|
||||
console_api_credentials_list:
|
||||
path: /api/credentials
|
||||
controller: App\Controller\ConsoleApiController::credentials
|
||||
methods: [GET, POST]
|
||||
controller: App\Controller\ConsoleApiController::listCredentials
|
||||
methods: [GET]
|
||||
|
||||
console_api_credential_detail:
|
||||
console_api_credentials_create:
|
||||
path: /api/credentials
|
||||
controller: App\Controller\ConsoleApiController::createCredential
|
||||
methods: [POST]
|
||||
|
||||
console_api_credential_get:
|
||||
path: /api/credentials/{id}
|
||||
controller: App\Controller\ConsoleApiController::credentialDetail
|
||||
methods: [GET, PUT, DELETE]
|
||||
controller: App\Controller\ConsoleApiController::getCredential
|
||||
methods: [GET]
|
||||
requirements:
|
||||
id: '\d+'
|
||||
|
||||
console_api_buckets:
|
||||
path: /api/buckets
|
||||
controller: App\Controller\ConsoleApiController::buckets
|
||||
methods: [GET, POST]
|
||||
console_api_credential_update:
|
||||
path: /api/credentials/{id}
|
||||
controller: App\Controller\ConsoleApiController::updateCredential
|
||||
methods: [PUT]
|
||||
requirements:
|
||||
id: '\d+'
|
||||
|
||||
console_api_bucket_detail:
|
||||
console_api_credential_delete:
|
||||
path: /api/credentials/{id}
|
||||
controller: App\Controller\ConsoleApiController::deleteCredential
|
||||
methods: [DELETE]
|
||||
requirements:
|
||||
id: '\d+'
|
||||
|
||||
console_api_buckets_list:
|
||||
path: /api/buckets
|
||||
controller: App\Controller\ConsoleApiController::listBuckets
|
||||
methods: [GET]
|
||||
|
||||
console_api_buckets_create:
|
||||
path: /api/buckets
|
||||
controller: App\Controller\ConsoleApiController::createBucket
|
||||
methods: [POST]
|
||||
|
||||
console_api_bucket_get:
|
||||
path: /api/buckets/{name}
|
||||
controller: App\Controller\ConsoleApiController::bucketDetail
|
||||
methods: [GET, DELETE]
|
||||
controller: App\Controller\ConsoleApiController::getBucket
|
||||
methods: [GET]
|
||||
requirements:
|
||||
name: '[a-z0-9\-\.]+'
|
||||
|
||||
console_api_objects:
|
||||
console_api_bucket_delete:
|
||||
path: /api/buckets/{name}
|
||||
controller: App\Controller\ConsoleApiController::deleteBucket
|
||||
methods: [DELETE]
|
||||
requirements:
|
||||
name: '[a-z0-9\-\.]+'
|
||||
|
||||
console_api_objects_list:
|
||||
path: /api/buckets/{bucketName}/objects
|
||||
controller: App\Controller\ConsoleApiController::objects
|
||||
methods: [GET, POST, DELETE]
|
||||
controller: App\Controller\ConsoleApiController::listObjects
|
||||
methods: [GET]
|
||||
requirements:
|
||||
bucketName: '[a-z0-9\-\.]+'
|
||||
|
||||
console_api_object_detail:
|
||||
console_api_create_object:
|
||||
path: /api/buckets/{bucketName}/objects
|
||||
controller: App\Controller\ConsoleApiController::createObject
|
||||
methods: [POST]
|
||||
requirements:
|
||||
bucketName: '[a-z0-9\-\.]+'
|
||||
|
||||
console_api_delete_objects:
|
||||
path: /api/buckets/{bucketName}/objects
|
||||
controller: App\Controller\ConsoleApiController::deleteObjects
|
||||
methods: [DELETE]
|
||||
requirements:
|
||||
bucketName: '[a-z0-9\-\.]+'
|
||||
|
||||
console_api_object_get:
|
||||
path: /api/buckets/{bucketName}/objects/{objectKey}
|
||||
controller: App\Controller\ConsoleApiController::objectDetail
|
||||
methods: [GET, DELETE]
|
||||
controller: App\Controller\ConsoleApiController::getObject
|
||||
methods: [GET]
|
||||
requirements:
|
||||
bucketName: '[a-z0-9\-\.]+'
|
||||
objectKey: '.+'
|
||||
|
||||
console_api_object_delete:
|
||||
path: /api/buckets/{bucketName}/objects/{objectKey}
|
||||
controller: App\Controller\ConsoleApiController::deleteObject
|
||||
methods: [DELETE]
|
||||
requirements:
|
||||
bucketName: '[a-z0-9\-\.]+'
|
||||
objectKey: '.+'
|
||||
|
||||
console_api_presigned_upload:
|
||||
path: /api/buckets/{bucketName}/objects/{objectKey}/presigned-upload
|
||||
controller: App\Controller\ConsoleApiController::createPresignedUpload
|
||||
methods: [POST]
|
||||
requirements:
|
||||
bucketName: '[a-z0-9\-\.]+'
|
||||
objectKey: '.+'
|
||||
@@ -45,10 +106,15 @@ console_api_multipart_uploads:
|
||||
requirements:
|
||||
bucketName: '[a-z0-9\-\.]+'
|
||||
|
||||
console_api_presigned_urls:
|
||||
console_api_presigned_urls_list:
|
||||
path: /api/presigned-urls
|
||||
controller: App\Controller\ConsoleApiController::presignedUrls
|
||||
methods: [GET, POST]
|
||||
controller: App\Controller\ConsoleApiController::listPresignedUrls
|
||||
methods: [GET]
|
||||
|
||||
console_api_presigned_urls_create:
|
||||
path: /api/presigned-urls
|
||||
controller: App\Controller\ConsoleApiController::createPresignedUrl
|
||||
methods: [POST]
|
||||
|
||||
console_api_stats:
|
||||
path: /api/stats
|
||||
@@ -56,10 +122,15 @@ console_api_stats:
|
||||
methods: [GET]
|
||||
|
||||
# Console Authentication Routes
|
||||
console_login:
|
||||
console_login_form:
|
||||
path: /console/login
|
||||
controller: App\Controller\ConsoleController::login
|
||||
methods: [GET, POST]
|
||||
controller: App\Controller\ConsoleController::loginForm
|
||||
methods: [GET]
|
||||
|
||||
console_login_submit:
|
||||
path: /console/login
|
||||
controller: App\Controller\ConsoleController::loginSubmit
|
||||
methods: [POST]
|
||||
|
||||
console_logout:
|
||||
path: /console/logout
|
||||
|
||||
@@ -28,315 +28,446 @@ class ConsoleApiController extends AbstractController
|
||||
}
|
||||
|
||||
// Credentials Management
|
||||
public function credentials(Request $request): JsonResponse
|
||||
public function listCredentials(Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
$resp = $this->checkAuth($request);
|
||||
if ($resp !== null) {
|
||||
return $resp;
|
||||
}
|
||||
if ($request->getMethod() === 'GET') {
|
||||
$credentials = $this->entityManager->getRepository(S3Credential::class)->findAll();
|
||||
|
||||
return new JsonResponse([
|
||||
'credentials' => array_map(function($cred) {
|
||||
return [
|
||||
'id' => $cred->getId(),
|
||||
'access_key' => $cred->getAccessKey(),
|
||||
'user_name' => $cred->getUserName(),
|
||||
'is_active' => $cred->isActive(),
|
||||
'created_at' => $cred->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'bucket_count' => $cred->getBuckets()->count()
|
||||
];
|
||||
}, $credentials)
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
$accessKey = $data['access_key'] ?? 'AKIA' . strtoupper(bin2hex(random_bytes(10)));
|
||||
$secretKey = $data['secret_key'] ?? base64_encode(random_bytes(30));
|
||||
$userName = $data['user_name'] ?? null;
|
||||
$credentials = $this->entityManager->getRepository(S3Credential::class)->findAll();
|
||||
|
||||
$credential = $this->s3Service->createCredential($accessKey, $secretKey, $userName);
|
||||
|
||||
return new JsonResponse([
|
||||
'id' => $credential->getId(),
|
||||
'access_key' => $credential->getAccessKey(),
|
||||
'secret_key' => $credential->getSecretKey(),
|
||||
'user_name' => $credential->getUserName(),
|
||||
'is_active' => $credential->isActive(),
|
||||
'created_at' => $credential->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
], 201);
|
||||
}
|
||||
|
||||
return new JsonResponse(['error' => 'Method not allowed'], 405);
|
||||
return new JsonResponse([
|
||||
'credentials' => array_map(function($cred) {
|
||||
return [
|
||||
'id' => $cred->getId(),
|
||||
'access_key' => $cred->getAccessKey(),
|
||||
'user_name' => $cred->getUserName(),
|
||||
'is_active' => $cred->isActive(),
|
||||
'created_at' => $cred->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'bucket_count' => $cred->getBuckets()->count()
|
||||
];
|
||||
}, $credentials)
|
||||
]);
|
||||
}
|
||||
|
||||
public function credentialDetail(int $id, Request $request): JsonResponse
|
||||
public function createCredential(Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
$resp = $this->checkAuth($request);
|
||||
if ($resp !== null) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
$accessKey = $data['access_key'] ?? 'AKIA' . strtoupper(bin2hex(random_bytes(10)));
|
||||
$secretKey = $data['secret_key'] ?? base64_encode(random_bytes(30));
|
||||
$userName = $data['user_name'] ?? null;
|
||||
|
||||
$credential = $this->s3Service->createCredential($accessKey, $secretKey, $userName);
|
||||
|
||||
return new JsonResponse([
|
||||
'id' => $credential->getId(),
|
||||
'access_key' => $credential->getAccessKey(),
|
||||
'secret_key' => $credential->getSecretKey(),
|
||||
'user_name' => $credential->getUserName(),
|
||||
'is_active' => $credential->isActive(),
|
||||
'created_at' => $credential->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function getCredential(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$credential = $this->entityManager->getRepository(S3Credential::class)->find($id);
|
||||
|
||||
|
||||
if (!$credential) {
|
||||
return new JsonResponse(['error' => 'Credential not found'], 404);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'GET') {
|
||||
return new JsonResponse([
|
||||
'id' => $credential->getId(),
|
||||
'access_key' => $credential->getAccessKey(),
|
||||
'secret_key' => $credential->getSecretKey(),
|
||||
'user_name' => $credential->getUserName(),
|
||||
'is_active' => $credential->isActive(),
|
||||
'created_at' => $credential->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'buckets' => array_map(function($bucket) {
|
||||
return [
|
||||
'name' => $bucket->getName(),
|
||||
'region' => $bucket->getRegion(),
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}, $credential->getBuckets()->toArray())
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'id' => $credential->getId(),
|
||||
'access_key' => $credential->getAccessKey(),
|
||||
'secret_key' => $credential->getSecretKey(),
|
||||
'user_name' => $credential->getUserName(),
|
||||
'is_active' => $credential->isActive(),
|
||||
'created_at' => $credential->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'buckets' => array_map(function($bucket) {
|
||||
return [
|
||||
'name' => $bucket->getName(),
|
||||
'region' => $bucket->getRegion(),
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}, $credential->getBuckets()->toArray())
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateCredential(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$credential = $this->entityManager->getRepository(S3Credential::class)->find($id);
|
||||
|
||||
if (!$credential) {
|
||||
return new JsonResponse(['error' => 'Credential not found'], 404);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'PUT') {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
if (isset($data['user_name'])) {
|
||||
$credential->setUserName($data['user_name']);
|
||||
}
|
||||
if (isset($data['is_active'])) {
|
||||
$credential->setIsActive($data['is_active']);
|
||||
}
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(['message' => 'Credential updated']);
|
||||
if (isset($data['user_name'])) {
|
||||
$credential->setUserName($data['user_name']);
|
||||
}
|
||||
if (isset($data['is_active'])) {
|
||||
$credential->setIsActive($data['is_active']);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'DELETE') {
|
||||
$this->entityManager->remove($credential);
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(['message' => 'Credential deleted']);
|
||||
return new JsonResponse(['message' => 'Credential updated']);
|
||||
}
|
||||
|
||||
public function deleteCredential(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$credential = $this->entityManager->getRepository(S3Credential::class)->find($id);
|
||||
|
||||
if (!$credential) {
|
||||
return new JsonResponse(['error' => 'Credential not found'], 404);
|
||||
}
|
||||
|
||||
return new JsonResponse(['error' => 'Method not allowed'], 405);
|
||||
$this->entityManager->remove($credential);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(['message' => 'Credential deleted']);
|
||||
}
|
||||
|
||||
// Buckets Management
|
||||
public function buckets(Request $request): JsonResponse
|
||||
public function listBuckets(Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
return $resp;
|
||||
}
|
||||
if ($request->getMethod() === 'GET') {
|
||||
$buckets = $this->entityManager->getRepository(S3Bucket::class)->findAll();
|
||||
|
||||
return new JsonResponse([
|
||||
'buckets' => array_map(function($bucket) {
|
||||
$objectCount = $this->entityManager->getRepository(S3Object::class)->count(['bucket' => $bucket]);
|
||||
$totalSize = $this->entityManager->createQueryBuilder()
|
||||
->select('SUM(o.size)')
|
||||
->from(S3Object::class, 'o')
|
||||
->where('o.bucket = :bucket')
|
||||
->setParameter('bucket', $bucket)
|
||||
->getQuery()
|
||||
->getSingleScalarResult() ?: 0;
|
||||
|
||||
return [
|
||||
'name' => $bucket->getName(),
|
||||
'region' => $bucket->getRegion(),
|
||||
'owner' => $bucket->getOwner()->getUserName() ?: $bucket->getOwner()->getAccessKey(),
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'object_count' => $objectCount,
|
||||
'total_size' => $totalSize,
|
||||
'total_size_human' => $this->formatBytes($totalSize)
|
||||
];
|
||||
}, $buckets)
|
||||
]);
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
$bucketName = $data['name'] ?? null;
|
||||
$ownerId = $data['owner_id'] ?? null;
|
||||
$region = $data['region'] ?? ($_ENV['APP_REGION'] ?? 'us-east-1');
|
||||
$buckets = $this->entityManager->getRepository(S3Bucket::class)->findAll();
|
||||
|
||||
if (!$bucketName || !$ownerId) {
|
||||
return new JsonResponse(['error' => 'Missing bucket name or owner'], 400);
|
||||
}
|
||||
return new JsonResponse([
|
||||
'buckets' => array_map(function($bucket) {
|
||||
$objectCount = $this->entityManager->getRepository(S3Object::class)->count(['bucket' => $bucket]);
|
||||
$totalSize = $this->entityManager->createQueryBuilder()
|
||||
->select('SUM(o.size)')
|
||||
->from(S3Object::class, 'o')
|
||||
->where('o.bucket = :bucket')
|
||||
->setParameter('bucket', $bucket)
|
||||
->getQuery()
|
||||
->getSingleScalarResult() ?: 0;
|
||||
|
||||
$owner = $this->entityManager->getRepository(S3Credential::class)->find($ownerId);
|
||||
if (!$owner) {
|
||||
return new JsonResponse(['error' => 'Owner not found'], 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$bucket = $this->s3Service->createBucket($bucketName, $owner, $region);
|
||||
|
||||
return new JsonResponse([
|
||||
return [
|
||||
'name' => $bucket->getName(),
|
||||
'region' => $bucket->getRegion(),
|
||||
'owner' => $bucket->getOwner()->getUserName() ?: $bucket->getOwner()->getAccessKey(),
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse(['error' => 'Method not allowed'], 405);
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'object_count' => $objectCount,
|
||||
'total_size' => $totalSize,
|
||||
'total_size_human' => $this->formatBytes($totalSize)
|
||||
];
|
||||
}, $buckets)
|
||||
]);
|
||||
}
|
||||
|
||||
public function bucketDetail(string $name, Request $request): JsonResponse
|
||||
public function createBucket(Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
return $resp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($name);
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'GET') {
|
||||
$objects = $this->s3Service->listObjects($bucket);
|
||||
$totalSize = array_sum(array_map(fn($obj) => $obj->getSize(), $objects));
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
$bucketName = $data['name'] ?? null;
|
||||
$ownerId = $data['owner_id'] ?? null;
|
||||
$region = $data['region'] ?? ($_ENV['APP_REGION'] ?? 'us-east-1');
|
||||
|
||||
if (!$bucketName || !$ownerId) {
|
||||
return new JsonResponse(['error' => 'Missing bucket name or owner'], 400);
|
||||
}
|
||||
|
||||
$owner = $this->entityManager->getRepository(S3Credential::class)->find($ownerId);
|
||||
if (!$owner) {
|
||||
return new JsonResponse(['error' => 'Owner not found'], 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$bucket = $this->s3Service->createBucket($bucketName, $owner, $region);
|
||||
|
||||
return new JsonResponse([
|
||||
'name' => $bucket->getName(),
|
||||
'region' => $bucket->getRegion(),
|
||||
'owner' => $bucket->getOwner()->getUserName() ?: $bucket->getOwner()->getAccessKey(),
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'object_count' => count($objects),
|
||||
'total_size' => $totalSize,
|
||||
'total_size_human' => $this->formatBytes($totalSize),
|
||||
'objects' => array_map(function($obj) {
|
||||
return [
|
||||
'key' => $obj->getObjectKey(),
|
||||
'size' => $obj->getSize(),
|
||||
'size_human' => $this->formatBytes($obj->getSize()),
|
||||
'content_type' => $obj->getContentType(),
|
||||
'etag' => $obj->getEtag(),
|
||||
'is_multipart' => $obj->isMultipart(),
|
||||
'created_at' => $obj->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}, $objects)
|
||||
]);
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'DELETE') {
|
||||
try {
|
||||
$this->s3Service->deleteBucket($bucket);
|
||||
return new JsonResponse(['message' => 'Bucket deleted']);
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse(['error' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
// Objects Management
|
||||
public function objects(string $bucketName, Request $request): JsonResponse
|
||||
public function getBucket(string $name, Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
return $resp;
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
|
||||
$bucket = $this->s3Service->findBucketByName($name);
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'GET') {
|
||||
$prefix = $request->query->get('prefix', '');
|
||||
$objects = $this->s3Service->listObjects($bucket, $prefix);
|
||||
|
||||
return new JsonResponse([
|
||||
'objects' => array_map(function($obj) {
|
||||
return [
|
||||
'key' => $obj->getObjectKey(),
|
||||
'size' => $obj->getSize(),
|
||||
'size_human' => $this->formatBytes($obj->getSize()),
|
||||
'content_type' => $obj->getContentType(),
|
||||
'etag' => $obj->getEtag(),
|
||||
'is_multipart' => $obj->isMultipart(),
|
||||
'created_at' => $obj->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}, $objects)
|
||||
]);
|
||||
}
|
||||
$objects = $this->s3Service->listObjects($bucket);
|
||||
$totalSize = array_sum(array_map(fn($obj) => $obj->getSize(), $objects));
|
||||
|
||||
if ($request->getMethod() === 'DELETE') {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$keys = $data['keys'] ?? [];
|
||||
|
||||
$deleted = [];
|
||||
foreach ($keys as $key) {
|
||||
$object = $this->s3Service->findObject($bucket, $key);
|
||||
if ($object) {
|
||||
$this->s3Service->deleteObject($object);
|
||||
$deleted[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse(['deleted' => $deleted]);
|
||||
}
|
||||
|
||||
return new JsonResponse(['error' => 'Method not allowed'], 405);
|
||||
return new JsonResponse([
|
||||
'name' => $bucket->getName(),
|
||||
'region' => $bucket->getRegion(),
|
||||
'owner' => $bucket->getOwner()->getUserName() ?: $bucket->getOwner()->getAccessKey(),
|
||||
'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'object_count' => count($objects),
|
||||
'total_size' => $totalSize,
|
||||
'total_size_human' => $this->formatBytes($totalSize),
|
||||
'objects' => array_map(function($obj) {
|
||||
return [
|
||||
'key' => $obj->getObjectKey(),
|
||||
'size' => $obj->getSize(),
|
||||
'size_human' => $this->formatBytes($obj->getSize()),
|
||||
'content_type' => $obj->getContentType(),
|
||||
'etag' => $obj->getEtag(),
|
||||
'is_multipart' => $obj->isMultipart(),
|
||||
'created_at' => $obj->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}, $objects)
|
||||
]);
|
||||
}
|
||||
|
||||
public function objectDetail(string $bucketName, string $objectKey, Request $request): JsonResponse
|
||||
public function deleteBucket(string $name, Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
return $resp;
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($name);
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->s3Service->deleteBucket($bucket);
|
||||
return new JsonResponse(['message' => 'Bucket deleted']);
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Objects Management
|
||||
public function listObjects(string $bucketName, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
$prefix = $request->query->get('prefix', '');
|
||||
$objects = $this->s3Service->listObjects($bucket, $prefix);
|
||||
|
||||
return new JsonResponse([
|
||||
'objects' => array_map(function($obj) {
|
||||
return [
|
||||
'key' => $obj->getObjectKey(),
|
||||
'size' => $obj->getSize(),
|
||||
'size_human' => $this->formatBytes($obj->getSize()),
|
||||
'content_type' => $obj->getContentType(),
|
||||
'etag' => $obj->getEtag(),
|
||||
'is_multipart' => $obj->isMultipart(),
|
||||
'created_at' => $obj->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}, $objects)
|
||||
]);
|
||||
}
|
||||
|
||||
public function createObject(string $bucketName, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
$objectKey = $request->headers->get('X-Object-Key')
|
||||
?? $request->query->get('key')
|
||||
?? $request->request->get('object_key');
|
||||
|
||||
if (empty($objectKey)) {
|
||||
return new JsonResponse(['error' => 'Missing object key'], 400);
|
||||
}
|
||||
|
||||
$contentType = $request->headers->get('Content-Type', 'application/octet-stream');
|
||||
$file = $request->files->get('file');
|
||||
if ($file instanceof \Symfony\Component\HttpFoundation\File\UploadedFile) {
|
||||
$contentType = $file->getMimeType() ?: $contentType;
|
||||
$content = file_get_contents($file->getPathname());
|
||||
} else {
|
||||
$content = $request->getContent();
|
||||
}
|
||||
|
||||
$object = $this->s3Service->putObject($bucket, $objectKey, $content, $contentType);
|
||||
|
||||
return new JsonResponse([
|
||||
'key' => $object->getObjectKey(),
|
||||
'etag' => $object->getEtag(),
|
||||
'size' => $object->getSize(),
|
||||
'content_type' => $object->getContentType()
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function deleteObjects(string $bucketName, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$keys = $data['keys'] ?? [];
|
||||
|
||||
$deleted = [];
|
||||
foreach ($keys as $key) {
|
||||
$object = $this->s3Service->findObject($bucket, $key);
|
||||
if ($object) {
|
||||
$this->s3Service->deleteObject($object);
|
||||
$deleted[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse(['deleted' => $deleted]);
|
||||
}
|
||||
|
||||
public function getObject(string $bucketName, string $objectKey, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
$object = $this->s3Service->findObject($bucket, $objectKey);
|
||||
|
||||
|
||||
if (!$object) {
|
||||
return new JsonResponse(['error' => 'Object not found'], 404);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'GET') {
|
||||
return new JsonResponse([
|
||||
'key' => $object->getObjectKey(),
|
||||
'size' => $object->getSize(),
|
||||
'size_human' => $this->formatBytes($object->getSize()),
|
||||
'content_type' => $object->getContentType(),
|
||||
'etag' => $object->getEtag(),
|
||||
'is_multipart' => $object->isMultipart(),
|
||||
'part_count' => $object->getPartCount(),
|
||||
'metadata' => $object->getMetadata(),
|
||||
'storage_path' => $object->getStoragePath(),
|
||||
'created_at' => $object->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $object->getUpdatedAt()->format('Y-m-d H:i:s')
|
||||
]);
|
||||
return new JsonResponse([
|
||||
'key' => $object->getObjectKey(),
|
||||
'size' => $object->getSize(),
|
||||
'size_human' => $this->formatBytes($object->getSize()),
|
||||
'content_type' => $object->getContentType(),
|
||||
'etag' => $object->getEtag(),
|
||||
'is_multipart' => $object->isMultipart(),
|
||||
'part_count' => $object->getPartCount(),
|
||||
'metadata' => $object->getMetadata(),
|
||||
'storage_path' => $object->getStoragePath(),
|
||||
'created_at' => $object->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $object->getUpdatedAt()->format('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteObject(string $bucketName, string $objectKey, Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'DELETE') {
|
||||
$this->s3Service->deleteObject($object);
|
||||
return new JsonResponse(['message' => 'Object deleted']);
|
||||
$object = $this->s3Service->findObject($bucket, $objectKey);
|
||||
|
||||
if (!$object) {
|
||||
return new JsonResponse(['error' => 'Object not found'], 404);
|
||||
}
|
||||
|
||||
return new JsonResponse(['error' => 'Method not allowed'], 405);
|
||||
$this->s3Service->deleteObject($object);
|
||||
return new JsonResponse(['message' => 'Object deleted']);
|
||||
}
|
||||
|
||||
public function createPresignedUpload(string $bucketName, string $objectKey, Request $request): JsonResponse
|
||||
{
|
||||
$resp = $this->checkAuth($request);
|
||||
if (!empty($resp)) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
if (!$bucket) {
|
||||
return new JsonResponse(['error' => 'Bucket not found'], 404);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$accessKey = $data['access_key'] ?? null;
|
||||
$expiresIn = $data['expires_in'] ?? 3600;
|
||||
$contentType = $data['content_type'] ?? 'application/octet-stream';
|
||||
|
||||
if (!$accessKey) {
|
||||
return new JsonResponse(['error' => 'Missing access key'], 400);
|
||||
}
|
||||
|
||||
$credential = $this->s3Service->findCredentialByAccessKey($accessKey);
|
||||
if (!$credential) {
|
||||
return new JsonResponse(['error' => 'Invalid access key'], 404);
|
||||
}
|
||||
|
||||
// Create placeholder object
|
||||
$this->s3Service->putObject($bucket, $objectKey, '', $contentType);
|
||||
|
||||
$url = $this->s3Service->generatePresignedUrl($bucketName, $objectKey, $credential, 'PUT', $expiresIn);
|
||||
|
||||
return new JsonResponse(['url' => $url], 201);
|
||||
}
|
||||
|
||||
// Multipart Uploads
|
||||
public function multipartUploads(string $bucketName, Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
return $resp;
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$bucket = $this->s3Service->findBucketByName($bucketName);
|
||||
|
||||
@@ -366,67 +497,72 @@ class ConsoleApiController extends AbstractController
|
||||
}
|
||||
|
||||
// Presigned URLs
|
||||
public function presignedUrls(Request $request): JsonResponse
|
||||
public function listPresignedUrls(Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
return $resp;
|
||||
}
|
||||
if ($request->getMethod() === 'GET') {
|
||||
$urls = $this->entityManager->getRepository(\App\Entity\S3PresignedUrl::class)
|
||||
->createQueryBuilder('p')
|
||||
->where('p.expiresAt > :now')
|
||||
->setParameter('now', new \DateTime())
|
||||
->orderBy('p.createdAt', 'DESC')
|
||||
->setMaxResults(100)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return new JsonResponse([
|
||||
'urls' => array_map(function($url) {
|
||||
return [
|
||||
'bucket_name' => $url->getBucketName(),
|
||||
'object_key' => $url->getObjectKey(),
|
||||
'method' => $url->getMethod(),
|
||||
'access_key' => $url->getAccessKey(),
|
||||
'expires_at' => $url->getExpiresAt()->format('Y-m-d H:i:s'),
|
||||
'created_at' => $url->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'url' => $url->getUrl()
|
||||
];
|
||||
}, $urls)
|
||||
]);
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
$bucketName = $data['bucket_name'] ?? null;
|
||||
$objectKey = $data['object_key'] ?? null;
|
||||
$method = $data['method'] ?? 'GET';
|
||||
$expiresIn = $data['expires_in'] ?? 3600;
|
||||
$accessKey = $data['access_key'] ?? null;
|
||||
$urls = $this->entityManager->getRepository(\App\Entity\S3PresignedUrl::class)
|
||||
->createQueryBuilder('p')
|
||||
->where('p.expiresAt > :now')
|
||||
->setParameter('now', new \DateTime())
|
||||
->orderBy('p.createdAt', 'DESC')
|
||||
->setMaxResults(100)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
if (!$bucketName || !$objectKey || !$accessKey) {
|
||||
return new JsonResponse(['error' => 'Missing required fields'], 400);
|
||||
}
|
||||
return new JsonResponse([
|
||||
'urls' => array_map(function($url) {
|
||||
return [
|
||||
'bucket_name' => $url->getBucketName(),
|
||||
'object_key' => $url->getObjectKey(),
|
||||
'method' => $url->getMethod(),
|
||||
'access_key' => $url->getAccessKey(),
|
||||
'expires_at' => $url->getExpiresAt()->format('Y-m-d H:i:s'),
|
||||
'created_at' => $url->getCreatedAt()->format('Y-m-d H:i:s'),
|
||||
'url' => $url->getUrl()
|
||||
];
|
||||
}, $urls)
|
||||
]);
|
||||
}
|
||||
|
||||
$credential = $this->s3Service->findCredentialByAccessKey($accessKey);
|
||||
if (!$credential) {
|
||||
return new JsonResponse(['error' => 'Invalid access key'], 404);
|
||||
}
|
||||
|
||||
$url = $this->s3Service->generatePresignedUrl($bucketName, $objectKey, $credential, $method, $expiresIn);
|
||||
|
||||
return new JsonResponse(['url' => $url], 201);
|
||||
public function createPresignedUrl(Request $request): JsonResponse
|
||||
{
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
|
||||
return new JsonResponse(['error' => 'Method not allowed'], 405);
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
$bucketName = $data['bucket_name'] ?? null;
|
||||
$objectKey = $data['object_key'] ?? null;
|
||||
$method = $data['method'] ?? 'GET';
|
||||
$expiresIn = $data['expires_in'] ?? 3600;
|
||||
$accessKey = $data['access_key'] ?? null;
|
||||
|
||||
if (!$bucketName || !$objectKey || !$accessKey) {
|
||||
return new JsonResponse(['error' => 'Missing required fields'], 400);
|
||||
}
|
||||
|
||||
$credential = $this->s3Service->findCredentialByAccessKey($accessKey);
|
||||
if (!$credential) {
|
||||
return new JsonResponse(['error' => 'Invalid access key'], 404);
|
||||
}
|
||||
|
||||
$url = $this->s3Service->generatePresignedUrl($bucketName, $objectKey, $credential, $method, $expiresIn);
|
||||
|
||||
return new JsonResponse(['url' => $url], 201);
|
||||
}
|
||||
|
||||
// Statistics
|
||||
public function stats(Request $request): JsonResponse
|
||||
{
|
||||
if ($resp = $this->checkAuth($request)) {
|
||||
return $resp;
|
||||
$authResp = $this->checkAuth($request);
|
||||
if ($authResp !== null) {
|
||||
return $authResp;
|
||||
}
|
||||
$credentialCount = $this->entityManager->getRepository(S3Credential::class)->count([]);
|
||||
$bucketCount = $this->entityManager->getRepository(S3Bucket::class)->count([]);
|
||||
@@ -467,4 +603,4 @@ class ConsoleApiController extends AbstractController
|
||||
|
||||
return round($bytes / pow(1024, $i), 2) . ' ' . $units[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,28 +18,32 @@ class ConsoleController extends AbstractController
|
||||
return $this->render('console/index.html.twig');
|
||||
}
|
||||
|
||||
public function login(Request $request): Response
|
||||
public function loginForm(Request $request): Response
|
||||
{
|
||||
if ($request->getSession()->get('console_logged_in')) {
|
||||
return new RedirectResponse('/console');
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($request->isMethod('POST')) {
|
||||
$user = $request->request->get('username');
|
||||
$pass = $request->request->get('password');
|
||||
$envUser = $_ENV['CONSOLE_USER'] ?? 'admin';
|
||||
$envPass = $_ENV['CONSOLE_PASS'] ?? 'password';
|
||||
return $this->render('console/login.html.twig', ['error' => null]);
|
||||
}
|
||||
|
||||
if ($user === $envUser && $pass === $envPass) {
|
||||
$request->getSession()->set('console_logged_in', true);
|
||||
return new RedirectResponse('/console');
|
||||
}
|
||||
|
||||
$error = 'Invalid credentials';
|
||||
public function loginSubmit(Request $request): Response
|
||||
{
|
||||
if ($request->getSession()->get('console_logged_in')) {
|
||||
return new RedirectResponse('/console');
|
||||
}
|
||||
|
||||
return $this->render('console/login.html.twig', ['error' => $error]);
|
||||
$user = $request->request->get('username');
|
||||
$pass = $request->request->get('password');
|
||||
$envUser = $_ENV['CONSOLE_USER'] ?? 'admin';
|
||||
$envPass = $_ENV['CONSOLE_PASS'] ?? 'password';
|
||||
|
||||
if ($user === $envUser && $pass === $envPass) {
|
||||
$request->getSession()->set('console_logged_in', true);
|
||||
return new RedirectResponse('/console');
|
||||
}
|
||||
|
||||
return $this->render('console/login.html.twig', ['error' => 'Invalid credentials']);
|
||||
}
|
||||
|
||||
public function logout(Request $request): Response
|
||||
|
||||
@@ -529,9 +529,12 @@
|
||||
<div x-show="selectedBucket" class="card">
|
||||
<div class="card-header">
|
||||
Objects
|
||||
<div>
|
||||
<input x-model="objectSearch" @input="filterObjects()"
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<input x-model="objectSearch" @input="filterObjects()"
|
||||
placeholder="Search objects..." class="search-input">
|
||||
<button @click="showUploadModal = true" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-upload"></i> Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -630,6 +633,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Object Modal -->
|
||||
<div x-show="showUploadModal" x-cloak class="modal" @click.self="showUploadModal = false">
|
||||
<div class="modal-content">
|
||||
<h3 style="margin-bottom: 1rem;">Upload Object</h3>
|
||||
<form @submit.prevent="uploadObject()">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Object Key</label>
|
||||
<input x-model="uploadKey" type="text" class="form-input" placeholder="path/to/file.txt" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Select File</label>
|
||||
<input type="file" @change="uploadFile = $event.target.files[0]" class="form-input" required>
|
||||
</div>
|
||||
<div style="display: flex; gap: 1rem; justify-content: flex-end;">
|
||||
<button type="button" @click="showUploadModal = false" class="btn btn-secondary">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Credential Modal -->
|
||||
<div x-show="showCreateCredentialModal" x-cloak class="modal" @click.self="showCreateCredentialModal = false">
|
||||
<div class="modal-content">
|
||||
@@ -775,6 +799,7 @@
|
||||
showCreateCredentialModal: false,
|
||||
showCreateBucketModal: false,
|
||||
showCreatePresignedModal: false,
|
||||
showUploadModal: false,
|
||||
|
||||
// Forms
|
||||
newCredential: {
|
||||
@@ -794,6 +819,8 @@
|
||||
expires_in: 3600,
|
||||
access_key: ''
|
||||
},
|
||||
uploadFile: null,
|
||||
uploadKey: '',
|
||||
|
||||
init() {
|
||||
this.loadStats();
|
||||
@@ -991,10 +1018,10 @@
|
||||
|
||||
async deleteObject(bucketName, objectKey) {
|
||||
if (!confirm('Are you sure you want to delete this object?')) return;
|
||||
|
||||
|
||||
try {
|
||||
await this.apiCall(`/buckets/${bucketName}/objects/${encodeURIComponent(objectKey)}`, {
|
||||
method: 'DELETE'
|
||||
await this.apiCall(`/buckets/${bucketName}/objects/${encodeURIComponent(objectKey)}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
this.showMessage('Object deleted successfully!');
|
||||
this.viewBucket(bucketName);
|
||||
@@ -1003,6 +1030,29 @@
|
||||
}
|
||||
},
|
||||
|
||||
async uploadObject() {
|
||||
if (!this.uploadFile || !this.uploadKey) return;
|
||||
|
||||
try {
|
||||
await fetch(`/api/buckets/${this.selectedBucket.name}/objects?key=${encodeURIComponent(this.uploadKey)}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': this.uploadFile.type || 'application/octet-stream',
|
||||
'X-Object-Key': this.uploadKey
|
||||
},
|
||||
body: this.uploadFile
|
||||
});
|
||||
this.showMessage('Object uploaded successfully!');
|
||||
this.showUploadModal = false;
|
||||
this.uploadFile = null;
|
||||
this.uploadKey = '';
|
||||
this.viewBucket(this.selectedBucket.name);
|
||||
} catch (error) {
|
||||
console.error('Failed to upload object:', error);
|
||||
this.showMessage('Upload failed', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
async viewCredential(credential) {
|
||||
// Could open a modal with full credential details
|
||||
alert(`Access Key: ${credential.access_key}\nUser: ${credential.user_name || 'N/A'}\nStatus: ${credential.is_active ? 'Active' : 'Inactive'}`);
|
||||
|
||||
@@ -589,6 +589,32 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreatePresignedUrlResponse'
|
||||
|
||||
/api/buckets/{bucketName}/objects/{objectKey}/presigned-upload:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/BucketName'
|
||||
- $ref: '#/components/parameters/ObjectKey'
|
||||
post:
|
||||
operationId: createPresignedUpload
|
||||
tags:
|
||||
- Management - Presigned URLs
|
||||
summary: Create presigned upload URL
|
||||
description: Creates an empty object and returns a presigned URL for uploading content
|
||||
security:
|
||||
- ApiKey: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreatePresignedUploadRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Presigned URL created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreatePresignedUrlResponse'
|
||||
components:
|
||||
schemas:
|
||||
CreatePresignedUrlResponse:
|
||||
@@ -597,6 +623,24 @@ components:
|
||||
url:
|
||||
type: string
|
||||
example: /my-bucket/file.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20230115%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230115T103000Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=abc123&hash=def456
|
||||
CreatePresignedUploadRequest:
|
||||
type: object
|
||||
required:
|
||||
- access_key
|
||||
properties:
|
||||
access_key:
|
||||
type: string
|
||||
example: "AKIAIOSFODNN7EXAMPLE"
|
||||
expires_in:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 604800
|
||||
default: 3600
|
||||
example: 3600
|
||||
content_type:
|
||||
type: string
|
||||
default: "application/octet-stream"
|
||||
example: "image/jpeg"
|
||||
ObjectSummary:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -573,6 +573,32 @@
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schemas.yaml#/CreatePresignedUrlRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Presigned URL created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schemas.yaml#/CreatePresignedUrlResponse'
|
||||
|
||||
/api/buckets/{bucketName}/objects/{objectKey}/presigned-upload:
|
||||
parameters:
|
||||
- $ref: './parameters.yaml#/BucketName'
|
||||
- $ref: './parameters.yaml#/ObjectKey'
|
||||
post:
|
||||
operationId: createPresignedUpload
|
||||
tags:
|
||||
- Management - Presigned URLs
|
||||
summary: Create presigned upload URL
|
||||
description: Creates an empty object and returns a presigned URL for uploading content
|
||||
security:
|
||||
- ApiKey: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './schemas.yaml#/CreatePresignedUploadRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Presigned URL created
|
||||
|
||||
@@ -628,6 +628,25 @@ CreatePresignedUrlRequest:
|
||||
type: string
|
||||
example: "AKIAIOSFODNN7EXAMPLE"
|
||||
|
||||
CreatePresignedUploadRequest:
|
||||
type: object
|
||||
required:
|
||||
- access_key
|
||||
properties:
|
||||
access_key:
|
||||
type: string
|
||||
example: "AKIAIOSFODNN7EXAMPLE"
|
||||
expires_in:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 604800
|
||||
default: 3600
|
||||
example: 3600
|
||||
content_type:
|
||||
type: string
|
||||
default: "application/octet-stream"
|
||||
example: "image/jpeg"
|
||||
|
||||
CreatePresignedUrlResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user