Merge pull request #8 from biondizzle/codex/build-file-uploader-for-management-console
Add admin object uploader
This commit is contained in:
@@ -277,6 +277,34 @@ class ConsoleApiController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$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);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'DELETE') {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$keys = $data['keys'] ?? [];
|
||||
|
||||
@@ -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'}`);
|
||||
|
||||
Reference in New Issue
Block a user