1
0
Fork 0
mirror of synced 2024-06-01 10:29:48 +12:00

Merge branch 'feat-new-document-usage-log' into feat-db-refactor-ui-fixes

This commit is contained in:
Matej Baco 2021-09-27 07:45:41 +02:00
commit 8e7039ab92
11 changed files with 370 additions and 80 deletions

View file

@ -1635,6 +1635,109 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
$response->dynamic($document, Response::MODEL_DOCUMENT);
});
App::get('/v1/database/collections/:collectionId/documents/:documentId/logs')
->desc('List Document Logs')
->groups(['api', 'database'])
->label('scope', 'documents.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'database')
->label('sdk.method', 'listDocumentLogs')
->label('sdk.description', '/docs/references/database/get-document-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('collectionId', '', new UID(), 'Collection unique ID.')
->param('documentId', null, new UID(), 'Document unique ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForExternal')
->inject('locale')
->inject('geodb')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $locale, $geodb) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
$collection = $dbForInternal->getDocument('collections', $collectionId);
if ($collection->isEmpty()) {
throw new Exception('Collection not found', 404);
}
$document = $dbForExternal->getDocument($collectionId, $documentId);
if ($document->isEmpty()) {
throw new Exception('No document found', 404);
}
$audit = new Audit($dbForInternal);
$logs = $audit->getLogsByResource('database/document/'.$document->getId());
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
$dd = new DeviceDetector($log['userAgent']);
$dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$dd->parse();
$os = $dd->getOs();
$osCode = (isset($os['short_name'])) ? $os['short_name'] : '';
$osName = (isset($os['name'])) ? $os['name'] : '';
$osVersion = (isset($os['version'])) ? $os['version'] : '';
$client = $dd->getClient();
$clientType = (isset($client['type'])) ? $client['type'] : '';
$clientCode = (isset($client['short_name'])) ? $client['short_name'] : '';
$clientName = (isset($client['name'])) ? $client['name'] : '';
$clientVersion = (isset($client['version'])) ? $client['version'] : '';
$clientEngine = (isset($client['engine'])) ? $client['engine'] : '';
$clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : '';
$output[$i] = new Document([
'event' => $log['event'],
'userId' => $log['userId'],
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'mode' => $log['data']['mode'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $osCode,
'osName' => $osName,
'osVersion' => $osVersion,
'clientType' => $clientType,
'clientCode' => $clientCode,
'clientName' => $clientName,
'clientVersion' => $clientVersion,
'clientEngine' => $clientEngine,
'clientEngineVersion' => $clientEngineVersion,
'deviceName' => $dd->getDeviceName(),
'deviceBrand' => $dd->getBrandName(),
'deviceModel' => $dd->getModel(),
]);
$record = $geodb->get($log['ip']);
if ($record) {
$output[$i]['countryCode'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
$output[$i]['countryName'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
} else {
$output[$i]['countryCode'] = '--';
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
}
}
$response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST);
});
App::patch('/v1/database/collections/:collectionId/documents/:documentId')
->desc('Update Document')
->groups(['api', 'database'])

View file

@ -218,6 +218,10 @@ App::get('/console/database/collection')
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'database.listCollectionLogs')
->setParam('params', [
'collection-id' => '{{router.params.id}}',
])
;
$page = new View(__DIR__.'/../../views/console/database/collection.phtml');
@ -245,14 +249,45 @@ App::get('/console/database/document')
->action(function ($collection, $layout) {
/** @var Utopia\View $layout */
$logs = new View(__DIR__.'/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'database.listDocumentLogs')
->setParam('params', [
'collection-id' => '{{router.params.collection}}',
'document-id' => '{{router.params.id}}',
])
;
$page = new View(__DIR__.'/../../views/console/database/document.phtml');
$searchFiles = new View(__DIR__.'/../../views/console/database/search/files.phtml');
$searchDocuments = new View(__DIR__.'/../../views/console/database/search/documents.phtml');
$page
->setParam('new', false)
->setParam('collection', $collection)
->setParam('searchFiles', $searchFiles)
->setParam('searchDocuments', $searchDocuments)
->setParam('logs', $logs)
;
$layout
->setParam('title', APP_NAME.' - Database Document')
->setParam('body', $page);
});
App::get('/console/database/document/new')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('collection', '', new UID(), 'Collection unique ID.')
->inject('layout')
->action(function ($collection, $layout) {
/** @var Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/database/document.phtml');
$page
->setParam('new', true)
->setParam('collection', $collection)
->setParam('logs', new View())
;
$layout

View file

@ -1,20 +1,23 @@
<?php
$interval = floor((int)$this->getParam('interval', 0) / 86400);
$method = $this->getParam('method', '');
$params = $this->getParam('params', []);
?>
<div
data-service="<?php echo $method; ?>"
data-service="database.listCollectionLogs"
data-param-collection-id="{{router.params.id}}"
<?php foreach($params as $key => $value): ?>
data-param-<?php echo $key; ?>="<?php echo $value; ?>"
<?php endforeach; ?>
data-scope="sdk"
data-event="load"
data-name="project-collection-logs">
<div class="box margin-bottom">
<div data-ls-if="0 == {{project-collection-logs.logs.length}}">
data-name="logs">
<div data-ls-if="0 == {{logs.logs.length}}">
<div class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Logs Found</h3>
<p class="margin-bottom-no">Logs are retained for <?php echo $this->escape($interval); ?> days.</p>
</div>
<table class="vertical small" data-ls-if="0 != {{project-collection-logs.logs.length}}">
<thead>
<tr>
@ -47,32 +50,70 @@ $interval = floor((int)$this->getParam('interval', 0) / 86400);
</tbody>
</table>
</div>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="database.listCollectionLogs"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-collection-logs"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-sum="{{project-collection-logs.sum}}" class="margin-end-small round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<div data-ls-if="0 != {{logs.logs.length}}">
<div class="margin-bottom-small margin-top-negative text-align-end text-size-small text-fade">Showing logs from the last <?php echo $this->escape($interval); ?> days</div>
<form
data-service="database.listCollectionLogs"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-collection-logs"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-collection-logs.sum}}" class="margin-start-small round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div class="box margin-bottom">
<table class="vertical small">
<thead>
<tr>
<th width="120">Date</th>
<th width="180">By</th>
<th>Event</th>
<th width="110">Location</th>
<th width="90">IP</th>
</tr>
</thead>
<tbody data-ls-loop="logs.logs" data-ls-as="log" class="text-size-small">
<tr>
<td data-title="Date: "><span class="text-fade" data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="By: ">
<span data-ls-if="{{log.userName|escape}} !== '' && {{log.mode}} === ''"><i class="icon-user"></i>&nbsp; <a data-ls-attrs="href=/console/users/user?id={{log.userId}}&project={{router.params.project}}" data-ls-bind="{{log.userName}}"></a></span>
<span data-ls-if="{{log.userName|escape}} === '' && {{log.userEmail}} !== '' && {{log.mode}} !== 'key'"><i class="icon-user"></i>&nbsp; Unknown</span>
<span data-ls-if="{{log.userName|escape}} === '' && {{log.userEmail}} === '' && {{log.mode}} !== 'key'"><i class="icon-user"></i>&nbsp; Anonymous User</span>
<span data-ls-if="{{log.mode}} === 'admin'">
<img src="" data-ls-attrs="src={{log.userName|avatar}}" data-size="45" alt="User Avatar" class="avatar xxs inline margin-end-small" loading="lazy" width="30" height="30" /> <span data-ls-bind="{{log.userName}}"></span> <span class="text-fade text-size-xs">(Admin)</span>
</span>
<span data-ls-if="{{log.mode}} === 'key'"> <i class="icon-key"></i>&nbsp; API Key</span>
</td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.className='avatar xxs hide'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.countryName}}"></span>
</td>
<td data-title="IP: "><span data-ls-bind="{{log.ip}}"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="database.listCollectionLogs"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="logs"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-sum="{{logs.sum}}" class="margin-end-small round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<form
data-service="database.listCollectionLogs"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="logs"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{logs.sum}}" class="margin-start-small round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
</div>
</div>

View file

@ -117,7 +117,7 @@ $logs = $this->getParam('logs', null);
</form>
</div>
<a data-ls-if="0 < {{project-collection.attributes.length}}" data-ls-attrs="href=/console/database/document?collection={{router.params.id}}&project={{router.params.project}}&buster={{project-collection.dateUpdated}}" class="button">
<a data-ls-if="0 < {{project-collection.attributes.length}}" data-ls-attrs="href=/console/database/document/new?collection={{router.params.id}}&project={{router.params.project}}" class="button">
Add Document
</a>
<a data-ls-if="!{{project-collection.attributes.length}}" data-ls-attrs="href=/console/database/collection/attributes?id={{router.params.id}}&project={{router.params.project}}" class="button">
@ -322,7 +322,7 @@ $logs = $this->getParam('logs', null);
<button class="new-index">Add Index</button>
</li>
<li data-state="/console/database/collection/activity?id={{router.params.id}}&project={{router.params.project}}">
<h2>Activity <span class="badge" data-ls-bind="{{project-collection-logs.logs.length}}"></span></h2>
<h2>Activity <span class="badge" data-ls-bind="{{logs.logs.length}}"></span></h2>
<?php echo $logs->render(); ?>
</li>
@ -517,16 +517,17 @@ $logs = $this->getParam('logs', null);
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label for="integer-min">Min</label>
<input id="integer-min" type="number" class="full-width" name="min" value="0" required autocomplete="off" data-cast-to="integer" />
<input id="integer-min" type="number" step="1" class="full-width" name="min" value="0" required autocomplete="off" data-cast-to="integer" />
</div>
<div class="col span-6 margin-bottom-small">
<label for="integer-max">Max</label>
<input id="integer-max" type="number" class="full-width" name="max" value="0" required autocomplete="off" data-cast-to="integer" />
<input id="integer-max" type="number" step="1" class="full-width" name="max" value="0" required autocomplete="off" data-cast-to="integer" />
</div>
</div>
<label for="integer-default">Default Value</label>
<input id="integer-default" name="xdefault" type="number" value="0" class="margin-bottom-large" autocomplete="off" data-cast-to="integer">
<input id="integer-default" name="xdefault" type="number" step="1" value="0" class="margin-bottom-large" autocomplete="off" data-cast-to="integer">
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
@ -540,26 +541,26 @@ $logs = $this->getParam('logs', null);
<h1>Add Float Attribute</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (float)"
data-service="database.createFloatAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (float)"
data-service="database.createFloatAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="float-attributeId">Attribute ID</label>
<input id="float-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="128" />
<label for="attributeId">Attribute ID</label>
<input type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="128" />
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -572,16 +573,16 @@ $logs = $this->getParam('logs', null);
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label for="float-min">Min</label>
<input id="float-min" type="number" class="full-width" name="min" value="0" step="any" required autocomplete="off" data-cast-to="float" />
<input id="float-min" type="number" class="full-width" name="min" value="0.00" step="0.01" required autocomplete="off" data-cast-to="float" />
</div>
<div class="col span-6 margin-bottom-small">
<label for="float-max">Max</label>
<input id="float-max" type="number" class="full-width" name="max" value="0" step="any" required autocomplete="off" data-cast-to="float" />
<input id="float-max" type="number" class="full-width" name="max" value="0.00" step="0.01" required autocomplete="off" data-cast-to="float" />
</div>
</div>
<label for="float-default">Default Value</label>
<input id="float-default" name="xdefault" type="number" value="0" step="any" class="margin-bottom-large" autocomplete="off" data-cast-to="float">
<input id="float-default" name="xdefault" type="number" value="0.00" step="0.01" class="margin-bottom-large" autocomplete="off" data-cast-to="float">
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>

View file

@ -1,3 +1,9 @@
<?php
$new = $this->getParam('new', false);
$logs = $this->getParam('logs', null);
?>
<div
data-service="database.getCollection"
data-param-collection-id="{{router.params.collection}}"
@ -40,9 +46,9 @@
<div class="zone xl margin-bottom-no">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/database/document?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
<h2>Overview</h2>
<h2 class="margin-bottom">Overview</h2>
<div class="row responsive margin-top-negative">
<div class="row responsive">
<div class="col span-8 margin-bottom">
<form
data-analytics
@ -62,12 +68,22 @@
data-failure-param-alert-classname="error">
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input data-ls-if="{{project-document.$id}}" type="hidden" name="documentId" data-ls-bind="{{project-document.$id}}" />
<label>&nbsp;</label>
<?php if(!$new): ?><input type="hidden" name="documentId" data-ls-bind="{{project-document.$id}}" /><?php endif; ?>
<div class="box">
<?php if($new): ?>
<label for="documentId">Document ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="database.getDocument"
required
maxlength="36"
name="documentId"
id="documentId" />
<?php endif; ?>
<fieldset name="data" data-cast-to="object">
<ul data-ls-loop="project-collection.attributes" data-ls-as="attribute">
<li>
@ -76,7 +92,21 @@
<div data-ls-if="{{attribute.required}}" class="text-size-xs text-danger text-fade">required</div>
<div data-ls-if="!{{attribute.required}}" class="text-size-xs text-fade">optional</div>
</label>
<textarea data-ls-attrs="placeholder={{attribute.default}},name={{attribute.key}},required={{attribute.required}}" data-ls-bind="{{project-document|documentAttribute}}"></textarea>
<div data-ls-if="!{{attribute.array}}" data-ls-template="template-{{attribute.type}}" data-type="script"></div>
<div data-ls-if="{{attribute.array}}" class="margin-bottom">
<div data-forms-clone="" data-label="Add Attribute" data-target="attributes-section" data-first="1">
<div class="row responsive thin margin-bottom-tiny">
<div class="col span-11 margin-bottom-small">
<div data-ls-template="template-{{attribute.type}}" data-type="script"></div>
</div>
<div class="col span-1 margin-bottom-small">
<button type="button" data-remove class="dark danger small round pull-end" style="margin-top: 10px;"><i class="icon-cancel"></i></button>
</div>
</div>
</div>
</div>
</li>
</ul>
</fieldset>
@ -146,10 +176,46 @@
</div>
</div>
</li>
<!-- <li data-ls-if="{{project-document.$id}}" data-state="/console/database/document/activity?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
<h2>Activity</h2>
</li> -->
<?php if(!$new): ?>
<li data-state="/console/database/document/activity?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
<h2>Activity <span class="badge" data-ls-bind="{{logs.logs.length}}"></span></h2>
<?php echo $logs->render(); ?>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</div>
</div>
<script type="text/html" id="template-string">
<textarea data-forms-text-resize data-forms-text-direction data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}"></textarea>
</script>
<script type="text/html" id="template-string-array">
<textarea data-forms-text-resize data-forms-text-direction data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}"></textarea>
</script>
<script type="text/html" id="template-integer">
<input type="number" step="1" data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" data-cast-to="integer" />
</script>
<script type="text/html" id="template-double">
<input type="number" step="0.01" data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" data-cast-to="float" />
</script>
<script type="text/html" id="template-boolean">
<input type="hidden" data-forms-switch data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" data-cast-to="boolean" class="margin-bottom-no" />
</script>
<script type="text/html" id="template-url">
<input type="url" data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" />
</script>
<script type="text/html" id="template-email">
<input type="email" data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" />
</script>
<script type="text/html" id="template-ip">
<input type="text" data-ls-attrs="name={{attribute.key}}" data-ls-bind="{{project-document|documentAttribute}}" pattern="((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))" title="Enter a valid IPV4 or IPV6 address" />
</script>

View file

@ -423,18 +423,18 @@ $auth = $this->getParam('auth', []);
data-scope="console">
<ul class="tiles cell-3 margin-bottom-small">
<?php foreach ($providers as $provider => $data):
if (isset($data['enabled']) && !$data['enabled']) {continue;}
// if (isset($data['mock']) && $data['mock']) {continue;}
if (isset($data['enabled']) && !$data['enabled']) {continue;}
if (isset($data['mock']) && $data['mock']) {continue;}
$sandbox = $data['sandbox'] ?? false;
$form = $data['form'] ?? false;
$name = $data['name'] ?? 'Unknown';
$beta = $data['beta'] ?? false;
?>
<li class="<?php echo (isset($data['enabled']) && !$data['enabled']) ? 'dev-feature' : ''; ?>">
<div data-ui-modal class="modal close" data-button-alias="none" data-open-event="provider-update-<?php echo $provider; ?>">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
?>
<li class="<?php echo (isset($data['enabled']) && !$data['enabled']) ? 'dev-feature' : ''; ?>">
<div data-ui-modal class="modal close" data-button-alias="none" data-open-event="provider-update-<?php echo $provider; ?>">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1><?php echo $this->escape($name); ?> <?php if ($sandbox): ?>Sandbox<?php endif;?> OAuth2 Settings</h1>
<h1><?php echo $this->escape($name); ?> <?php if ($sandbox): ?>Sandbox<?php endif;?> OAuth2 Settings</h1>
<form
autocomplete="off"

View file

@ -0,0 +1,14 @@
let sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.listDocumentLogs('[COLLECTION_ID]', '[DOCUMENT_ID]');
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});

View file

@ -0,0 +1 @@
Get the document activity logs list by its unique ID.

View file

@ -203,7 +203,9 @@ if(typeof read!=='undefined'){payload['read']=read;}
if(typeof write!=='undefined'){payload['write']=write;}
const uri=new URL(this.config.endpoint+path);return yield this.call('patch',uri,{'content-type':'application/json',},payload);}),deleteDocument:(collectionId,documentId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
let path='/database/collections/{collectionId}/documents/{documentId}'.replace('{collectionId}',collectionId).replace('{documentId}',documentId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listIndexes:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/documents/{documentId}'.replace('{collectionId}',collectionId).replace('{documentId}',documentId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('delete',uri,{'content-type':'application/json',},payload);}),listDocumentLogs:(collectionId,documentId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof documentId==='undefined'){throw new AppwriteException('Missing required parameter: "documentId"');}
let path='/database/collections/{collectionId}/documents/{documentId}/logs'.replace('{collectionId}',collectionId).replace('{documentId}',documentId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),listIndexes:(collectionId)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
let path='/database/collections/{collectionId}/indexes'.replace('{collectionId}',collectionId);let payload={};const uri=new URL(this.config.endpoint+path);return yield this.call('get',uri,{'content-type':'application/json',},payload);}),createIndex:(collectionId,indexId,type,attributes,orders)=>__awaiter(this,void 0,void 0,function*(){if(typeof collectionId==='undefined'){throw new AppwriteException('Missing required parameter: "collectionId"');}
if(typeof indexId==='undefined'){throw new AppwriteException('Missing required parameter: "indexId"');}
if(typeof type==='undefined'){throw new AppwriteException('Missing required parameter: "type"');}

View file

@ -1755,6 +1755,30 @@
'content-type': 'application/json',
}, payload);
}),
/**
* List Document Logs
*
* Get the document activity logs list by its unique ID.
*
* @param {string} collectionId
* @param {string} documentId
* @throws {AppwriteException}
* @returns {Promise}
*/
listDocumentLogs: (collectionId, documentId) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"');
}
if (typeof documentId === 'undefined') {
throw new AppwriteException('Missing required parameter: "documentId"');
}
let path = '/database/collections/{collectionId}/documents/{documentId}/logs'.replace('{collectionId}', collectionId).replace('{documentId}', documentId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* List Indexes
*

View file

@ -13,6 +13,9 @@
case 'integer':
value = parseInt(value);
break;
case 'float':
value = parseFloat(parseFloat(value).toFixed(2));
break;
case 'numeric':
value = Number(value);
break;