diff --git a/CHANGES.md b/CHANGES.md index 070c7ed402..cd7a5c6c15 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,15 @@ - Updated UI with these changes - Updated event names from `function.tags.*` to `function.deployments.*` +# Version 0.12.3 + +## Bugs +- Fix update membership roles (#2799) +- Fix migration to 0.12.x to populate search fields (#2799) + +## Security +- Fix URL schema Validation to only allow http/https (#2801) + # Version 0.12.2 ## Bugs diff --git a/README-CN.md b/README-CN.md index c815eaedd8..ac8a10864d 100644 --- a/README-CN.md +++ b/README-CN.md @@ -59,7 +59,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:0.12.2 + appwrite/appwrite:0.12.3 ``` ### Windows @@ -71,7 +71,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:0.12.2 + appwrite/appwrite:0.12.3 ``` #### PowerShell @@ -81,7 +81,7 @@ docker run -it --rm , --volume /var/run/docker.sock:/var/run/docker.sock , --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw , --entrypoint="install" , - appwrite/appwrite:0.12.2 + appwrite/appwrite:0.12.3 ``` 运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。 diff --git a/README.md b/README.md index c2e802bacb..93fc52cd45 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:0.12.2 + appwrite/appwrite:0.12.3 ``` ### Windows @@ -74,7 +74,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:0.12.2 + appwrite/appwrite:0.12.3 ``` #### PowerShell @@ -84,7 +84,7 @@ docker run -it --rm , --volume /var/run/docker.sock:/var/run/docker.sock , --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw , --entrypoint="install" , - appwrite/appwrite:0.12.2 + appwrite/appwrite:0.12.3 ``` Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after installation completes. diff --git a/app/config/platforms.php b/app/config/platforms.php index d91c827d44..71ce9043c5 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -205,6 +205,24 @@ return [ 'gitUserName' => 'appwrite', 'gitBranch' => 'master', ], + [ + 'key' => 'node-cli', + 'name' => 'Command Line', + 'version' => '0.13.0', + 'url' => 'https://github.com/appwrite/sdk-for-node-cli', + 'package' => 'https://www.npmjs.com/package/appwrite-cli', + 'enabled' => true, + 'beta' => true, + 'dev' => false, + 'hidden' => false, + 'family' => APP_PLATFORM_CONSOLE, + 'prism' => 'bash', + 'source' => \realpath(__DIR__ . '/../sdks/server-cli'), + 'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git', + 'gitRepoName' => 'sdk-for-node-cli', + 'gitUserName' => 'appwrite', + 'gitBranch' => 'master', + ], [ 'key' => 'deno', 'name' => 'Deno', diff --git a/app/config/providers.php b/app/config/providers.php index 9940c185a3..1691f814bd 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -84,7 +84,7 @@ return [ // Ordered by ABC. 'github' => [ 'name' => 'GitHub', 'developers' => 'https://developer.github.com/', - 'icon' => 'icon-github-circled', + 'icon' => 'icon-github', 'enabled' => true, 'sandbox' => false, 'form' => false, diff --git a/app/config/storage/logos.php b/app/config/storage/logos.php index 39d8a40fa6..8816611edc 100644 --- a/app/config/storage/logos.php +++ b/app/config/storage/logos.php @@ -15,30 +15,30 @@ return [ // Based on this list @see http://stackoverflow.com/a/4212908/2299554 'video/x-ms-wmv' => __DIR__.'/logos/video.png', // // Microsoft Word - 'application/msword' => __DIR__.'/logos/word.png', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/logos/word.png', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/logos/word.png', - 'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/logos/word.png', + // 'application/msword' => __DIR__.'/logos/word.png', + // 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/logos/word.png', + // 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/logos/word.png', + // 'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/logos/word.png', // // Microsoft Excel - 'application/vnd.ms-excel' => __DIR__.'/logos/excel.png', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/logos/excel.png', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/logos/excel.png', - 'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/logos/excel.png', - 'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/logos/excel.png', - 'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/logos/excel.png', - 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/logos/excel.png', + // 'application/vnd.ms-excel' => __DIR__.'/logos/excel.png', + // 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/logos/excel.png', + // 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/logos/excel.png', + // 'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/logos/excel.png', + // 'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/logos/excel.png', + // 'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/logos/excel.png', + // 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/logos/excel.png', // // Microsoft Power Point - 'application/vnd.ms-powerpoint' => __DIR__.'/logos/ppt.png', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/logos/ppt.png', - 'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/logos/ppt.png', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/logos/ppt.png', - 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/logos/ppt.png', - 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/logos/ppt.png', - 'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/logos/ppt.png', - 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/logos/ppt.png', + // 'application/vnd.ms-powerpoint' => __DIR__.'/logos/ppt.png', + // 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/logos/ppt.png', + // 'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/logos/ppt.png', + // 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/logos/ppt.png', + // 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/logos/ppt.png', + // 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/logos/ppt.png', + // 'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/logos/ppt.png', + // 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/logos/ppt.png', // Adobe PDF - 'application/pdf' => __DIR__.'/logos/pdf.png', + // 'application/pdf' => __DIR__.'/logos/pdf.png', ]; \ No newline at end of file diff --git a/app/config/storage/logos/excel.png b/app/config/storage/logos/excel.png deleted file mode 100644 index ebf1a7cd99..0000000000 Binary files a/app/config/storage/logos/excel.png and /dev/null differ diff --git a/app/config/storage/logos/none.png b/app/config/storage/logos/none.png index 003ae49b53..b068cf5f71 100644 Binary files a/app/config/storage/logos/none.png and b/app/config/storage/logos/none.png differ diff --git a/app/config/storage/logos/pdf.png b/app/config/storage/logos/pdf.png deleted file mode 100644 index 8bbc791d18..0000000000 Binary files a/app/config/storage/logos/pdf.png and /dev/null differ diff --git a/app/config/storage/logos/ppt.png b/app/config/storage/logos/ppt.png deleted file mode 100644 index 4db13644d3..0000000000 Binary files a/app/config/storage/logos/ppt.png and /dev/null differ diff --git a/app/config/storage/logos/video.png b/app/config/storage/logos/video.png index 8cb7519f1b..83be90388a 100644 Binary files a/app/config/storage/logos/video.png and b/app/config/storage/logos/video.png differ diff --git a/app/config/storage/logos/word.png b/app/config/storage/logos/word.png deleted file mode 100644 index 4b13f18f91..0000000000 Binary files a/app/config/storage/logos/word.png and /dev/null differ diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 6e18a6067f..a6c4109718 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -99,8 +99,6 @@ App::post('/v1/storage/buckets') ]); } - $dbForProject->createCollection('bucket_' . $bucketId, $attributes, $indexes); - $bucket = $dbForProject->createDocument('buckets', new Document([ '$id' => $bucketId, '$collection' => 'buckets', @@ -117,6 +115,10 @@ App::post('/v1/storage/buckets') '$write' => $write ?? [], 'search' => implode(' ', [$bucketId, $name]), ])); + + $bucket = $dbForProject->getDocument('buckets', $bucketId); + + $dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes); } catch (Duplicate $th) { throw new Exception('Bucket already exists', 409, Exception::STORAGE_BUCKET_ALREADY_EXISTS); } @@ -483,11 +485,11 @@ App::post('/v1/storage/buckets/:bucketId/files') $path = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucketId, $fileId) { - return $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId) { + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $file = $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } $metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)]; @@ -579,11 +581,11 @@ App::post('/v1/storage/buckets/:bucketId/files') 'metadata' => $metadata, ]); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucketId, $doc) { - return $dbForProject->createDocument('bucket_' . $bucketId, $doc); + $file = Authorization::skip(function () use ($dbForProject, $bucket, $doc) { + return $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); }); } else { - $file = $dbForProject->createDocument('bucket_' . $bucketId, $doc); + $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); } } else { $file = $file @@ -601,11 +603,11 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('chunksUploaded', $chunksUploaded); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucketId, $fileId, $file) { - return $dbForProject->updateDocument('bucket_' . $bucketId, $fileId, $file); + $file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId, $file) { + return $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); }); } else { - $file = $dbForProject->updateDocument('bucket_' . $bucketId, $fileId, $file); + $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } } @@ -649,11 +651,11 @@ App::post('/v1/storage/buckets/:bucketId/files') 'metadata' => $metadata, ]); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucketId, $doc) { - return $dbForProject->createDocument('bucket_' . $bucketId, $doc); + $file = Authorization::skip(function () use ($dbForProject, $bucket, $doc) { + return $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); }); } else { - $file = $dbForProject->createDocument('bucket_' . $bucketId, $doc); + $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); } } else { $file = $file @@ -661,11 +663,11 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('metadata', $metadata); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucketId, $fileId, $file) { - return $dbForProject->updateDocument('bucket_' . $bucketId, $fileId, $file); + $file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId, $file) { + return $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); }); } else { - $file = $dbForProject->updateDocument('bucket_' . $bucketId, $fileId, $file); + $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } } } catch (StructureException $exception) { @@ -737,10 +739,10 @@ App::get('/v1/storage/buckets/:bucketId/files') if (!empty($cursor)) { if ($bucket->getAttribute('permission') === 'bucket') { $cursorFile = Authorization::skip(function () use ($dbForProject, $bucket, $cursor) { - return $dbForProject->getDocument('bucket_' . $bucket->getId(), $cursor); + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor); }); } else { - $cursorFile = $dbForProject->getDocument('bucket_' . $bucket->getId(), $cursor); + $cursorFile = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor); } if ($cursorFile->isEmpty()) { @@ -755,11 +757,11 @@ App::get('/v1/storage/buckets/:bucketId/files') } if ($bucket->getAttribute('permission') === 'bucket') { - $files = Authorization::skip(function () use ($dbForProject, $bucketId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderType) { - return $dbForProject->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection); + $files = Authorization::skip(function () use ($dbForProject, $bucket, $queries, $limit, $offset, $cursor, $cursorDirection, $orderType) { + return $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection); }); } else { - $files = $dbForProject->find('bucket_' . $bucketId, $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection); + $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection); } $usage @@ -769,7 +771,7 @@ App::get('/v1/storage/buckets/:bucketId/files') $response->dynamic(new Document([ 'files' => $files, - 'sum' => $dbForProject->count('bucket_' . $bucketId, $queries, APP_LIMIT_COUNT), + 'sum' => $dbForProject->count('bucket_' . $bucket->getInternalId(), $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FILE_LIST); }); @@ -813,11 +815,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') } if ($bucket->getAttribute('permission') === 'bucket') { - $file = Authorization::skip(function () use ($dbForProject, $bucketId, $fileId) { - return $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId) { + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $file = $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { @@ -902,11 +904,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') if ($bucket->getAttribute('permission') === 'bucket') { // skip authorization - $file = Authorization::skip(function () use ($dbForProject, $bucketId, $fileId) { - return $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId) { + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $file = $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { @@ -1061,11 +1063,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') } if ($bucket->getAttribute('permission') === 'bucket') { - $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucketId) { - return $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) { + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $file = $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { @@ -1206,11 +1208,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') } if ($bucket->getAttribute('permission') === 'bucket') { - $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucketId) { - return $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) { + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $file = $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } $mimes = Config::getParam('storage-mimes'); @@ -1387,11 +1389,11 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') } if ($bucket->getAttribute('permission') === 'bucket') { - $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucketId) { - return $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) { + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $file = $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { @@ -1399,14 +1401,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') } if ($bucket->getAttribute('permission') === 'bucket') { - $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucketId, $file, $read, $write) { - return $dbForProject->updateDocument('bucket_' . $bucketId, $fileId, $file + $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket, $file, $read, $write) { + return $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file ->setAttribute('$read', $read) ->setAttribute('$write', $write) ); }); } else { - $file = $dbForProject->updateDocument('bucket_' . $bucketId, $fileId, $file + $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file ->setAttribute('$read', $read) ->setAttribute('$write', $write) ); @@ -1475,11 +1477,11 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') } if ($bucket->getAttribute('permission') === 'bucket') { - $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucketId) { - return $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) { + return $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $file = $dbForProject->getDocument('bucket_' . $bucketId, $fileId); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) { @@ -1503,11 +1505,11 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') $deviceLocal->delete($cacheDir, true); if ($bucket->getAttribute('permission') === 'bucket') { - $deleted = Authorization::skip(function () use ($dbForProject, $fileId, $bucketId) { - return $dbForProject->deleteDocument('bucket_' . $bucketId, $fileId); + $deleted = Authorization::skip(function () use ($dbForProject, $fileId, $bucket) { + return $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); }); } else { - $deleted = $dbForProject->deleteDocument('bucket_' . $bucketId, $fileId); + $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); } if (!$deleted) { throw new Exception('Failed to remove file from DB', 500, Exception::GENERAL_SERVER_ERROR); diff --git a/app/init.php b/app/init.php index 885f7c4834..82a2665734 100644 --- a/app/init.php +++ b/app/init.php @@ -71,7 +71,7 @@ const APP_LIMIT_ENCRYPTION = 20000000; //20MB const APP_LIMIT_COMPRESSION = 20000000; //20MB const APP_LIMIT_PREVIEW = 10000000; //10MB file size limit for preview endpoint const APP_CACHE_BUSTER = 201; -const APP_VERSION_STABLE = '0.13.0'; +const APP_VERSION_STABLE = '0.12.3'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; const APP_DATABASE_ATTRIBUTE_IP = 'ip'; diff --git a/app/tasks/usage.php b/app/tasks/usage.php index 6216d08cb5..0ee28f5cb6 100644 --- a/app/tasks/usage.php +++ b/app/tasks/usage.php @@ -236,6 +236,8 @@ $cli $max = 10; $sleep = 1; + $db = null; + $redis = null; do { // connect to db try { $attempts++; @@ -275,47 +277,47 @@ $cli * @var InfluxDB\Client $client */ $client = $register->get('influxdb'); - if ($client) { - $attempts = 0; - $max = 10; - $sleep = 1; + $attempts = 0; + $max = 10; + $sleep = 1; - do { // check if telegraf database is ready - try { - $attempts++; - $database = $client->selectDB('telegraf'); - if(in_array('telegraf', $client->listDatabases())) { - break; // leave the do-while if successful - } - } catch (\Throwable $th) { - Console::warning("InfluxDB not ready. Retrying connection ({$attempts})..."); - if ($attempts >= $max) { - throw new \Exception('InfluxDB database not ready yet'); - } - sleep($sleep); + do { // check if telegraf database is ready + try { + $attempts++; + $database = $client->selectDB('telegraf'); + if(in_array('telegraf', $client->listDatabases())) { + break; // leave the do-while if successful } - } while ($attempts < $max); + } catch (\Throwable $th) { + Console::warning("InfluxDB not ready. Retrying connection ({$attempts})..."); + if ($attempts >= $max) { + throw new \Exception('InfluxDB database not ready yet'); + } + sleep($sleep); + } + } while ($attempts < $max); - // sync data - foreach ($globalMetrics as $metric => $options) { //for each metrics - foreach ($periods as $period) { // aggregate data for each period - $start = DateTime::createFromFormat('U', \strtotime($period['startTime']))->format(DateTime::RFC3339); - if (!empty($latestTime[$metric][$period['key']])) { - $start = DateTime::createFromFormat('U', $latestTime[$metric][$period['key']])->format(DateTime::RFC3339); - } - $end = DateTime::createFromFormat('U', \strtotime('now'))->format(DateTime::RFC3339); + // sync data + foreach ($globalMetrics as $metric => $options) { //for each metrics + foreach ($periods as $period) { // aggregate data for each period + $start = DateTime::createFromFormat('U', \strtotime($period['startTime']))->format(DateTime::RFC3339); + if (!empty($latestTime[$metric][$period['key']])) { + $start = DateTime::createFromFormat('U', $latestTime[$metric][$period['key']])->format(DateTime::RFC3339); + } + $end = DateTime::createFromFormat('U', \strtotime('now'))->format(DateTime::RFC3339); - $table = $options['table']; //Which influxdb table to query for this metric - $groupBy = empty($options['groupBy']) ? '' : ', "' . $options['groupBy'] . '"'; //Some sub level metrics may be grouped by other tags like collectionId, bucketId, etc + $table = $options['table']; //Which influxdb table to query for this metric + $groupBy = empty($options['groupBy']) ? '' : ', "' . $options['groupBy'] . '"'; //Some sub level metrics may be grouped by other tags like collectionId, bucketId, etc - $filters = $options['filters'] ?? []; // Some metrics might have additional filters, like function's status - if (!empty($filters)) { - $filters = ' AND ' . implode(' AND ', array_map(fn ($filter, $value) => "\"{$filter}\"='{$value}'", array_keys($filters), array_values($filters))); - } else { - $filters = ''; - } + $filters = $options['filters'] ?? []; // Some metrics might have additional filters, like function's status + if (!empty($filters)) { + $filters = ' AND ' . implode(' AND ', array_map(fn ($filter, $value) => "\"{$filter}\"='{$value}'", array_keys($filters), array_values($filters))); + } else { + $filters = ''; + } - $query = "SELECT sum(value) AS \"value\" FROM \"{$table}\" WHERE \"time\" > '{$start}' AND \"time\" < '{$end}' AND \"metric_type\"='counter' {$filters} GROUP BY time({$period['key']}), \"projectId\" {$groupBy} FILL(null)"; + $query = "SELECT sum(value) AS \"value\" FROM \"{$table}\" WHERE \"time\" > '{$start}' AND \"time\" < '{$end}' AND \"metric_type\"='counter' {$filters} GROUP BY time({$period['key']}), \"projectId\" {$groupBy} FILL(null)"; + try { $result = $database->query($query); $points = $result->getPoints(); @@ -362,124 +364,305 @@ $cli } } } + } catch (\Exception $e) { + Console::warning("Failed to Query: {$e->getMessage()}"); } } } + if ($iterations % 30 != 0) { // Aggregate aggregate number of objects in database only after 15 minutes + $iterations++; + $loopTook = microtime(true) - $loopStart; + $now = date('d-m-Y H:i:s', time()); + Console::info("[{$now}] Aggregation took {$loopTook} seconds"); + return; + } + /** * Aggregate MariaDB every 15 minutes * Some of the queries here might contain full-table scans. */ - if ($iterations % 30 === 0) { // Every 15 minutes aggregate number of objects in database + $now = date('d-m-Y H:i:s', time()); + Console::info("[{$now}] Aggregating database counters."); + + $latestProject = null; + do { // Loop over all the projects + $attempts = 0; + $max = 10; + $sleep = 1; - $latestProject = null; - - do { // Loop over all the projects - $attempts = 0; - $max = 10; - $sleep = 1; - - do { // list projects - try { - $attempts++; - $projects = $dbForConsole->find('projects', [], 100, cursor: $latestProject); - break; // leave the do-while if successful - } catch (\Exception $e) { - Console::warning("Console DB not ready yet. Retrying ({$attempts})..."); - if ($attempts >= $max) { - throw new \Exception('Failed access console db: ' . $e->getMessage()); - } - sleep($sleep); + do { // list projects + try { + $attempts++; + $projects = $dbForConsole->find('projects', [], 100, cursor: $latestProject); + break; // leave the do-while if successful + } catch (\Exception $e) { + Console::warning("Console DB not ready yet. Retrying ({$attempts})..."); + if ($attempts >= $max) { + throw new \Exception('Failed access console db: ' . $e->getMessage()); } - } while ($attempts < $max); + sleep($sleep); + } + } while ($attempts < $max); - if (empty($projects)) { - continue; + if (empty($projects)) { + continue; + } + + $latestProject = $projects[array_key_last($projects)]; + + foreach ($projects as $project) { + $projectId = $project->getId(); + + // Get total storage + $dbForProject->setNamespace('_project_' . $projectId); + $storageTotal = $dbForProject->sum('tags', 'size'); + + $time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes + $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.tags.total', + 'value' => $storageTotal, + 'type' => 1, + ])); + } else { + $dbForProject->updateDocument( + 'stats', + $document->getId(), + $document->setAttribute('value', $storageTotal) + ); } - $latestProject = $projects[array_key_last($projects)]; + $time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day + $id = \md5($time . '_1d_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' => '1d', + 'time' => $time, + 'metric' => 'storage.tags.total', + 'value' => $storageTotal, + 'type' => 1, + ])); + } else { + $dbForProject->updateDocument( + 'stats', + $document->getId(), + $document->setAttribute('value', $storageTotal) + ); + } - foreach ($projects as $project) { - $projectId = $project->getId(); - - // Get total storage - $dbForProject->setNamespace('_project_' . $projectId); - $storageTotal = $dbForProject->sum('deployments', 'size'); - - $time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes - $id = \md5($time . '_30m_storage.deployments.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.deployments.total', - 'value' => $storageTotal, - 'type' => 1, - ])); - } else { - $dbForProject->updateDocument( - 'stats', - $document->getId(), - $document->setAttribute('value', $storageTotal) - ); - } - - $time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day - $id = \md5($time . '_1d_storage.deployments.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' => '1d', - 'time' => $time, - 'metric' => 'storage.deployments.total', - 'value' => $storageTotal, - 'type' => 1, - ])); - } else { - $dbForProject->updateDocument( - 'stats', - $document->getId(), - $document->setAttribute('value', $storageTotal) - ); - } - - $collections = [ - 'users' => [ - 'namespace' => '', - ], - 'collections' => [ - 'metricPrefix' => 'database', - 'namespace' => '', - 'subCollections' => [ // Some collections, like collections and later buckets have child collections that need counting - 'documents' => [ - 'namespace' => '', - ], + $collections = [ + 'users' => [ + 'namespace' => '', + ], + 'collections' => [ + 'metricPrefix' => 'database', + 'namespace' => '', + 'subCollections' => [ // Some collections, like collections and later buckets have child collections that need counting + 'documents' => [ + 'collectionPrefix' => 'collection_', + 'namespace' => '', ], ], - 'buckets' => [ - 'metricPrefix' => 'storage', - 'namespace' => '', - 'subCollections' => [ - 'files' => [ - 'namespace' => '', - 'collectionPrefix' => 'bucket_', - 'sum' => [ - 'field' => 'sizeOriginal' - ] - ], - ] + ], + 'buckets' => [ + 'metricPrefix' => 'storage', + 'namespace' => '', + 'subCollections' => [ + 'files' => [ + 'namespace' => '', + 'collectionPrefix' => 'bucket_', + 'sum' => [ + 'field' => 'sizeOriginal' + ] + ], ] - ]; + ] + ]; - foreach ($collections as $collection => $options) { - try { + foreach ($collections as $collection => $options) { + try { + $dbForProject->setNamespace("_project_{$projectId}"); + $count = $dbForProject->count($collection); + $metricPrefix = $options['metricPrefix'] ?? ''; + $metric = empty($metricPrefix) ? "{$collection}.count" : "{$metricPrefix}.{$collection}.count"; + + $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) + ); + } + + $subCollections = $options['subCollections'] ?? []; + + if (empty($subCollections)) { + continue; + } + + $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}"); - $count = $dbForProject->count($collection); - $metricPrefix = $options['metricPrefix'] ?? ''; - $metric = empty($metricPrefix) ? "{$collection}.count" : "{$metricPrefix}.{$collection}.count"; + $parents = $dbForProject->find($collection, [], 100, cursor: $latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections + + if (empty($parents)) { + continue; + } + + $latestParent = $parents[array_key_last($parents)]; + + foreach ($parents as $parent) { + foreach ($subCollections as $subCollection => $subOptions) { // Sub collection counts, like database.collections.collectionId.documents.count + $dbForProject->setNamespace("_project_{$projectId}"); + $count = $dbForProject->count(($subOptions['collectionPrefix'] ?? '') . $parent->getId()); + + $subCollectionCounts[$subCollection] = ($subCollectionCounts[$subCollection] ?? 0) + $count; // Project level counts for sub collections like database.documents.count + + $dbForProject->setNamespace("_project_{$projectId}"); + + $metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.count" : "{$metricPrefix}.{$collection}.{$parent->getId()}.{$subCollection}.count"; + $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) + ); + } + + // check if sum calculation is required + $sum = $subOptions['sum'] ?? []; + if(empty($sum)) { + continue; + } + + $dbForProject->setNamespace("_project_{$projectId}"); + $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}"); + + $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)); + + /** + * Inserting project level counts for sub collections like database.documents.count + */ + foreach ($subCollectionCounts as $subCollection => $count) { + $dbForProject->setNamespace("_project_{$projectId}"); + + $metric = empty($metricPrefix) ? "{$subCollection}.count" : "{$metricPrefix}.{$subCollection}.count"; $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 @@ -520,227 +703,56 @@ $cli $document->setAttribute('value', $count) ); } - - $subCollections = $options['subCollections'] ?? []; - - if (empty($subCollections)) { - continue; - } - - $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}"); - $parents = $dbForProject->find($collection, [], 100, cursor: $latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections - - if (empty($parents)) { - continue; - } - - $latestParent = $parents[array_key_last($parents)]; - - foreach ($parents as $parent) { - foreach ($subCollections as $subCollection => $subOptions) { // Sub collection counts, like database.collections.collectionId.documents.count - $dbForProject->setNamespace("_project_{$projectId}"); - $count = $dbForProject->count(($subOptions['collectionPrefix'] ?? '') . $parent->getId()); - - $subCollectionCounts[$subCollection] = ($subCollectionCounts[$subCollection] ?? 0) + $count; // Project level counts for sub collections like database.documents.count - - $dbForProject->setNamespace("_project_{$projectId}"); - - $metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.count" : "{$metricPrefix}.{$collection}.{$parent->getId()}.{$subCollection}.count"; - $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) - ); - } - - // check if sum calculation is required - $sum = $subOptions['sum'] ?? []; - if(empty($sum)) { - continue; - } - - $dbForProject->setNamespace("_project_{$projectId}"); - $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}"); - - $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)); - - /** - * Inserting project level counts for sub collections like database.documents.count - */ - foreach ($subCollectionCounts as $subCollection => $count) { - $dbForProject->setNamespace("_project_{$projectId}"); - - $metric = empty($metricPrefix) ? "{$subCollection}.count" : "{$metricPrefix}.{$subCollection}.count"; - - $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) - ); - } - } - - /** - * Inserting project level sums for sub collections like storage.total - */ - foreach ($subCollectionTotals as $subCollection => $count) { - $dbForProject->setNamespace("_project_{$projectId}"); - - $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()}"); } + + /** + * Inserting project level sums for sub collections like storage.total + */ + foreach ($subCollectionTotals as $subCollection => $count) { + $dbForProject->setNamespace("_project_{$projectId}"); + + $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()}"); } } - } while (!empty($projects)); - } + } + } while (!empty($projects)); $iterations++; $loopTook = microtime(true) - $loopStart; diff --git a/app/views/console/account/index.phtml b/app/views/console/account/index.phtml index cf9f11785f..71f2f83645 100644 --- a/app/views/console/account/index.phtml +++ b/app/views/console/account/index.phtml @@ -251,8 +251,10 @@
- - + + + +
@@ -317,7 +319,10 @@ - + + + + Unknown diff --git a/app/views/console/comps/footer.phtml b/app/views/console/comps/footer.phtml index 065dabb14b..3281849316 100644 --- a/app/views/console/comps/footer.phtml +++ b/app/views/console/comps/footer.phtml @@ -10,7 +10,7 @@ $version = $this->getParam('version', '') . '.' . APP_CACHE_BUSTER; data-analytics-event="click" data-analytics-category="console/footer" data-analytics-label="GitHub Link" - href="https://github.com/appwrite/appwrite" target="_blank" rel="noopener"> GitHub + href="https://github.com/appwrite/appwrite" target="_blank" rel="noopener"> GitHub
  • -
  • + @@ -222,3 +222,38 @@ + +
    +
    +

    + Uploading files + +

    + + +
    +
    +
      + +
    +
    +
    diff --git a/app/views/console/comps/upload-modal.phtml b/app/views/console/comps/upload-modal.phtml new file mode 100644 index 0000000000..f630e3e396 --- /dev/null +++ b/app/views/console/comps/upload-modal.phtml @@ -0,0 +1,6 @@ +
    + + +
    \ No newline at end of file diff --git a/app/views/console/functions/function.phtml b/app/views/console/functions/function.phtml index f810741507..6910d2dcf9 100644 --- a/app/views/console/functions/function.phtml +++ b/app/views/console/functions/function.phtml @@ -541,7 +541,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
    - +
    @@ -554,7 +554,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
    - +
    diff --git a/app/views/console/index.phtml b/app/views/console/index.phtml index bbf44a6737..c60a1830d7 100644 --- a/app/views/console/index.phtml +++ b/app/views/console/index.phtml @@ -116,7 +116,7 @@ $home = $this->getParam('home', ''); data-analytics data-analytics-type="click" data-analytics-category="console/home" - data-analytics-label="GitHub Link">
    + data-analytics-label="GitHub Link">
    diff --git a/app/views/console/settings/index.phtml b/app/views/console/settings/index.phtml index e4ad543b2b..426281f013 100644 --- a/app/views/console/settings/index.phtml +++ b/app/views/console/settings/index.phtml @@ -241,11 +241,6 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);

    - -
    - -
    Enabled
    -
    Disabled
    @@ -548,7 +543,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
         Pending Approval
    - + diff --git a/app/views/console/storage/bucket.phtml b/app/views/console/storage/bucket.phtml index 06c7787f24..dc2723c0f9 100644 --- a/app/views/console/storage/bucket.phtml +++ b/app/views/console/storage/bucket.phtml @@ -81,7 +81,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
    files found
    - +
    @@ -89,128 +89,143 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0); + - +
    Type Size Created
    - + - +
    + +
    -
    - - -
    - incomplete + +
    +
    +
      +
    • Update
    • +
    • Delete
    • +
    +
    +
    -
    getParam('fileLimitHuman', 0); 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"> + x-data + @submit.prevent="$store.uploader.uploadFile($event.target)" + > diff --git a/app/views/console/storage/index.phtml b/app/views/console/storage/index.phtml index 654b9318ab..a141b3ffe9 100644 --- a/app/views/console/storage/index.phtml +++ b/app/views/console/storage/index.phtml @@ -35,7 +35,6 @@
  •  
    -
  • diff --git a/app/views/console/users/index.phtml b/app/views/console/users/index.phtml index db50aee38c..319ac52463 100644 --- a/app/views/console/users/index.phtml +++ b/app/views/console/users/index.phtml @@ -79,13 +79,13 @@ $smtpEnabled = $this->getParam('smtpEnabled', false); - + Unknown Anonymous User - + @@ -245,7 +245,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false); Collection Avatar - + @@ -406,7 +406,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false); Email/Password Logo - escape($name); ?> soon + escape($name); ?> soon

    diff --git a/app/views/console/users/user.phtml b/app/views/console/users/user.phtml index d29afd3f10..e769ef4bb1 100644 --- a/app/views/console/users/user.phtml +++ b/app/views/console/users/user.phtml @@ -295,8 +295,8 @@ data-param-user-id="{{router.params.id}}" data-event="load,users.update"> -