1
0
Fork 0
mirror of synced 2024-06-29 19:50:26 +12:00

Merge branch 'feat-storage-buckets' into feat-large-file

This commit is contained in:
Damodar Lohani 2021-12-09 13:52:53 +05:45
commit eb2968256a
19 changed files with 1355 additions and 390 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -58,17 +58,19 @@ App::post('/v1/storage/buckets')
->param('antiVirus', true, new Boolean(), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('audits')
->inject('usage')
->action(function ($bucketId, $name, $permission, $read, $write, $maximumFileSize, $allowedFileExtensions, $enabled, $adapter, $encryption, $antiVirus, $response, $dbForInternal, $audits, $usage) {
->action(function ($bucketId, $name, $permission, $read, $write, $maximumFileSize, $allowedFileExtensions, $enabled, $adapter, $encryption, $antiVirus, $response, $dbForInternal, $dbForExternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$bucketId = $bucketId === 'unique()' ? $dbForInternal->getId() : $bucketId;
try {
$dbForInternal->createCollection('bucket_' . $bucketId, [
$dbForExternal->createCollection('bucket_' . $bucketId, [
new Document([
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
@ -527,14 +529,16 @@ App::post('/v1/storage/buckets/:bucketId/files')
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('user')
->inject('audits')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $file, $read, $write, $request, $response, $dbForInternal, $user, $audits, $usage, $mode) {
->action(function ($bucketId, $fileId, $file, $read, $write, $request, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage, $mode) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@ -581,7 +585,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
$size = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
$contentRange = $request->getHeader('content-range');
$fileId = $fileId === 'unique()' ? $dbForInternal->getId() : $fileId;
$fileId = $fileId === 'unique()' ? $$dbForExternal->getId() : $fileId;
$chunk = 1;
$chunks = 1;
@ -624,7 +628,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
$path = $device->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$path = str_ireplace($device->getRoot(), $device->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
if (!$file->isEmpty()) {
$chunks = $file->getAttribute('chunksTotal', 1);
@ -712,11 +716,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
'search' => implode(' ', [$fileId, $fileName,]),
]);
if($permissionBucket) {
$file = Authorization::skip(function() use ($dbForInternal, $bucketId, $doc) {
return $dbForInternal->createDocument('bucket_' . $bucketId, $doc);
$file = Authorization::skip(function() use ($dbForExternal, $bucketId, $doc) {
return $dbForExternal->createDocument('bucket_' . $bucketId, $doc);
});
} else {
$file = $dbForInternal->createDocument('bucket_' . $bucketId, $doc);
$file = $dbForExternal->createDocument('bucket_' . $bucketId, $doc);
}
} else {
$file = $file
@ -733,11 +737,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
->setAttribute('chunksUploaded', $chunksUploaded);
if($permissionBucket) {
$file = Authorization::skip(function() use ($dbForInternal, $bucketId, $fileId, $file) {
return $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
$file = Authorization::skip(function() use ($dbForExternal, $bucketId, $fileId, $file) {
return $dbForExternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
});
} else {
$file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
$file = $dbForExternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
}
}
@ -770,22 +774,22 @@ App::post('/v1/storage/buckets/:bucketId/files')
'search' => implode(' ', [$fileId, $fileName,]),
]);
if($permissionBucket) {
$file = Authorization::skip(function() use ($dbForInternal, $bucketId, $doc) {
return $dbForInternal->createDocument('bucket_' . $bucketId, $doc);
$file = Authorization::skip(function() use ($dbForExternal, $bucketId, $doc) {
return $dbForExternal->createDocument('bucket_' . $bucketId, $doc);
});
} else {
$file = $dbForInternal->createDocument('bucket_' . $bucketId, $doc);
$file = $dbForExternal->createDocument('bucket_' . $bucketId, $doc);
}
} else {
$file = $file
->setAttribute('chunksUploaded', $chunksUploaded);
if($permissionBucket) {
$file = Authorization::skip(function() use ($dbForInternal, $bucketId, $fileId, $file) {
return $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
$file = Authorization::skip(function() use ($dbForExternal, $bucketId, $fileId, $file) {
return $dbForExternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
});
} else {
$file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
$file = $dbForExternal->updateDocument('bucket_' . $bucketId, $fileId, $file);
}
}
}
@ -834,11 +838,13 @@ App::get('/v1/storage/buckets/:bucketId/files')
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal, $usage, $mode) {
->action(function ($bucketId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Stats\Stats $usage */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
@ -864,11 +870,11 @@ App::get('/v1/storage/buckets/:bucketId/files')
if (!empty($cursor)) {
if($bucket->getAttribute('permission') ==='bucket') {
$cursorFile = Authorization::skip(function() use ($dbForInternal, $bucket, $cursor) {
return $dbForInternal->getDocument('bucket_' . $bucket->getId(), $cursor);
$cursorFile = Authorization::skip(function() use ($dbForExternal, $bucket, $cursor) {
return $dbForExternal->getDocument('bucket_' . $bucket->getId(), $cursor);
});
} else {
$cursorFile = $dbForInternal->getDocument('bucket_' . $bucket->getId(), $cursor);
$cursorFile = $dbForExternal->getDocument('bucket_' . $bucket->getId(), $cursor);
}
if ($cursorFile->isEmpty()) {
@ -883,11 +889,11 @@ App::get('/v1/storage/buckets/:bucketId/files')
}
if($bucket->getAttribute('permission') === 'bucket') {
$files = Authorization::skip(function() use ($dbForInternal, $bucketId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderType) {
return $dbForInternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
$files = Authorization::skip(function() use ($dbForExternal, $bucketId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderType) {
return $dbForExternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
});
} else {
$files = $dbForInternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
$files = $dbForExternal->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
}
$usage
@ -897,7 +903,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
$response->dynamic(new Document([
'files' => $files,
'sum' => $dbForInternal->count('bucket_' . $bucketId, $queries, APP_LIMIT_COUNT),
'sum' => $dbForExternal->count('bucket_' . $bucketId, $queries, APP_LIMIT_COUNT),
]), Response::MODEL_FILE_LIST);
});
@ -917,11 +923,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $response, $dbForInternal, $usage, $mode) {
->action(function ($bucketId, $fileId, $response, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Stats\Stats $usage */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
@ -940,11 +948,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
}
if($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function() use ($dbForInternal, $bucketId, $fileId) {
return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = Authorization::skip(function() use ($dbForExternal, $bucketId, $fileId) {
return $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
});
} else {
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
@ -986,13 +994,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->inject('response')
->inject('project')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal, $usage, $mode) {
->action(function ($bucketId, $fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Stats\Stats $usage */
$storage = 'files';
@ -1032,11 +1042,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
if($bucket->getAttribute('permission')==='bucket') {
// skip authorization
$file = Authorization::skip(function () use ($dbForInternal, $bucketId, $fileId) {
return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = Authorization::skip(function () use ($dbForExternal, $bucketId, $fileId) {
return $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
});
} else {
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
@ -1158,12 +1168,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $request, $response, $dbForInternal, $usage, $mode) {
->action(function ($bucketId, $fileId, $request, $response, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Stats\Stats $usage */
$bucket = $dbForInternal->getDocument('buckets', $bucketId);
@ -1182,11 +1194,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
if($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) {
return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = Authorization::skip(function() use ($dbForExternal, $fileId, $bucketId) {
return $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
});
} else {
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
@ -1295,9 +1307,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->inject('response')
->inject('request')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $response, $request, $dbForInternal, $usage, $mode) {
->action(function ($bucketId, $fileId, $response, $request, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Swoole\Request $request */
/** @var Utopia\Database\Database $dbForInternal */
@ -1319,11 +1332,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
}
if($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) {
return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = Authorization::skip(function() use ($dbForExternal, $fileId, $bucketId) {
return $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
});
} else {
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
}
$mimes = Config::getParam('storage-mimes');
@ -1447,10 +1460,11 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('audits')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $read, $write, $response, $dbForInternal, $audits, $usage, $mode) {
->action(function ($bucketId, $fileId, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
@ -1472,11 +1486,11 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
}
if($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) {
return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = Authorization::skip(function() use ($dbForExternal, $fileId, $bucketId) {
return $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
});
} else {
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
@ -1484,14 +1498,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
}
if($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId, $file, $read, $write) {
return $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file
$file = Authorization::skip(function() use ($dbForExternal, $fileId, $bucketId, $file, $read, $write) {
return $dbForExternal->updateDocument('bucket_' . $bucketId, $fileId, $file
->setAttribute('$read', $read)
->setAttribute('$write', $write)
);
});
} else {
$file = $dbForInternal->updateDocument('bucket_' . $bucketId, $fileId, $file
$file = $dbForExternal->updateDocument('bucket_' . $bucketId, $fileId, $file
->setAttribute('$read', $read)
->setAttribute('$write', $write)
);
@ -1526,13 +1540,15 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->param('fileId', '', new UID(), 'File unique ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('events')
->inject('audits')
->inject('usage')
->inject('mode')
->action(function ($bucketId, $fileId, $response, $dbForInternal, $events, $audits, $usage, $mode) {
->action(function ($bucketId, $fileId, $response, $dbForInternal, $dbForExternal, $events, $audits, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@ -1553,11 +1569,11 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
}
if($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) {
return $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = Authorization::skip(function() use ($dbForExternal, $fileId, $bucketId) {
return $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
});
} else {
$file = $dbForInternal->getDocument('bucket_' . $bucketId, $fileId);
$file = $dbForExternal->getDocument('bucket_' . $bucketId, $fileId);
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
@ -1568,11 +1584,11 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
if ($device->delete($file->getAttribute('path', ''))) {
if($bucket->getAttribute('permission') === 'bucket') {
$deleted = Authorization::skip(function() use ($dbForInternal, $fileId, $bucketId) {
return $dbForInternal->deleteDocument('bucket_' . $bucketId, $fileId);
$deleted = Authorization::skip(function() use ($dbForExternal, $fileId, $bucketId) {
return $dbForExternal->deleteDocument('bucket_' . $bucketId, $fileId);
});
} else {
$deleted = $dbForInternal->deleteDocument('bucket_' . $bucketId, $fileId);
$deleted = $dbForExternal->deleteDocument('bucket_' . $bucketId, $fileId);
}
if (!$deleted) {
throw new Exception('Failed to remove file from DB', 500);
@ -1638,8 +1654,18 @@ App::get('/v1/storage/usage')
];
$metrics = [
'storage.total',
'storage.files.count'
"storage.tags.total",
"storage.files.total",
"storage.files.count",
"storage.buckets.count",
"storage.buckets.create",
"storage.buckets.read",
"storage.buckets.update",
"storage.buckets.delete",
"storage.files.create",
"storage.files.read",
"storage.files.update",
"storage.files.delete",
];
$stats = [];
@ -1653,7 +1679,7 @@ App::get('/v1/storage/usage')
new Query('period', Query::TYPE_EQUAL, [$period]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $limit, 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
@ -1682,8 +1708,18 @@ App::get('/v1/storage/usage')
$usage = new Document([
'range' => $range,
'storage' => $stats['storage.total'],
'files' => $stats['storage.files.count']
'filesStorage' => $stats['storage.files.total'],
'tagsStorage' => $stats['storage.tags.total'],
'filesCount' => $stats['storage.files.count'],
'bucketsCount' => $stats['storage.buckets.count'],
'bucketsCreate' => $stats['storage.buckets.create'],
'bucketsRead' => $stats['storage.buckets.read'],
'bucketsUpdate' => $stats['storage.buckets.update'],
'bucketsDelete' => $stats['storage.buckets.delete'],
'filesCreate' => $stats['storage.files.create'],
'filesRead' => $stats['storage.files.read'],
'filesUpdate' => $stats['storage.files.update'],
'filesDelete' => $stats['storage.files.delete'],
]);
}
@ -1737,6 +1773,7 @@ App::get('/v1/storage/:bucketId/usage')
$metrics = [
"storage.buckets.$bucketId.files.count",
"storage.buckets.$bucketId.files.total",
"storage.buckets.$bucketId.files.create",
"storage.buckets.$bucketId.files.read",
"storage.buckets.$bucketId.files.update",
@ -1749,12 +1786,11 @@ App::get('/v1/storage/:bucketId/usage')
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForInternal->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $limit, 0, ['time'], [Database::ORDER_DESC]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
@ -1783,6 +1819,7 @@ App::get('/v1/storage/:bucketId/usage')
$usage = new Document([
'range' => $range,
'filesStorage' => $stats["storage.buckets.$bucketId.files.total"],
'filesCount' => $stats["storage.buckets.$bucketId.files.count"],
'filesCreate' => $stats["storage.buckets.$bucketId.files.create"],
'filesRead' => $stats["storage.buckets.$bucketId.files.read"],

View file

@ -315,6 +315,36 @@ App::get('/console/storage')
->setParam('body', $page);
});
App::get('/console/storage/bucket')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('id', '', new UID(), 'Bucket unique ID.')
->inject('response')
->inject('layout')
->action(function ($id, $response, $layout) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/storage/bucket.phtml');
$page
->setParam('home', App::getEnv('_APP_HOME', 0))
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
;
$layout
->setParam('title', APP_NAME.' - Storage Bucket')
->setParam('body', $page)
;
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Expires', 0)
->addHeader('Pragma', 'no-cache')
;
});
App::get('/console/users')
->groups(['web', 'console'])
->label('permission', 'public')

View file

@ -66,7 +66,7 @@ const APP_LIMIT_ANTIVIRUS = 20971520; //20MB
const APP_LIMIT_ENCRYPTION = 20971520; //20MB
const APP_LIMIT_COMPRESSION = 20971520; //20MB
const APP_CACHE_BUSTER = 181;
const APP_VERSION_STABLE = '0.12.0';
const APP_VERSION_STABLE = '0.13.0';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';

View file

@ -30,7 +30,7 @@ $cli
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
if(!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x'])) {
if(!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x'])) {
throw new Exception('Unknown version given');
}

View file

@ -43,6 +43,10 @@ use Utopia\Database\Validator\Authorization;
* storage.buckets.read
* storage.buckets.update
* storage.buckets.delete
* storage.files.create
* storage.files.read
* storage.files.update
* storage.files.delete
* storage.buckets.{bucketId}.files.create
* storage.buckets.{bucketId}.files.read
* storage.buckets.{bucketId}.files.update
@ -69,6 +73,7 @@ use Utopia\Database\Validator\Authorization;
* users.count
* storage.buckets.count
* storage.files.count
* storage.buckets.{bucketId}.files.count
* database.collections.count
* database.documents.count
* database.collections.{collectionId}.documents.count
@ -161,6 +166,18 @@ $cli
'storage.buckets.delete' => [
'table' => 'appwrite_usage_storage_buckets_delete',
],
'storage.files.create' => [
'table' => 'appwrite_usage_storage_files_create',
],
'storage.files.read' => [
'table' => 'appwrite_usage_storage_files_read',
],
'storage.files.update' => [
'table' => 'appwrite_usage_storage_files_update',
],
'storage.files.delete' => [
'table' => 'appwrite_usage_storage_files_delete',
],
'storage.buckets.bucketId.files.create' => [
'table' => 'appwrite_usage_storage_files_create',
'groupBy' => 'bucketId',
@ -387,19 +404,19 @@ $cli
foreach ($projects as $project) {
$projectId = $project->getId();
// Get total storage
// storage.tags.total
$dbForProject->setNamespace('project_' . $projectId . '_internal');
$storageTotal = $dbForProject->sum('files', 'sizeOriginal') + $dbForProject->sum('tags', 'size');
$storageTotal = (int) $dbForProject->sum('tags', 'size');
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_storage.total'); //Construct unique id for each metric using time, period and metric
$id = \md5($time . '_30m_storage.tags.total'); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => '30m',
'time' => $time,
'metric' => 'storage.total',
'metric' => 'storage.tags.total',
'value' => $storageTotal,
'type' => 1,
]));
@ -419,7 +436,7 @@ $cli
'$id' => $id,
'period' => '1d',
'time' => $time,
'metric' => 'storage.total',
'metric' => 'storage.tags.total',
'value' => $storageTotal,
'type' => 1,
]));
@ -449,7 +466,11 @@ $cli
'namespace' => 'internal',
'subCollections' => [
'files' => [
'namespace' => 'internal',
'namespace' => 'external',
'collectionPrefix' => 'bucket_',
'sum' => [
'field' => 'sizeOriginal'
]
],
]
]
@ -511,6 +532,7 @@ $cli
$latestParent = null;
$subCollectionCounts = []; //total project level count of sub collections
$subCollectionTotals = []; //total project level sum of sub collections
do { // Loop over all the parent collection document for each sub collection
$dbForProject->setNamespace("project_{$projectId}_{$options['namespace']}");
@ -525,7 +547,7 @@ $cli
foreach ($parents as $parent) {
foreach ($subCollections as $subCollection => $subOptions) { // Sub collection counts, like database.collections.collectionId.documents.count
$dbForProject->setNamespace("project_{$projectId}_{$subOptions['namespace']}");
$count = $dbForProject->count($parent->getId());
$count = $dbForProject->count(($subOptions['collectionPrefix'] ?? '') . $parent->getId());
$subCollectionCounts[$subCollection] = ($subCollectionCounts[$subCollection] ?? 0) + $count; // Project level counts for sub collections like database.documents.count
@ -571,6 +593,55 @@ $cli
$document->setAttribute('value', $count)
);
}
// check if sum calculation is required
$sum = $subOptions['sum'] ?? [];
if(empty($sum)) {
continue;
}
$dbForProject->setNamespace("project_{$projectId}_{$subOptions['namespace']}");
$total = (int) $dbForProject->sum(($subOptions['collectionPrefix'] ?? '') . $parent->getId(), $sum['field']);
$subCollectionTotals[$subCollection] = ($ssubCollectionTotals[$subCollection] ?? 0) + $total; // Project level sum for sub collections like storage.total
$dbForProject->setNamespace("project_{$projectId}_internal");
$metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.total" : "{$metricPrefix}.{$collection}.{$parent->getId()}.{$subCollection}.total";
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '30m',
'metric' => $metric,
'value' => $total,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $total));
}
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '1d',
'metric' => $metric,
'value' => $total,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $total));
}
}
}
} while (!empty($parents));
@ -623,7 +694,50 @@ $cli
);
}
}
} catch (\Exception $e) {
/**
* Inserting project level sums for sub collections like storage.total
*/
foreach ($subCollectionTotals as $subCollection => $count) {
$dbForProject->setNamespace("project_{$projectId}_internal");
$metric = empty($metricPrefix) ? "{$subCollection}.total" : "{$metricPrefix}.{$subCollection}.total";
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '30m',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
$document = $dbForProject->getDocument('stats', $id);
if ($document->isEmpty()) {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'time' => $time,
'period' => '1d',
'metric' => $metric,
'value' => $count,
'type' => 1,
]));
} else {
$dbForProject->updateDocument('stats', $document->getId(),
$document->setAttribute('value', $count));
}
}
} catch (\Exception$e) {
Console::warning("Failed to save database counters data for project {$collection}: {$e->getMessage()}");
}
}

View file

@ -0,0 +1,492 @@
<?php
$home = $this->getParam('home', '');
$fileLimit = $this->getParam('fileLimit', 0);
$fileLimitHuman = $this->getParam('fileLimitHuman', 0);
?>
<div
data-service="storage.getBucket"
data-param-bucket-id="{{router.params.id}}"
data-scope="sdk"
data-event="load,storage.updateBucket"
data-name="project-bucket">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/storage?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Storage</a>
<br />
<span data-ls-bind="{{project-bucket.name}}">&nbsp;&nbsp;</span>
</h1>
</div>
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-json">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>JSON View</h2>
<div class="margin-bottom">
<input type="hidden" data-ls-bind="{{project-bucket}}" data-forms-code />
</div>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/storage/bucket?id={{router.params.id}}&project={{router.params.project}}">
<h2 class="margin-bottom">Files</h2>
<form class="box padding-small margin-bottom search"
data-service="storage.listFiles"
data-event="submit"
data-param-bucket-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset=""
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<div class="row thin responsive">
<div class="col span-10">
<input name="search" id="searchFiles" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
</div>
<div class="col span-2 desktops-only">
<button class="fill" title="Search" aria-label="Search">Search</button>
</div>
</div>
</form>
<div
data-service="storage.listFiles"
data-event="load,storage.createFile,storage.updateFile,storage.deleteFile"
data-param-bucket-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset="{{router.params.offset}}"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files">
<div data-ls-if="0 == {{project-files.sum}}" class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Files Found</h3>
<p class="margin-bottom-no">Upload your first file to get started</p>
</div>
<div data-ls-if="0 != {{project-files.sum}}">
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-files.sum}}"></span> files found</div>
<div class="box margin-bottom">
<table class="vertical">
<thead>
<tr>
<th width="40"></th>
<th>Filename</th>
<th width="140">Type</th>
<th width="100">Size</th>
<th width="120">Created</th>
</tr>
</thead>
<tbody data-ls-loop="project-files.files" data-ls-as="file">
<tr>
<td class="hide">
<img src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/buckets/{{router.params.id}}/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
</td>
<td data-title="Name: " class="text-one-liner" data-ls-attrs="title={{file.name}}" >
<div data-ui-modal class="box modal sticky-footer width-large close" data-button-text="{{file.name}}" data-button-class="link" data-button-element="span">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update File</h1>
<hr />
<div class="row responsive modalize">
<div class="col span-8">
<form class="strip"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Storage File"
data-service="storage.updateFile"
data-event="file-update-{{file.$id}}"
data-scope="sdk"
data-success="alert,trigger"
data-success-param-alert-text="File updated successfully"
data-success-param-trigger-events="storage.updateFile"
data-failure="alert"
data-failure-param-alert-text="Failed to update file"
data-failure-param-alert-classname="error">
<label for="files-fileId">File ID</label>
<div class="input-copy">
<input data-forms-copy type="text" data-ls-attrs="id=file-id-{{file.$id}}" name="fileId" disabled data-ls-bind="{{file.$id}}" />
</div>
<input type="hidden" data-ls-attrs="id=file-bucketId-{{file.$id}}" name="bucketId" data-ls-bind="{{file.bucketId}}">
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" data-ls-attrs="id=file-read-{{file.$id}}" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{file.$read}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" data-ls-attrs="id=file-write-{{file.$id}}" name="write" data-forms-tags data-cast-to="json" data-ls-bind="{{file.$write}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
</form>
<form class="strip"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete File"
data-service="storage.deleteFile"
data-scope="sdk"
data-event="file-delete-{{file.$id}}"
data-confirm="Are you sure you want to delete this file?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted file successfully"
data-success-param-trigger-events="storage.deleteFile"
data-failure="alert"
data-failure-param-alert-text="Failed to delete file"
data-failure-param-alert-classname="error">
<input type="hidden" name="bucketId" data-ls-bind="{{file.bucketId}}" />
<input type="hidden" name="fileId" data-ls-bind="{{file.$id}}" />
</form>
</div>
<div class="col span-4 text-size-small">
<div class="margin-bottom-small">File Preview</div>
<div class="margin-bottom-small">
<img src="" class="file-preview" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/buckets/{{router.params.id}}/files/{{file.$id}}/preview?width=350&height=250&project={{router.params.project}}&mode=admin" loading="lazy" width="225" height="160" />
</div>
<div class="margin-bottom-tiny">
<a href="" data-ls-attrs="href={{env.ENDPOINT}}/v1/storage/buckets/{{router.params.id}}/files/{{file.$id}}/view?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> New Window <i class="icon-link-ext"></i></a>
</div>
<div class="margin-bottom-small">
<a href="" data-ls-attrs="href={{env.ENDPOINT}}/v1/storage/buckets/{{router.params.id}}/files/{{file.$id}}/download?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Download <i class="icon-link-ext"></i></a>
</div>
<div class="margin-bottom-tiny">
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Type: <span data-ls-bind="{{file.mimeType}}"></span>
</div>
<div class="margin-bottom-tiny">
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Size: <span data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</div>
<div class="margin-bottom">
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Created at: <span data-ls-bind="{{file.dateCreated|dateText}}"></span>
</div>
</div>
</div>
<footer>
<button class="link pull-end text-danger" data-ls-ui-trigger="file-delete-{{file.$id}},modal-close">Delete File</button>
<button type="button" data-ls-ui-trigger="file-update-{{file.$id}},modal-close">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse desktops-only-inline tablets-only-inline">Cancel</button>
</footer>
</div>
</td>
<td data-title="Type: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.mimeType}}"></span>
</td>
<td data-title="Size: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</td>
<td data-title="Created: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="storage.listFiles"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-sum="{{project-files.sum}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-files.sum|pageTotal}}"></span>
<form
data-service="storage.listFiles"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-files.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="box modal sticky-footer close" data-button-text="Add File">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Upload File</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Storage File"
data-service="storage.createFile"
data-event="submit"
data-scope="sdk"
data-loading="Uploading File..."
data-success="alert,trigger,reset"
data-success-param-alert-text="File uploaded successfully"
data-success-param-trigger-events="storage.createFile"
data-failure="alert"
data-failure-param-alert-text="Failed to upload file"
data-failure-param-alert-classname="error">
<input type="hidden" name="bucketId" id="files-bucketId" data-ls-bind="{{router.params.id}}">
<label for="fileId">File ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="storage.getFile"
required
maxlength="36"
name="fileId"
id="fileId" />
<label for="file-read">File</label>
<input type="file" name="file" id="file-file" size="1" required>
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div>
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-read" name="read" data-forms-tags data-cast-to="json" value="<?php echo htmlentities(json_encode(['role:all'])); ?>" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-write" name="write" data-forms-tags data-cast-to="json" value="" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
</div>
</li>
<li data-state="/console/storage/bucket/usage?id={{router.params.id}}&project={{router.params.project}}">
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="storage.getBucketUsage"
data-event="submit"
data-name="usage"
data-param-bucket-id="{{router.params.id}}"
data-param-range="90d">
<button class="tick">90d</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
data-service="storage.getBucketUsage"
data-event="submit"
data-name="usage"
data-param-bucket-id="{{router.params.id}}">
<button class="tick">30d</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="storage.getBucketUsage"
data-event="submit"
data-name="usage"
data-param-bucket-id="{{router.params.id}}"
data-param-range="24h">
<button class="tick">24h</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
<h2>Usage</h2>
<div data-service="storage.getBucketUsage" data-event="load" data-name="usage" data-param-bucket-id="{{router.params.id}}">
<h3 class="margin-bottom-tiny">Files</h3>
<p class="text-fade">Count of files over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-show-y-axis="true" data-forms-chart="Files=filesCount" data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Files </li>
</ul>
<h3 class="margin-bottom-tiny">Operations</h3>
<p class="text-fade">Count of files create, read, update and delete operations over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-show-y-axis="true" data-forms-chart="Create=filesCreate,Read=filesRead,Updated=filesUpdate,Deleted=filesDelete" data-colors="create,read,update,delete" data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes crud margin-bottom-large">
<li>Create</li>
<li>Read</li>
<li>Update</li>
<li>Delete</li>
</ul>
</div>
</li>
<li data-state="/console/storage/bucket/settings?id={{router.params.id}}&project={{router.params.project}}">
<h2>Settings</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Storage Bucket"
data-service="storage.updateBucket"
data-scope="sdk"
data-event="submit"
data-param-bucket-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated bucket successfully"
data-success-param-trigger-events="storage.updateBucket"
data-failure="alert"
data-failure-param-alert-text="Failed to update bucket"
data-failure-param-alert-classname="error">
<label>&nbsp;</label>
<div class="box">
<label for="bucket-name">Name</label>
<input name="name" id="bucket-name" type="text" autocomplete="off" data-ls-bind="{{project-bucket.name}}" data-forms-text-direction required placeholder="Bucket Name" maxlength="128" />
<label for="bucket-maximum-file-size">Maximum File Size (bytes) <span class="tooltip small" data-tooltip="Limit file size allowed in the bucket."><i class="icon-info-circled"></i></span></label>
<input name="maximumFileSize" id="bucket-maximum-file-size" type="number" autocomplete="off" data-ls-bind="{{project-bucket.maximumFileSize}}" min="1" data-cast-to="integer" />
<div class="margin-bottom">
<input name="enabled" type="hidden" data-forms-switch data-cast-to="boolean" data-ls-bind="{{project-bucket.enabled}}" /> &nbsp; Enabled <span class="tooltip" data-tooltip="Mark whether bucket is enabled"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="encryption" type="hidden" data-forms-switch data-cast-to="boolean" data-ls-bind="{{project-bucket.encryption}}" /> &nbsp; Encryption <span class="tooltip" data-tooltip="Mark whether bucket is encrypted"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="antiVirus" type="hidden" data-forms-switch data-cast-to="boolean" data-ls-bind="{{project-bucket.antiVirus}}" /> &nbsp; Anti Virus <span class="tooltip" data-tooltip="Mark whether anti virus scanning is enabled"><i class="icon-info-circled"></i></span>
</div>
<label for="bucket-allowedFileExtensions">Allowed File Extensions</label>
<input type="hidden" id="bucket-allowedFileExtensions" name="allowedFileExtensions" data-forms-tags data-cast-to="json" data-ls-bind="{{project-bucket.allowedFileExtensions}}" placeholder="Allowed file extensions (pdf, mp4)" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty to allow all.</div>
<label class="margin-bottom-small">Permissions</label>
<p class="text-fade text-size-small">Choose the permissions model for this bucket.</p>
<hr class="margin-top-small" />
<div class="row">
<div class="col span-1"><input name="permission" value="file" type="radio" class="margin-top-no" data-ls-bind="{{project-bucket.permission}}" /></div>
<div class="col span-11">
<b>File Level</b>
<p class="text-fade margin-top-tiny">With File Level permissions, you have granular access control over every file. Users will only be able to access files for which they have explicit permissions.</p>
<p class="text-fade margin-top-tiny">In this permission level file permissions take precedence and bucket permissions are ignored.</p>
</div>
</div>
<div class="row">
<div class="col span-1"><input name="permission" value="bucket" type="radio" class="margin-top-tiny" data-ls-bind="{{project-bucket.permission}}" /></div>
<div class="col span-11">
<b>Bucket Level</b>
<p class="text-fade margin-top-tiny">With Bucket Level permissions, you assign permissions only once in the bucket.</p>
<p class="text-fade margin-top-tiny">In this permission level permissions assigned to bucket takes the precedence and file permissions are ignored</p>
<div data-ls-if="{{project-bucket.permission}} == 'bucket'">
<label for="bucket-read">Read Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
<input type="hidden" id="bucket-read" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{project-bucket.$permissions.read}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add * for wildcard access</div>
<label for="bucket-write">Write Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="bucket-write" name="write" data-forms-tags data-cast-to="json" data-ls-bind="{{project-bucket.$permissions.write}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add * for wildcard access</div>
</div>
</div>
</div>
<hr class="margin-top-no" />
<button>Update</button>
</form>
</div>
</div>
<div class="col span-4 sticky-top">
<label>Bucket ID</label>
<div class="input-copy margin-bottom">
<input id="id" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-bucket.$id}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-bucket.dateUpdated|dateText}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-bucket.dateCreated|dateText}}"></span></li>
</ul>
<form name="storage.deleteBucket" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Storage Bucket"
data-service="storage.deleteBucket"
data-event="submit"
data-param-bucket-id="{{router.params.id}}"
data-confirm="Are you sure you want to delete this bucket?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Bucket deleted successfully"
data-success-param-trigger-events="storage.deleteBucket"
data-success-param-redirect-url="/console/storage?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete bucket"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Bucket</button>
</form>
</div>
</div>
</li>
</ul>
</div>
</div>

View file

@ -1,8 +1,3 @@
<?php
$home = $this->getParam('home', '');
$fileLimit = $this->getParam('fileLimit', 0);
$fileLimitHuman = $this->getParam('fileLimitHuman', 0);
?>
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/home?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Home</a>
@ -15,320 +10,202 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/storage?project={{router.params.project}}">
<h2 class="margin-bottom">Files</h2>
<h2>Buckets</h2>
<form class="box padding-small margin-bottom search"
data-service="storage.listFiles"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset=""
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<div class="row thin responsive">
<div class="col span-10">
<input name="search" id="searchFiles" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
</div>
<div class="col span-2 desktops-only">
<button class="fill" title="Search" aria-label="Search">Search</button>
</div>
</div>
</form>
<div
data-service="storage.listFiles"
data-event="load,storage.createFile,storage.updateFile,storage.deleteFile"
<div class="margin-top"
data-service="storage.listBuckets"
data-event="load,storage.createBucket,storage.updateBucket,storage.deleteBucket"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-offset="{{router.params.offset}}"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files">
data-name="project-buckets">
<div data-ls-if="0 == {{project-files.sum}}" class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Files Found</h3>
<div data-ls-if="(!{{project-buckets.sum}})" class="box dashboard margin-bottom">
<div class="margin-bottom-small margin-top-small margin-end margin-start">
<h3 class="margin-bottom-small text-bold">No Buckets Found</h3>
<p class="margin-bottom-no">Upload your first file to get started</p>
<p class="margin-bottom-no">You haven't created any buckets for your project yet.</p>
</div>
</div>
<div data-ls-if="0 != {{project-files.sum}}">
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-files.sum}}"></span> files found</div>
<div data-ls-if="0 != {{project-buckets.sum}}">
<ul data-ls-loop="project-buckets.buckets" data-ls-as="bucket" data-ls-append="" class="tiles cell-3 margin-bottom-small">
<li class="margin-bottom">
<a data-ls-attrs="href=/console/storage/bucket?id={{bucket.$id}}&project={{router.params.project}}" class="box">
<div data-ls-bind="{{bucket.name}}" class="text-one-liner margin-bottom text-bold">&nbsp;</div>
<div class="box margin-bottom">
<table class="vertical">
<thead>
<tr>
<th width="40"></th>
<th>Filename</th>
<th width="140">Type</th>
<th width="100">Size</th>
<th width="120">Created</th>
</tr>
</thead>
<tbody data-ls-loop="project-files.files" data-ls-as="file">
<tr>
<td class="hide">
<img src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
</td>
<td data-title="Name: " class="text-one-liner" data-ls-attrs="title={{file.name}}" >
<div data-ui-modal class="box modal sticky-footer width-large close" data-button-text="{{file.name}}" data-button-class="link" data-button-element="span">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update File</h1>
<hr />
<div class="row responsive modalize">
<div class="col span-8">
<form class="strip"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Storage File"
data-service="storage.updateFile"
data-event="file-update-{{file.$id}}"
data-scope="sdk"
data-success="alert,trigger"
data-success-param-alert-text="File updated successfully"
data-success-param-trigger-events="storage.updateFile"
data-failure="alert"
data-failure-param-alert-text="Failed to update file"
data-failure-param-alert-classname="error">
<label for="files-fileId">File ID</label>
<div class="input-copy">
<input data-forms-copy type="text" data-ls-attrs="id=file-id-{{file.$id}}" name="fileId" disabled data-ls-bind="{{file.$id}}" />
</div>
<input type="hidden" data-ls-attrs="id=file-folderId-{{file.$id}}" name="folderId" data-cast-to="integer" value="1">
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" data-ls-attrs="id=file-read-{{file.$id}}" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{file.$read}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" data-ls-attrs="id=file-write-{{file.$id}}" name="write" data-forms-tags data-cast-to="json" data-ls-bind="{{file.$write}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
</form>
<form class="strip"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete File"
data-service="storage.deleteFile"
data-scope="sdk"
data-event="file-delete-{{file.$id}}"
data-confirm="Are you sure you want to delete this file?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted file successfully"
data-success-param-trigger-events="storage.deleteFile"
data-failure="alert"
data-failure-param-alert-text="Failed to delete file"
data-failure-param-alert-classname="error">
<input type="hidden" name="fileId" data-ls-bind="{{file.$id}}" />
</form>
</div>
<div class="col span-4 text-size-small">
<div class="margin-bottom-small">File Preview</div>
<div class="margin-bottom-small">
<img src="" class="file-preview" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/preview?width=350&height=250&project={{router.params.project}}&mode=admin" loading="lazy" width="225" height="160" />
</div>
<div class="margin-bottom-tiny">
<a href="" data-ls-attrs="href={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/view?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> New Window <i class="icon-link-ext"></i></a>
</div>
<div class="margin-bottom-small">
<a href="" data-ls-attrs="href={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/download?project={{router.params.project}}&mode=admin" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Download <i class="icon-link-ext"></i></a>
</div>
<div class="margin-bottom-tiny">
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Type: <span data-ls-bind="{{file.mimeType}}"></span>
</div>
<div class="margin-bottom-tiny">
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Size: <span data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</div>
<div class="margin-bottom">
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Created at: <span data-ls-bind="{{file.dateCreated|dateText}}"></span>
</div>
</div>
</div>
<footer>
<button class="link pull-end text-danger" data-ls-ui-trigger="file-delete-{{file.$id}},modal-close">Delete File</button>
<button type="button" data-ls-ui-trigger="file-update-{{file.$id}},modal-close">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse desktops-only-inline tablets-only-inline">Cancel</button>
</footer>
</div>
</td>
<td data-title="Type: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.mimeType}}"></span>
</td>
<td data-title="Size: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
</td>
<td data-title="Created: ">
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>
</td>
</tr>
</tbody>
</table>
</div>
<i class="icon-right-open"></i>
</a>
</li>
</ul>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="storage.listFiles"
data-service="storage.listBuckets"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-name="project-buckets"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-sum="{{project-files.sum}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-sum="{{project-buckets.sum}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-files.sum|pageTotal}}"></span>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-buckets.sum|pageTotal}}"></span>
<form
data-service="storage.listFiles"
data-service="storage.listBuckets"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-files"
data-name="project-buckets"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-files.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-buckets.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="box modal sticky-footer close" data-button-text="Add File">
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Bucket">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Upload File</h1>
<h1>New Bucket</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Storage File"
data-service="storage.createFile"
data-analytics-label="Create Storage Bucket"
data-service="storage.createBucket"
data-event="submit"
data-scope="sdk"
data-loading="Uploading File..."
data-success="alert,trigger,reset"
data-success-param-alert-text="File uploaded successfully"
data-success-param-trigger-events="storage.createFile"
data-success="alert,reset,redirect,trigger"
data-success-param-alert-text="Bucket created successfully"
data-success-param-redirect-url="/console/storage/bucket/settings?id={{serviceData.$id}}&project={{router.params.project}}"
data-success-param-trigger-events="storage.createBucket"
data-failure="alert"
data-failure-param-alert-text="Failed to upload file"
data-failure-param-alert-text="Failed to create bucket"
data-failure-param-alert-classname="error">
<input type="hidden" name="folderId" id="files-folderId" data-cast-to="integer" value="1">
<label for="fileId">File ID</label>
<label for="bucket-id">Bucket ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="storage.getFile"
data-validator="storage.getBucket"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
name="fileId"
id="fileId" />
name="bucketId" />
<label for="file-read">File</label>
<input type="file" name="file" id="file-file" size="1" required>
<label for="bucket-name">Name</label>
<input type="text" class="full-width" id="bucket-name" name="name" required autocomplete="off" maxlength="128" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div>
<input type="hidden" id="bucket-permission" name="permission" required value="file" />
<input type="hidden" id="bucket-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="bucket-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<label for="file-read">Read Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-read" name="read" data-forms-tags data-cast-to="json" value="<?php echo htmlentities(json_encode(['role:all'])); ?>" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
<hr />
<label for="file-write">Write Access (<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</label>
<input type="hidden" id="file-write" name="write" data-forms-tags data-cast-to="json" value="" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'role:all' for wildcard access</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</li>
<li data-state="/console/storage/usage?project={{router.params.project}}">
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
</li>
<li data-state="/console/storage/usage?project={{router.params.project}}">
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="storage.getUsage"
data-event="submit"
data-name="usage"
data-param-range="90d">
<button class="tick">90d</button>
</form>
<button class="tick">90d</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
data-service="storage.getUsage"
data-event="submit"
data-name="usage">
<button class="tick">30d</button>
</form>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
data-service="storage.getUsage"
data-event="submit"
data-name="usage">
<button class="tick">30d</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="storage.getUsage"
data-event="submit"
data-name="usage"
data-param-range="24h">
<button class="tick">24h</button>
</form>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="storage.getUsage"
data-event="submit"
data-name="usage"
data-param-range="24h">
<button class="tick">24h</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
<h2>Usage</h2>
<h2>Usage</h2>
<div
data-service="storage.getUsage"
data-event="load"
data-name="usage">
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Total Files=files" data-height="140" data-show-y-axis="true" />
<div
data-service="storage.getUsage"
data-event="load"
data-name="usage">
<h3 class="margin-bottom-tiny">Objects</h3>
<p class="text-fade">Count of buckets, files and total storage used over time</p>
<div class="box">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-show-y-axis="true" data-forms-chart="Total Files=filesCount,Total Buckets=bucketsCount" data-height="140" />
</div>
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Total Files <span data-ls-bind="({{usage.files|statsGetLast|statsTotal}})"></span></li>
</ul>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Total Storage=storage" data-height="140" data-show-y-axis="true" />
<ul class="chart-notes margin-top-small margin-bottom-large">
<li>Total Files</li>
<li>Total Buckets</li>
<!-- <li>Total Storage <span data-ls-bind="({{usage.filesStorage|statsGetLast|statsTotal}})"></span></li> -->
</ul>
<!-- CRUDS class for graph fixed colors, use color codes from Docs for CRUD operations -->
<h3 class="margin-bottom-tiny">Buckets</h3>
<p class="text-fade">Count of bucket create, read, update and delete operations over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no crud margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Buckets create=bucketsCreate,Buckets read=bucketsRead,Buckets update=bucketsUpdate,Buckets delete=bucketsDelete" data-show-y-axis="true" data-colors="create,read,update,delete" data-height="140" />
</div>
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Total Storage (<span data-ls-bind="{{usage.storage|statsGetLast|humanFileSize}}"></span> <span data-ls-bind="{{usage.storage|statsGetLast|humanFileUnit}}"></span>)</li>
</ul>
</div>
</li>
</ul>
<ul class="chart-notes crud margin-bottom-large">
<li class="green">Create</li>
<li class="green">Read</li>
<li class="green">Update</li>
<li class="green">Delete</li>
</ul>
<h3 class="margin-bottom-tiny">Files</h3>
<p class="text-fade">Count of file create, read, update and delete operations over time</p>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart background-image-no border-no crud margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Files create=filesCreate,Files read=filesRead,Files update=filesUpdate,Files delete=filesDelete" data-show-y-axis="true" data-colors="create,read,update,delete" data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes crud margin-bottom-large">
<li class="green">Create</li>
<li class="green">Read</li>
<li class="green">Update</li>
<li class="green">Delete</li>
</ul>
</div>
</li>
</ul>
</div>
</div>

152
composer.lock generated
View file

@ -489,16 +489,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.4.0",
"version": "7.4.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94"
"reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/868b3571a039f0ebc11ac8f344f4080babe2cb94",
"reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
"reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
"shasum": ""
},
"require": {
@ -507,7 +507,7 @@
"guzzlehttp/psr7": "^1.8.3 || ^2.1",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2"
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
@ -593,7 +593,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.4.0"
"source": "https://github.com/guzzle/guzzle/tree/7.4.1"
},
"funding": [
{
@ -609,7 +609,7 @@
"type": "tidelift"
}
],
"time": "2021-10-18T09:52:00+00:00"
"time": "2021-12-06T18:43:05+00:00"
},
{
"name": "guzzlehttp/promises",
@ -1591,25 +1591,25 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.5.0",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8"
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=8.0.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
"dev-main": "3.0-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1638,7 +1638,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0"
},
"funding": [
{
@ -1654,7 +1654,7 @@
"type": "tidelift"
}
],
"time": "2021-07-12T14:48:14+00:00"
"time": "2021-11-01T23:48:49+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -2554,7 +2554,7 @@
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage",
"reference": "88ed83274542ba3c9d34c5dabd17e0cfb480519d"
"reference": "c47611b3a4a36c674c16e9720e86187452eb1cc2"
},
"require": {
"php": ">=8.0",
@ -2570,6 +2570,11 @@
"Utopia\\Storage\\": "src/Storage"
}
},
"autoload-dev": {
"psr-4": {
"Utopia\\Tests\\": "tests/Storage"
}
},
"license": [
"MIT"
],
@ -2587,7 +2592,7 @@
"upf",
"utopia"
],
"time": "2021-12-03T04:31:08+00:00"
"time": "2021-12-09T07:34:55+00:00"
},
{
"name": "utopia-php/swoole",
@ -3033,6 +3038,77 @@
},
"time": "2021-11-12T11:09:38+00:00"
},
{
"name": "composer/pcre",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "3d322d715c43a1ac36c7fe215fa59336265500f2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/3d322d715c43a1ac36c7fe215fa59336265500f2",
"reference": "3d322d715c43a1ac36c7fe215fa59336265500f2",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1",
"phpstan/phpstan-strict-rules": "^1.1",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/1.0.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2021-12-06T15:17:27+00:00"
},
{
"name": "composer/semver",
"version": "3.2.6",
@ -3116,25 +3192,27 @@
},
{
"name": "composer/xdebug-handler",
"version": "2.0.2",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
"reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339"
"reference": "6555461e76962fd0379c444c46fd558a0fcfb65e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339",
"reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6555461e76962fd0379c444c46fd558a0fcfb65e",
"reference": "6555461e76962fd0379c444c46fd558a0fcfb65e",
"shasum": ""
},
"require": {
"composer/pcre": "^1",
"php": "^5.3.2 || ^7.0 || ^8.0",
"psr/log": "^1 || ^2 || ^3"
},
"require-dev": {
"phpstan/phpstan": "^0.12.55",
"symfony/phpunit-bridge": "^4.2 || ^5"
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-strict-rules": "^1.1",
"symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0"
},
"type": "library",
"autoload": {
@ -3160,7 +3238,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/xdebug-handler/issues",
"source": "https://github.com/composer/xdebug-handler/tree/2.0.2"
"source": "https://github.com/composer/xdebug-handler/tree/2.0.3"
},
"funding": [
{
@ -3176,7 +3254,7 @@
"type": "tidelift"
}
],
"time": "2021-07-31T17:03:58+00:00"
"time": "2021-12-08T13:07:32+00:00"
},
{
"name": "dnoegel/php-xdg-base-dir",
@ -4007,16 +4085,16 @@
},
{
"name": "phpspec/prophecy",
"version": "1.14.0",
"version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e"
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
"shasum": ""
},
"require": {
@ -4068,22 +4146,22 @@
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/1.14.0"
"source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
},
"time": "2021-09-10T09:02:12+00:00"
"time": "2021-12-08T12:19:24+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.9",
"version": "9.2.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b"
"reference": "d5850aaf931743067f4bfc1ae4cbd06468400687"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687",
"reference": "d5850aaf931743067f4bfc1ae4cbd06468400687",
"shasum": ""
},
"require": {
@ -4139,7 +4217,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.9"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10"
},
"funding": [
{
@ -4147,7 +4225,7 @@
"type": "github"
}
],
"time": "2021-11-19T15:21:02+00:00"
"time": "2021-12-05T09:12:13+00:00"
},
{
"name": "phpunit/php-file-iterator",

View file

@ -3870,11 +3870,10 @@
};
this.storage = {
/**
* List Files
* List buckets
*
* Get a list of all the user files. You can use the query params to filter
* your results. On admin mode, this endpoint will return a list of all of the
* project's files. [Learn more about different API modes](/docs/admin).
* Get a list of all the storage buckets. You can use the query params to
* filter your results.
*
* @param {string} search
* @param {number} limit
@ -3885,8 +3884,219 @@
* @throws {AppwriteException}
* @returns {Promise}
*/
listFiles: (search, limit, offset, cursor, cursorDirection, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/storage/files';
listBuckets: (search, limit, offset, cursor, cursorDirection, orderType) => __awaiter(this, void 0, void 0, function* () {
let path = '/storage/buckets';
let payload = {};
if (typeof search !== 'undefined') {
payload['search'] = search;
}
if (typeof limit !== 'undefined') {
payload['limit'] = limit;
}
if (typeof offset !== 'undefined') {
payload['offset'] = offset;
}
if (typeof cursor !== 'undefined') {
payload['cursor'] = cursor;
}
if (typeof cursorDirection !== 'undefined') {
payload['cursorDirection'] = cursorDirection;
}
if (typeof orderType !== 'undefined') {
payload['orderType'] = orderType;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Create storage bucket
*
* Create a new storage bucket.
*
* @param {string} bucketId
* @param {string} name
* @param {string} permission
* @param {string} read
* @param {string} write
* @param {number} maximumFileSize
* @param {string[]} allowedFileExtensions
* @param {boolean} enabled
* @param {string} adapter
* @param {boolean} encryption
* @param {boolean} antiVirus
* @throws {AppwriteException}
* @returns {Promise}
*/
createBucket: (bucketId, name, permission, read, write, maximumFileSize, allowedFileExtensions, enabled, adapter, encryption, antiVirus) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof name === 'undefined') {
throw new AppwriteException('Missing required parameter: "name"');
}
if (typeof permission === 'undefined') {
throw new AppwriteException('Missing required parameter: "permission"');
}
let path = '/storage/buckets';
let payload = {};
if (typeof bucketId !== 'undefined') {
payload['bucketId'] = bucketId;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof permission !== 'undefined') {
payload['permission'] = permission;
}
if (typeof read !== 'undefined') {
payload['read'] = read;
}
if (typeof write !== 'undefined') {
payload['write'] = write;
}
if (typeof maximumFileSize !== 'undefined') {
payload['maximumFileSize'] = maximumFileSize;
}
if (typeof allowedFileExtensions !== 'undefined') {
payload['allowedFileExtensions'] = allowedFileExtensions;
}
if (typeof enabled !== 'undefined') {
payload['enabled'] = enabled;
}
if (typeof adapter !== 'undefined') {
payload['adapter'] = adapter;
}
if (typeof encryption !== 'undefined') {
payload['encryption'] = encryption;
}
if (typeof antiVirus !== 'undefined') {
payload['antiVirus'] = antiVirus;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('post', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Get Bucket
*
* Get a storage bucket by its unique ID. This endpoint response returns a
* JSON object with the storage bucket metadata.
*
* @param {string} bucketId
* @throws {AppwriteException}
* @returns {Promise}
*/
getBucket: (bucketId) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
let path = '/storage/buckets/{bucketId}'.replace('{bucketId}', bucketId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Update Bucket
*
* Update a storage bucket by its unique ID.
*
* @param {string} bucketId
* @param {string} name
* @param {string} read
* @param {string} write
* @param {number} maximumFileSize
* @param {string[]} allowedFileExtensions
* @param {boolean} enabled
* @param {boolean} encryption
* @param {boolean} antiVirus
* @throws {AppwriteException}
* @returns {Promise}
*/
updateBucket: (bucketId, name, read, write, maximumFileSize, allowedFileExtensions, enabled, encryption, antiVirus) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof name === 'undefined') {
throw new AppwriteException('Missing required parameter: "name"');
}
let path = '/storage/buckets/{bucketId}'.replace('{bucketId}', bucketId);
let payload = {};
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof read !== 'undefined') {
payload['read'] = read;
}
if (typeof write !== 'undefined') {
payload['write'] = write;
}
if (typeof maximumFileSize !== 'undefined') {
payload['maximumFileSize'] = maximumFileSize;
}
if (typeof allowedFileExtensions !== 'undefined') {
payload['allowedFileExtensions'] = allowedFileExtensions;
}
if (typeof enabled !== 'undefined') {
payload['enabled'] = enabled;
}
if (typeof encryption !== 'undefined') {
payload['encryption'] = encryption;
}
if (typeof antiVirus !== 'undefined') {
payload['antiVirus'] = antiVirus;
}
const uri = new URL(this.config.endpoint + path);
return yield this.call('put', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Delete Bucket
*
* Delete a storage bucket by its unique ID.
*
* @param {string} bucketId
* @throws {AppwriteException}
* @returns {Promise}
*/
deleteBucket: (bucketId) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
let path = '/storage/buckets/{bucketId}'.replace('{bucketId}', bucketId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('delete', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* List Files
*
* Get a list of all the user files. You can use the query params to filter
* your results. On admin mode, this endpoint will return a list of all of the
* project's files. [Learn more about different API modes](/docs/admin).
*
* @param {string} bucketId
* @param {string} search
* @param {number} limit
* @param {number} offset
* @param {string} cursor
* @param {string} cursorDirection
* @param {string} orderType
* @throws {AppwriteException}
* @returns {Promise}
*/
listFiles: (bucketId, search, limit, offset, cursor, cursorDirection, orderType) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
let path = '/storage/buckets/{bucketId}/files'.replace('{bucketId}', bucketId);
let payload = {};
if (typeof search !== 'undefined') {
payload['search'] = search;
@ -3918,21 +4128,25 @@
* assigned to read and write access unless he has passed custom values for
* read and write arguments.
*
* @param {string} bucketId
* @param {string} fileId
* @param {File} file
* @param {string[]} read
* @param {string[]} write
* @param {string} read
* @param {string} write
* @throws {AppwriteException}
* @returns {Promise}
*/
createFile: (fileId, file, read, write) => __awaiter(this, void 0, void 0, function* () {
createFile: (bucketId, fileId, file, read, write) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof fileId === 'undefined') {
throw new AppwriteException('Missing required parameter: "fileId"');
}
if (typeof file === 'undefined') {
throw new AppwriteException('Missing required parameter: "file"');
}
let path = '/storage/files';
let path = '/storage/buckets/{bucketId}/files'.replace('{bucketId}', bucketId);
let payload = {};
if (typeof fileId !== 'undefined') {
payload['fileId'] = fileId;
@ -3957,15 +4171,19 @@
* Get a file by its unique ID. This endpoint response returns a JSON object
* with the file metadata.
*
* @param {string} bucketId
* @param {string} fileId
* @throws {AppwriteException}
* @returns {Promise}
*/
getFile: (fileId) => __awaiter(this, void 0, void 0, function* () {
getFile: (bucketId, fileId) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof fileId === 'undefined') {
throw new AppwriteException('Missing required parameter: "fileId"');
}
let path = '/storage/files/{fileId}'.replace('{fileId}', fileId);
let path = '/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}', bucketId).replace('{fileId}', fileId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
@ -3978,13 +4196,17 @@
* Update a file by its unique ID. Only users with write permissions have
* access to update this resource.
*
* @param {string} bucketId
* @param {string} fileId
* @param {string[]} read
* @param {string[]} write
* @param {string} read
* @param {string} write
* @throws {AppwriteException}
* @returns {Promise}
*/
updateFile: (fileId, read, write) => __awaiter(this, void 0, void 0, function* () {
updateFile: (bucketId, fileId, read, write) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof fileId === 'undefined') {
throw new AppwriteException('Missing required parameter: "fileId"');
}
@ -3994,7 +4216,7 @@
if (typeof write === 'undefined') {
throw new AppwriteException('Missing required parameter: "write"');
}
let path = '/storage/files/{fileId}'.replace('{fileId}', fileId);
let path = '/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}', bucketId).replace('{fileId}', fileId);
let payload = {};
if (typeof read !== 'undefined') {
payload['read'] = read;
@ -4013,15 +4235,19 @@
* Delete a file by its unique ID. Only users with write permissions have
* access to delete this resource.
*
* @param {string} bucketId
* @param {string} fileId
* @throws {AppwriteException}
* @returns {Promise}
*/
deleteFile: (fileId) => __awaiter(this, void 0, void 0, function* () {
deleteFile: (bucketId, fileId) => __awaiter(this, void 0, void 0, function* () {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof fileId === 'undefined') {
throw new AppwriteException('Missing required parameter: "fileId"');
}
let path = '/storage/files/{fileId}'.replace('{fileId}', fileId);
let path = '/storage/buckets/{bucketId}/files/{fileId}'.replace('{bucketId}', bucketId).replace('{fileId}', fileId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('delete', uri, {
@ -4035,15 +4261,19 @@
* 'Content-Disposition: attachment' header that tells the browser to start
* downloading the file to user downloads directory.
*
* @param {string} bucketId
* @param {string} fileId
* @throws {AppwriteException}
* @returns {URL}
*/
getFileDownload: (fileId) => {
getFileDownload: (bucketId, fileId) => {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof fileId === 'undefined') {
throw new AppwriteException('Missing required parameter: "fileId"');
}
let path = '/storage/files/{fileId}/download'.replace('{fileId}', fileId);
let path = '/storage/buckets/{bucketId}/files/{fileId}/download'.replace('{bucketId}', bucketId).replace('{fileId}', fileId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
payload['project'] = this.config.project;
@ -4060,6 +4290,7 @@
* and spreadsheets, will return the file icon image. You can also pass query
* string arguments for cutting and resizing your preview image.
*
* @param {string} bucketId
* @param {string} fileId
* @param {number} width
* @param {number} height
@ -4075,11 +4306,14 @@
* @throws {AppwriteException}
* @returns {URL}
*/
getFilePreview: (fileId, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output) => {
getFilePreview: (bucketId, fileId, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output) => {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof fileId === 'undefined') {
throw new AppwriteException('Missing required parameter: "fileId"');
}
let path = '/storage/files/{fileId}/preview'.replace('{fileId}', fileId);
let path = '/storage/buckets/{bucketId}/files/{fileId}/preview'.replace('{bucketId}', bucketId).replace('{fileId}', fileId);
let payload = {};
if (typeof width !== 'undefined') {
payload['width'] = width;
@ -4128,15 +4362,19 @@
* download method but returns with no 'Content-Disposition: attachment'
* header.
*
* @param {string} bucketId
* @param {string} fileId
* @throws {AppwriteException}
* @returns {URL}
*/
getFileView: (fileId) => {
getFileView: (bucketId, fileId) => {
if (typeof bucketId === 'undefined') {
throw new AppwriteException('Missing required parameter: "bucketId"');
}
if (typeof fileId === 'undefined') {
throw new AppwriteException('Missing required parameter: "fileId"');
}
let path = '/storage/files/{fileId}/view'.replace('{fileId}', fileId);
let path = '/storage/buckets/{bucketId}/files/{fileId}/view'.replace('{bucketId}', bucketId).replace('{fileId}', fileId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
payload['project'] = this.config.project;

View file

@ -183,6 +183,13 @@ window.ls.filter
return $value[$value.length - 1].value;
})
.add("statsGetLast", function ($value) {
if (!$value || $value.length < 1) {
return 0;
}
return $value[$value.length - 1].value;
})
.add("isEmpty", function ($value) {
return (!!$value);
})

View file

@ -160,8 +160,17 @@ window.ls.router
scope: "console",
project: true
})
.add("/console/storage/:tab", {
template: "/console/storage?version=" + APP_ENV.CACHEBUSTER,
.add("/console/storage/bucket", {
template: function(window) {
return window.location.pathname + window.location.search + '&version=' + APP_ENV.CACHEBUSTER;
},
scope: "console",
project: true
})
.add("/console/storage/bucket/:tab", {
template: function(window) {
return window.location.pathname + window.location.search + '&version=' + APP_ENV.CACHEBUSTER;
},
scope: "console",
project: true
})

View file

@ -51,8 +51,9 @@
display: showYAxis,
min: 0,
ticks: {
count: ticksCount,
fontColor: "#8f8f8f"
}
},
}
},
plugins: {
@ -67,7 +68,7 @@
mode: "index",
intersect: false,
caretPadding: 0
}
},
}
}
};
@ -113,8 +114,8 @@
config.options.scales.y.ticks.stepSize = highest / ticksCount;
config.options.scales.y.max = highest;
}
if (chart) {
if(chart) {
chart.destroy();
}
else {

View file

@ -50,6 +50,7 @@ abstract class Migration
'0.10.4' => 'V09',
'0.11.0' => 'V10',
'0.12.0' => 'V10',
'0.13.0' => 'V10',
];
/**

View file

@ -23,6 +23,13 @@ class UsageBuckets extends Model
'example' => new \stdClass,
'array' => true
])
->addRule('filesStorage', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total storage of files in this bucket.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('filesCreate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files created.',

View file

@ -16,20 +16,90 @@ class UsageStorage extends Model
'default' => '',
'example' => '30d',
])
->addRule('storage', [
->addRule('filesStorage', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for the occupied storage size (in bytes).',
'description' => 'Aggregated stats for the occupied storage size by files (in bytes).',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('files', [
->addRule('tagsStorage', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for the occupied storage size by tags (in bytes).',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('filesCount', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of files.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('bucketsCount', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of buckets.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('bucketsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for buckets created.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('bucketsRead', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for buckets read.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('bucketsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for buckets updated.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('bucketsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for buckets deleted.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('filesCreate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files created.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('filesRead', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files read.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('filesUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files updated.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
->addRule('filesDelete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for files deleted.',
'default' => [],
'example' => new \stdClass,
'array' => true
])
;
}

View file

@ -38,10 +38,10 @@ class StorageConsoleClientTest extends Scope
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertEquals(count($response['body']), 3);
$this->assertEquals(count($response['body']), 13);
$this->assertEquals($response['body']['range'], '24h');
$this->assertIsArray($response['body']['storage']);
$this->assertIsArray($response['body']['files']);
$this->assertIsArray($response['body']['filesStorage']);
$this->assertIsArray($response['body']['filesCount']);
}
public function testGetStorageBucketUsage()
@ -93,12 +93,13 @@ class StorageConsoleClientTest extends Scope
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertEquals(count($response['body']), 6);
$this->assertEquals(count($response['body']), 7);
$this->assertEquals($response['body']['range'], '24h');
$this->assertIsArray($response['body']['filesCount']);
$this->assertIsArray($response['body']['filesCreate']);
$this->assertIsArray($response['body']['filesRead']);
$this->assertIsArray($response['body']['filesUpdate']);
$this->assertIsArray($response['body']['filesDelete']);
$this->assertIsArray($response['body']['filesStorage']);
}
}