1
0
Fork 0
mirror of synced 2024-05-20 04:32:37 +12:00

Merge branch '1.1.x' of https://github.com/appwrite/appwrite into fix-deletes-worker

This commit is contained in:
Christy Jacob 2022-11-16 11:10:39 +00:00
commit 08a986e433
339 changed files with 623 additions and 52912 deletions

View file

@ -13,6 +13,8 @@ jobs:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# Fetch submodules
submodules: recursive
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 2.0.0

View file

@ -1,4 +1,6 @@
# Version 1.1.0
## Features
- Added new property to projects configuration: `authDuration` which allows you to alter the duration of signed in sessions for your project. [#4618](https://github.com/appwrite/appwrite/pull/4618)
## Bugs
- Fix license detection for Flutter and Dart SDKs [#4435](https://github.com/appwrite/appwrite/pull/4435)
@ -9,6 +11,11 @@
# Version 1.0.4
- Fix project pagination in DB usage collector [#4517](https://github.com/appwrite/appwrite/pull/4517)
- Fix missing `status`, `buildStderr` and `buildStderr` from get deployment response [#4611](https://github.com/appwrite/appwrite/pull/4611)
- Fix project pagination in DB usage aggregation [#4517](https://github.com/appwrite/appwrite/pull/4517)
# Features
- Added Auth Duration API to allow users to set the duration of their sessions. [#4618](https://github.com/appwrite/appwrite/pull/4618)
# Version 1.0.3
## Bugs

View file

@ -14,12 +14,9 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
FROM node:16.14.2-alpine3.15 as node
WORKDIR /usr/local/src/
COPY app/console /usr/local/src/console
COPY package-lock.json /usr/local/src/
COPY package.json /usr/local/src/
COPY gulpfile.js /usr/local/src/
COPY public /usr/local/src/public
WORKDIR /usr/local/src/console
RUN npm ci
RUN npm run build
@ -35,7 +32,7 @@ ENV PHP_REDIS_VERSION=5.3.7 \
PHP_IMAGICK_VERSION=3.7.0 \
PHP_YAML_VERSION=2.2.2 \
PHP_MAXMINDDB_VERSION=v1.11.0 \
PHP_ZSTD_VERSION="4504e4186e79b197cfcb75d4d09aa47ef7d92fe9 "
PHP_ZSTD_VERSION="4504e4186e79b197cfcb75d4d09aa47ef7d92fe9"
RUN \
apk add --no-cache --virtual .deps \
@ -297,7 +294,7 @@ RUN \
WORKDIR /usr/src/code
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
COPY --from=node /usr/local/src/public/dist /usr/src/code/public/dist
COPY --from=node /usr/local/src/console/build /usr/src/code/console
COPY --from=swoole /usr/local/lib/php/extensions/no-debug-non-zts-20200930/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/yasd.so* /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
COPY --from=redis /usr/local/lib/php/extensions/no-debug-non-zts-20200930/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
COPY --from=imagick /usr/local/lib/php/extensions/no-debug-non-zts-20200930/imagick.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
@ -311,8 +308,6 @@ COPY --from=zstd /usr/local/lib/php/extensions/no-debug-non-zts-20200930/zstd.so
COPY ./app /usr/src/code/app
COPY ./bin /usr/local/bin
COPY ./docs /usr/src/code/docs
COPY ./public/fonts /usr/src/code/public/fonts
COPY ./public/images /usr/src/code/public/images
COPY ./src /usr/src/code/src
# Set Volumes
@ -376,4 +371,4 @@ RUN echo "opcache.jit=1235" >> /usr/local/etc/php/conf.d/appwrite.ini
EXPOSE 80
CMD [ "php", "app/http.php", "-dopcache.preload=opcache.preload=/usr/src/code/app/preload.php" ]
CMD [ "php", "app/http.php", "-dopcache.preload=opcache.preload=/usr/src/code/app/preload.php" ]

View file

@ -64,7 +64,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:1.0.3
appwrite/appwrite:1.1.0
```
### Windows
@ -76,7 +76,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:1.0.3
appwrite/appwrite:1.1.0
```
#### PowerShell
@ -86,7 +86,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:1.0.3
appwrite/appwrite:1.1.0
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -75,7 +75,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:1.0.3
appwrite/appwrite:1.1.0
```
### Windows
@ -87,7 +87,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:1.0.3
appwrite/appwrite:1.1.0
```
#### PowerShell
@ -97,7 +97,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:1.0.3
appwrite/appwrite:1.1.0
```
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.

View file

@ -1632,17 +1632,6 @@ $collections = [
'array' => false,
'filters' => ['encrypt'],
],
[
'$id' => ID::custom('expire'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('userAgent'),
'type' => Database::VAR_STRING,

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

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

1
app/console Submodule

@ -0,0 +1 @@
Subproject commit de73c020a7798e580c48197816124ca4783e115b

View file

@ -163,10 +163,11 @@ App::post('/v1/account/sessions/email')
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('locale')
->inject('geodb')
->inject('events')
->action(function (string $email, string $password, Request $request, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Event $events) {
->action(function (string $email, string $password, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
$email = \strtolower($email);
$protocol = $request->getProtocol();
@ -183,9 +184,11 @@ App::post('/v1/account/sessions/email')
throw new Exception(Exception::USER_BLOCKED); // User is in status blocked
}
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
$expire = DateTime::addSeconds(new \DateTime(), $duration);
$secret = Auth::tokenGenerator();
$session = new Document(array_merge(
[
@ -195,7 +198,6 @@ App::post('/v1/account/sessions/email')
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $email,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
@ -242,6 +244,7 @@ App::post('/v1/account/sessions/email')
$session
->setAttribute('current', true)
->setAttribute('countryName', $countryName)
->setAttribute('expire', $expire)
;
$events
@ -448,7 +451,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
$sessions = $user->getAttribute('sessions', []);
$current = Auth::sessionVerify($sessions, Auth::$secret);
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$current = Auth::sessionVerify($sessions, Auth::$secret, $authDuration);
if ($current) { // Delete current session of new one.
$currentDocument = $dbForProject->getDocument('sessions', $current);
@ -523,10 +527,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
// Create session token, verify user account and update OAuth2 ID and Access Token
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$secret = Auth::tokenGenerator();
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
$expire = DateTime::addSeconds(new \DateTime(), $duration);
$session = new Document(array_merge([
'$id' => ID::unique(),
@ -538,7 +543,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'providerRefreshToken' => $refreshToken,
'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
@ -569,6 +573,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$dbForProject->deleteCachedDocument('users', $user->getId());
$session->setAttribute('expire', $expire);
$events
->setParam('userId', $user->getId())
->setParam('sessionId', $session->getId())
@ -757,10 +763,11 @@ App::put('/v1/account/sessions/magic-url')
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('locale')
->inject('geodb')
->inject('events')
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Event $events) {
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
/** @var Utopia\Database\Document $user */
@ -776,10 +783,11 @@ App::put('/v1/account/sessions/magic-url')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$secret = Auth::tokenGenerator();
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
$expire = DateTime::addSeconds(new \DateTime(), $duration);
$session = new Document(array_merge(
[
@ -788,7 +796,6 @@ App::put('/v1/account/sessions/magic-url')
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_MAGIC_URL,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
@ -848,6 +855,7 @@ App::put('/v1/account/sessions/magic-url')
$session
->setAttribute('current', true)
->setAttribute('countryName', $countryName)
->setAttribute('expire', $expire)
;
$response->dynamic($session, Response::MODEL_SESSION);
@ -994,10 +1002,11 @@ App::put('/v1/account/sessions/phone')
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('locale')
->inject('geodb')
->inject('events')
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Event $events) {
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
@ -1011,10 +1020,11 @@ App::put('/v1/account/sessions/phone')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$secret = Auth::tokenGenerator();
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
$expire = DateTime::addSeconds(new \DateTime(), $duration);
$session = new Document(array_merge(
[
@ -1023,7 +1033,6 @@ App::put('/v1/account/sessions/phone')
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_PHONE,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
@ -1081,6 +1090,7 @@ App::put('/v1/account/sessions/phone')
$session
->setAttribute('current', true)
->setAttribute('countryName', $countryName)
->setAttribute('expire', $expire)
;
$response->dynamic($session, Response::MODEL_SESSION);
@ -1162,11 +1172,11 @@ App::post('/v1/account/sessions/anonymous')
])));
// Create session token
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$secret = Auth::tokenGenerator();
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
$expire = DateTime::addSeconds(new \DateTime(), $duration);
$session = new Document(array_merge(
[
@ -1175,7 +1185,6 @@ App::post('/v1/account/sessions/anonymous')
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_ANONYMOUS,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
@ -1215,6 +1224,7 @@ App::post('/v1/account/sessions/anonymous')
$session
->setAttribute('current', true)
->setAttribute('countryName', $countryName)
->setAttribute('expire', $expire)
;
$response->dynamic($session, Response::MODEL_SESSION);
@ -1322,10 +1332,12 @@ App::get('/v1/account/sessions')
->inject('response')
->inject('user')
->inject('locale')
->action(function (Response $response, Document $user, Locale $locale) {
->inject('project')
->action(function (Response $response, Document $user, Locale $locale, Document $project) {
$sessions = $user->getAttribute('sessions', []);
$current = Auth::sessionVerify($sessions, Auth::$secret);
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$current = Auth::sessionVerify($sessions, Auth::$secret, $authDuration);
foreach ($sessions as $key => $session) {/** @var Document $session */
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
@ -1420,11 +1432,13 @@ App::get('/v1/account/sessions/:sessionId')
->inject('user')
->inject('locale')
->inject('dbForProject')
->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Database $dbForProject) {
->inject('project')
->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Database $dbForProject, Document $project) {
$sessions = $user->getAttribute('sessions', []);
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$sessionId = ($sessionId === 'current')
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret, $authDuration)
: $sessionId;
foreach ($sessions as $session) {/** @var Document $session */
@ -1434,6 +1448,7 @@ App::get('/v1/account/sessions/:sessionId')
$session
->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret)))
->setAttribute('countryName', $countryName)
->setAttribute('expire', DateTime::addSeconds(new \DateTime($session->getCreatedAt()), $authDuration))
;
return $response->dynamic($session, Response::MODEL_SESSION);
@ -1700,11 +1715,13 @@ App::delete('/v1/account/sessions/:sessionId')
->inject('dbForProject')
->inject('locale')
->inject('events')
->action(function (?string $sessionId, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $events) {
->inject('project')
->action(function (?string $sessionId, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $events, Document $project) {
$protocol = $request->getProtocol();
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$sessionId = ($sessionId === 'current')
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret, $authDuration)
: $sessionId;
$sessions = $user->getAttribute('sessions', []);
@ -1775,9 +1792,9 @@ App::patch('/v1/account/sessions/:sessionId')
->inject('locale')
->inject('events')
->action(function (?string $sessionId, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Event $events) {
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$sessionId = ($sessionId === 'current')
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret, $authDuration)
: $sessionId;
$sessions = $user->getAttribute('sessions', []);
@ -1818,6 +1835,10 @@ App::patch('/v1/account/sessions/:sessionId')
$dbForProject->deleteCachedDocument('users', $user->getId());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session->setAttribute('expire', DateTime::addSeconds(new \DateTime($session->getCreatedAt()), $authDuration));
$events
->setParam('userId', $user->getId())
->setParam('sessionId', $session->getId())
@ -1871,6 +1892,7 @@ App::delete('/v1/account/sessions')
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) {
$session->setAttribute('current', true);
$session->setAttribute('expire', DateTime::addSeconds(new \DateTime($session->getCreatedAt()), Auth::TOKEN_EXPIRATION_LOGIN_LONG));
// If current session delete the cookies too
$response

View file

@ -342,7 +342,6 @@ App::get('/v1/avatars/initials')
->desc('Get User Initials')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resource', 'avatar/initials')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
@ -399,8 +398,8 @@ App::get('/v1/avatars/initials')
$punch->newImage($width, $height, 'transparent');
$draw->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
$image->setFont(__DIR__ . "/../../../public/fonts/poppins-v9-latin-500.ttf");
$draw->setFont(__DIR__ . "/../../assets/fonts/poppins-v9-latin-500.ttf");
$image->setFont(__DIR__ . "/../../assets/fonts/poppins-v9-latin-500.ttf");
$draw->setFillColor(new ImagickPixel('black'));
$draw->setFontSize($fontSize);

View file

@ -32,6 +32,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Hostname;
use Utopia\Validator\Integer;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@ -80,7 +81,7 @@ App::post('/v1/projects')
}
$auth = Config::getParam('auth', []);
$auths = ['limit' => 0];
$auths = ['limit' => 0, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
foreach ($auth as $index => $method) {
$auths[$method['key'] ?? ''] = true;
}
@ -510,6 +511,37 @@ App::patch('/v1/projects/:projectId/auth/limit')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/duration')
->desc('Update Project Authentication Duration')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthDuration')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('duration', 31536000, new Range(0, 31536000), 'Project session length in seconds. Max length: 31536000 seconds.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['duration'] = $duration;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/:method')
->desc('Update Project auth method status. Use this endpoint to enable or disable a given auth method for this project.')
->groups(['api', 'projects'])

View file

@ -783,6 +783,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('cache', true)
->label('cache.resourceType', 'bucket/{request.bucketId}')
->label('cache.resource', 'file/{request.fileId}')
->label('usage.metric', 'files.{scope}.requests.read')
->label('usage.params', ['bucketId:{request.bucketId}'])
@ -840,9 +841,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
$key = \md5($fileId . $width . $height . $gravity . $quality . $borderWidth . $borderColor . $borderRadius . $opacity . $rotation . $background . $output);
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {

View file

@ -677,9 +677,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('project')
->inject('geodb')
->inject('events')
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, Event $events) {
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $events) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@ -731,7 +732,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_LOGIN_LONG);
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$id' => ID::unique(),
@ -740,7 +742,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',

View file

@ -566,32 +566,6 @@ App::error()
);
});
App::get('/manifest.json')
->desc('Progressive app manifest file')
->label('scope', 'public')
->label('docs', false)
->inject('response')
->action(function (Response $response) {
$response->json([
'name' => APP_NAME,
'short_name' => APP_NAME,
'start_url' => '.',
'url' => 'https://appwrite.io/',
'display' => 'standalone',
'background_color' => '#fff',
'theme_color' => '#f02e65',
'description' => 'End to end backend server for frontend and mobile apps. 👩‍💻👨‍💻',
'icons' => [
[
'src' => 'images/favicon.png',
'sizes' => '256x256',
'type' => 'image/png',
],
],
]);
});
App::get('/robots.txt')
->desc('Robots.txt File')
->label('scope', 'public')

View file

@ -208,19 +208,52 @@ App::init()
$useCache = $route->getLabel('cache', false);
if ($useCache) {
$key = md5($request->getURI() . implode('*', $request->getParams()));
$key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
$timestamp = 60 * 60 * 24 * 30;
$data = $cache->load($key, $timestamp);
if (!empty($data)) {
$data = json_decode($data, true);
$parts = explode('/', $data['resourceType']);
$type = $parts[0] ?? null;
if ($type === 'bucket') {
$bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$parts = explode('/', $data['resource']);
$fileId = $parts[1] ?? null;
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
}
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
->addHeader('X-Appwrite-Cache', 'hit')
->setContentType($data['content-type'])
->setContentType($data['contentType'])
->send(base64_decode($data['payload']))
;
@ -408,7 +441,7 @@ App::shutdown()
*/
$useCache = $route->getLabel('cache', false);
if ($useCache) {
$resource = null;
$resource = $resourceType = null;
$data = $response->getPayload();
if (!empty($data['payload'])) {
@ -417,9 +450,16 @@ App::shutdown()
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user);
}
$key = md5($request->getURI() . implode('*', $request->getParams()));
$pattern = $route->getLabel('cache.resourceType', null);
if (!empty($pattern)) {
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
}
$key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
$data = json_encode([
'content-type' => $response->getContentType(),
'resourceType' => $resourceType,
'resource' => $resource,
'contentType' => $response->getContentType(),
'payload' => base64_encode($data['payload']),
]) ;

View file

@ -1,609 +1,19 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\View;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Domains\Domain;
use Utopia\Database\Validator\UID;
use Utopia\Storage\Storage;
App::init()
->groups(['console'])
->inject('layout')
->action(function (View $layout) {
$layout
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
->setParam('analytics', 'UA-26264668-5')
;
});
App::shutdown()
->groups(['console'])
->inject('response')
->inject('layout')
->action(function (Response $response, View $layout) {
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
$header
->setParam('regions', Config::getParam('regions', []))
;
$footer
->setParam('home', App::getEnv('_APP_HOME', ''))
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
->setParam('header', [$header])
->setParam('footer', [$footer])
;
$response->html($layout->render());
});
App::get('/error/:code')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'home')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
->inject('layout')
->action(function (int $code, View $layout) {
$page = new View(__DIR__ . '/../../views/error.phtml');
$page
->setParam('code', $code)
;
$layout
->setParam('title', APP_NAME . ' - Error')
->setParam('body', $page);
});
App::get('/console')
->groups(['web', 'console'])
->alias('/')
->alias('/invite')
->alias('/login')
->alias('/recover')
->alias('/register')
->groups(['web'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/index.phtml');
$page
->setParam('home', App::getEnv('_APP_HOME', ''))
;
$layout
->setParam('title', APP_NAME . ' - Console')
->setParam('body', $page);
});
App::get('/console/account')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/account/index.phtml');
$cc = new View(__DIR__ . '/../../views/console/forms/credit-card.phtml');
$page
->setParam('cc', $cc)
;
$layout
->setParam('title', 'Account - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/console/notifications')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/v1/console/notifications/index.phtml');
$layout
->setParam('title', APP_NAME . ' - Notifications')
->setParam('body', $page);
});
App::get('/console/home')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/home/index.phtml');
$page
->setParam('usageStatsEnabled', App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled');
$layout
->setParam('title', APP_NAME . ' - Console')
->setParam('body', $page);
});
App::get('/console/settings')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
$page = new View(__DIR__ . '/../../views/console/settings/index.phtml');
$page->setParam('services', array_filter(Config::getParam('services'), fn($element) => $element['optional']))
->setParam('customDomainsEnabled', ($target->isKnown() && !$target->isTest()))
->setParam('customDomainsTarget', $target->get())
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
;
$layout
->setParam('title', APP_NAME . ' - Settings')
->setParam('body', $page);
});
App::get('/console/webhooks')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/webhooks/index.phtml');
$page->setParam('events', Config::getParam('events', []));
$layout
->setParam('title', APP_NAME . ' - Webhooks')
->setParam('body', $page);
});
App::get('/console/webhooks/webhook')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('id', '', new UID(), 'Webhook unique ID.')
->inject('layout')
->action(function (string $id, View $layout) {
$page = new View(__DIR__ . '/../../views/console/webhooks/webhook.phtml');
$page
->setParam('events', Config::getParam('events', []))
->setParam('new', false)
;
$layout
->setParam('title', APP_NAME . ' - Webhooks')
->setParam('body', $page);
});
App::get('/console/webhooks/webhook/new')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/webhooks/webhook.phtml');
$page
->setParam('events', Config::getParam('events', []))
->setParam('new', true)
;
$layout
->setParam('title', APP_NAME . ' - Webhooks')
->setParam('body', $page);
});
App::get('/console/keys')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$scopes = array_keys(Config::getParam('scopes'));
$page = new View(__DIR__ . '/../../views/console/keys/index.phtml');
$page->setParam('scopes', $scopes);
$layout
->setParam('title', APP_NAME . ' - API Keys')
->setParam('body', $page);
});
App::get('/console/databases')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/databases/index.phtml');
$layout
->setParam('title', APP_NAME . ' - Database')
->setParam('body', $page);
});
App::get('/console/databases/database')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('id', '', new UID(), 'Database unique ID.')
->label('scope', 'home')
->inject('response')
->inject('layout')
->action(function (string $id, Response $response, View $layout) {
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'database.listLogs')
->setParam('params', [
'database-id' => '{{router.params.id}}',
])
;
$page = new View(__DIR__ . '/../../views/console/databases/database.phtml');
$page->setParam('logs', $logs);
$layout
->setParam('title', APP_NAME . ' - Database')
->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/databases/collection')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('id', '', new UID(), 'Collection unique ID.')
->inject('response')
->inject('layout')
->action(function (string $id, Response $response, View $layout) {
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'databases.listCollectionLogs')
->setParam('params', [
'collection-id' => '{{router.params.id}}',
'database-id' => '{{router.params.databaseId}}'
])
;
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$permissions
->setParam('method', 'databases.getCollection')
->setParam('events', 'load,databases.updateCollection')
->setParam('form', 'collectionPermissions')
->setParam('data', 'project-collection')
->setParam('params', [
'collection-id' => '{{router.params.id}}',
'database-id' => '{{router.params.databaseId}}'
]);
$page = new View(__DIR__ . '/../../views/console/databases/collection.phtml');
$page
->setParam('permissions', $permissions)
->setParam('logs', $logs)
;
$layout
->setParam('title', APP_NAME . ' - Database Collection')
->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/databases/document')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('databaseId', '', new UID(), 'Database unique ID.')
->param('collectionId', '', new UID(), 'Collection unique ID.')
->inject('layout')
->action(function (string $databaseId, string $collectionId, View $layout) {
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'databases.listDocumentLogs')
->setParam('params', [
'database-id' => '{{router.params.databaseId}}',
'collection-id' => '{{router.params.collectionId}}',
'document-id' => '{{router.params.id}}',
])
;
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$permissions
->setParam('method', 'databases.getDocument')
->setParam('events', 'load,databases.updateDocument')
->setParam('form', 'documentPermissions')
->setParam('data', 'project-document')
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'collection-id' => '{{router.params.collectionId}}',
'database-id' => '{{router.params.databaseId}}',
'document-id' => '{{router.params.id}}',
]);
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
$page
->setParam('new', false)
->setParam('database', $databaseId)
->setParam('collection', $collectionId)
->setParam('permissions', $permissions)
->setParam('logs', $logs)
;
$layout
->setParam('title', APP_NAME . ' - Database Document')
->setParam('body', $page);
});
App::get('/console/databases/document/new')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('databaseId', '', new UID(), 'Database unique ID.')
->param('collectionId', '', new UID(), 'Collection unique ID.')
->inject('layout')
->action(function (string $databaseId, string $collectionId, View $layout) {
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$permissions
->setParam('data', 'project-document')
->setParam('form', 'documentPermissions')
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'collection-id' => '{{router.params.collectionId}}',
'database-id' => '{{router.params.databaseId}}',
'document-id' => '{{router.params.id}}',
]);
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
$page
->setParam('new', true)
->setParam('database', $databaseId)
->setParam('collection', $collectionId)
->setParam('permissions', $permissions)
->setParam('logs', new View())
;
$layout
->setParam('title', APP_NAME . ' - Database Document')
->setParam('body', $page);
});
App::get('/console/storage')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/storage/index.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')
->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 (string $id, Response $response, View $layout) {
$bucketPermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$bucketPermissions
->setParam('method', 'databases.getBucket')
->setParam('events', 'load,databases.updateBucket')
->setParam('data', 'project-bucket')
->setParam('form', 'bucketPermissions')
->setParam('params', [
'bucket-id' => '{{router.params.id}}',
]);
$fileCreatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$fileCreatePermissions
->setParam('form', 'fileCreatePermissions')
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
]);
$fileUpdatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$fileUpdatePermissions
->setParam('method', 'storage.getFile')
->setParam('data', 'file')
->setParam('form', 'fileUpdatePermissions')
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'bucket-id' => '{{router.params.id}}',
]);
$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)))
->setParam('bucketPermissions', $bucketPermissions)
->setParam('fileCreatePermissions', $fileCreatePermissions)
->setParam('fileUpdatePermissions', $fileUpdatePermissions)
;
$layout
->setParam('title', APP_NAME . ' - Storage Buckets')
->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')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/users/index.phtml');
$page
->setParam('auth', Config::getParam('auth'))
->setParam('providers', Config::getParam('providers'))
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
;
$layout
->setParam('title', APP_NAME . ' - Users')
->setParam('body', $page);
});
App::get('/console/users/user')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/users/user.phtml');
$layout
->setParam('title', APP_NAME . ' - User')
->setParam('body', $page);
});
App::get('/console/users/teams/team')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/users/team.phtml');
$layout
->setParam('title', APP_NAME . ' - Team')
->setParam('body', $page);
});
App::get('/console/functions')
->groups(['web', 'console'])
->desc('Platform console project functions')
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/functions/index.phtml');
$page
->setParam('runtimes', Config::getParam('runtimes'))
;
$layout
->setParam('title', APP_NAME . ' - Functions')
->setParam('body', $page);
});
App::get('/console/functions/function')
->groups(['web', 'console'])
->desc('Platform console project function')
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/console/functions/function.phtml');
$page
->setParam('events', Config::getParam('events', []))
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
->setParam('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
->setParam('usageStatsEnabled', App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled');
;
$layout
->setParam('title', APP_NAME . ' - Function')
->setParam('body', $page);
});
App::get('/console/version')
->groups(['web', 'console'])
->desc('Check for new version')
->label('permission', 'public')
->label('scope', 'console')
->inject('response')
->action(function ($response) {
try {
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost') . '/v1/health/version'), true);
if ($version && isset($version['version'])) {
return $response->json(['version' => $version['version']]);
} else {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to check for a newer version');
}
} catch (\Throwable $th) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to check for a newer version');
}
->action(function (Response $response) {
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
$response->html($fallback);
});

View file

@ -1,236 +1,8 @@
<?php
use Appwrite\Utopia\View;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
App::init()
->groups(['home'])
->inject('layout')
->action(function (View $layout) {
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
$footer
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
->setParam('title', APP_NAME)
->setParam('description', '')
->setParam('class', 'home')
->setParam('platforms', Config::getParam('platforms'))
->setParam('header', [$header])
->setParam('footer', [$footer])
;
});
App::shutdown()
->groups(['home'])
->inject('response')
->inject('layout')
->action(function (Response $response, View $layout) {
$response->html($layout->render());
});
App::get('/')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('response')
->inject('dbForConsole')
->inject('project')
->action(function (Response $response, Database $dbForConsole, Document $project) {
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Expires', 0)
->addHeader('Pragma', 'no-cache')
;
if ('console' === $project->getId() || $project->isEmpty()) {
$whitelistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled');
if ($whitelistRoot !== 'disabled') {
$count = $dbForConsole->count('users', [], 1);
if ($count !== 0) {
return $response->redirect('/auth/signin');
}
}
}
$response->redirect('/auth/signup');
});
App::get('/auth/signin')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/signin.phtml');
$page
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
;
$layout
->setParam('title', 'Sign In - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/auth/signup')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/signup.phtml');
$page
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
;
$layout
->setParam('title', 'Sign Up - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/auth/recovery')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/recovery.phtml');
$page
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
;
$layout
->setParam('title', 'Password Recovery - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/auth/confirm')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/confirm.phtml');
$layout
->setParam('title', 'Account Confirmation - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/auth/join')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/join.phtml');
$layout
->setParam('title', 'Invitation - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/auth/recovery/reset')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/recovery/reset.phtml');
$layout
->setParam('title', 'Password Reset - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/auth/oauth2/success')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/oauth2.phtml');
$layout
->setParam('title', APP_NAME)
->setParam('body', $page)
->setParam('header', [])
->setParam('footer', [])
;
});
App::get('/auth/magic-url')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/magicURL.phtml');
$layout
->setParam('title', APP_NAME)
->setParam('body', $page)
->setParam('header', [])
->setParam('footer', [])
;
});
App::get('/auth/oauth2/failure')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->inject('layout')
->action(function (View $layout) {
$page = new View(__DIR__ . '/../../views/home/auth/oauth2.phtml');
$layout
->setParam('title', APP_NAME)
->setParam('body', $page)
->setParam('header', [])
->setParam('footer', [])
;
});
App::get('/error/:code')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
->inject('layout')
->action(function (int $code, View $layout) {
$page = new View(__DIR__ . '/../../views/error.phtml');
$page
->setParam('code', $code)
;
$layout
->setParam('title', 'Error' . ' - ' . APP_NAME)
->setParam('body', $page);
});
App::get('/versions')
->desc('Get Version')
@ -238,7 +10,6 @@ App::get('/versions')
->label('scope', 'public')
->inject('response')
->action(function (Response $response) {
$platforms = Config::getParam('platforms');
$versions = [

View file

@ -51,7 +51,7 @@ $http->on('AfterReload', function ($server, $workerId) {
Console::success('Reload completed...');
});
Files::load(__DIR__ . '/../public');
Files::load(__DIR__ . '/../console');
include __DIR__ . '/controllers/general.php';

View file

@ -95,7 +95,7 @@ const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate pe
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 501;
const APP_VERSION_STABLE = '1.0.3';
const APP_VERSION_STABLE = '1.1.0';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@ -600,7 +600,7 @@ $register->set('smtp', function () {
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-06.mmdb');
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2022-06.mmdb');
});
$register->set('db', function () {
// This is usually for our workers or CLI commands scope
@ -836,9 +836,11 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$user = $dbForConsole->getDocument('users', Auth::$unique);
}
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
if (
$user->isEmpty() // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration)
) { // Validate user has valid login token
$user = new Document(['$id' => ID::custom(''), '$collection' => 'users']);
}
@ -920,6 +922,7 @@ App::setResource('console', function () {
'legalTaxId' => '',
'auths' => [
'limit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
],
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],

View file

@ -306,7 +306,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
[$consoleDatabase, $returnConsoleDatabase] = getDatabase($register, '_console');
$project = Authorization::skip(fn() => $consoleDatabase->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
[$database, $returnDatabase] = getDatabase($register, "_{$project->getInternalId()}");
$user = $database->getDocument('users', $userId);
@ -484,6 +484,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try {
$app = new App('UTC');
$response = new Response(new SwooleResponse());
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
@ -493,12 +494,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace("_console");
$projectId = $realtime->connections[$connection]['projectId'];
if ($projectId !== 'console') {
$project = Authorization::skip(fn() => $database->getDocument('projects', $projectId));
$database->setNamespace("_{$project->getInternalId()}");
}
$project = $projectId === 'console' ? $app->getResource('console') : Authorization::skip(fn () => $database->getDocument('projects', $projectId));
$database->setNamespace("_{$project->getInternalId()}");
/*
* Abuse Check
*
@ -536,10 +533,11 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
Auth::$secret = $session['secret'] ?? '';
$user = $database->getDocument('users', Auth::$unique);
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
if (
empty($user->getId()) // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration) // Validate user has valid login token
) {
// cookie not valid
throw new Exception('Session is not valid.', 1003);

View file

@ -98,7 +98,7 @@ $cli
{
(new Delete())
->setType(DELETE_TYPE_SESSIONS)
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * Auth::TOKEN_EXPIRATION_LOGIN_LONG))
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * Auth::TOKEN_EXPIRATION_LOGIN_LONG)) //TODO: Update to use project session expiration instead of default.
->trigger();
}

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', '0.13.x', '0.14.x', '0.15.x', '1.0.x', 'latest'])) {
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', '0.14.x', '0.15.x', '1.0.x', '1.1.x', 'latest'])) {
throw new Exception('Unknown version given');
}

View file

@ -1,369 +0,0 @@
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Home</a>
<br />
<span>Your Account</span>
</h1>
</div>
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/account">
<h2 class="margin-bottom">Overview</h2>
<div
data-service="account.get"
data-scope="console"
data-name="account"
data-event="load"
data-failure="trigger"
data-failure-param-trigger-events="account.deleteSession">
<div class="row responsive force-reverse">
<div class="col span-3 text-align-center margin-bottom">
<img src="" data-ls-attrs="src={{account|avatar}}" data-size="200" height="150" alt="User Avatar" class="avatar huge huge margin-bottom-small" />
</div>
<div class="col span-9">
<div class="box margin-bottom-xl">
<div>
<form name="account.update"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Account Name"
data-service="account.updateName"
data-scope="console"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="Your name was updated successfully"
data-success-param-trigger-events="account.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update your name"
data-failure-param-alert-classname="error">
<label for="name">Name</label>
<div class="row responsive">
<div class="col span-8 margin-bottom-small">
<input name="name" id="name" type="text" autocomplete="off" data-ls-bind="{{account.name}}" required class="margin-bottom-no" maxlength="128">
</div>
<div class="col span-4">
<button type="submit" class="fill margin-bottom-no">Update Name</button>
</div>
</div>
</form>
</div>
<hr />
<form name="update-email"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Account Email"
data-service="account.updateEmail"
data-scope="console"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="Email address updated successfully"
data-success-param-trigger-events="account-update,modal-close"
data-failure="alert"
data-failure-param-alert-text="Failed updating email address"
data-failure-param-alert-classname="error">
<label>Email</label>
<div class="row responsive">
<div class="col span-8 margin-bottom-small">
<input name="email" type="email" class="margin-bottom-no" autocomplete="off" placeholder="me@example.com" data-ls-bind="{{account.email}}" required>
</div>
<div class="col span-4">
<div data-ui-modal class="modal box close width-small height-small" data-button-text="Update Email" data-button-class="fill">
<h3>Confirm Password</h3>
<hr />
<label>Password</label>
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="" required>
<hr />
<button type="submit" class="margin-bottom-no">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
</div>
</div>
</form>
<hr />
<div data-ui-modal class="modal box close width-small" data-button-text="Update Password" data-button-class="">
<h1>Update Password</h1>
<form name="update-password"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Account Password"
data-service="account.updatePassword"
data-scope="console"
data-event="submit"
data-success="trigger,alert,reset"
data-success-param-trigger-events="account-update"
data-success-param-alert-text="Password updated successfully"
data-failure="alert"
data-failure-param-alert-text="Failed updating password"
data-failure-param-alert-classname="error">
<label>Current Password</label>
<input name="oldPassword" type="password" class="full-width" autocomplete="off" placeholder="" required>
<label>New Password</label>
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="" required data-forms-password-meter>
<hr />
<footer>
<button type="submit">Update Password</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<hr />
<form class="margin-top"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Account Current Session"
data-service="account.deleteSession"
data-scope="console"
data-event="submit"
data-success="trigger"
data-success-param-trigger-events="account.deleteSession"
data-failure="alert"
data-failure-param-alert-text="Logout failed"
data-failure-param-alert-classname="error">
<input type="hidden" name="sessionId" value="current">
<button class="fill danger icon fill"><i class="icon-login"></i> Logout</button>
</form>
</div>
</div>
</div>
<div class="row responsive">
<div class="col span-9">
<h3 class="text-danger">Danger Zone</h3>
<div class="box danger">
<p>This is the area where you can delete your account.</p>
<p>By deleting your account you will lose access to any of your teams and shared data.</p>
<p>PLEASE NOTE: Account deletion is irreversible.</p>
<form class="inline"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Account"
data-service="account.delete"
data-scope="console"
data-event="submit"
data-confirm="Are you sure you want to delete your account?"
data-success="trigger"
data-success-param-trigger-events="account.delete"
data-failure="alert"
data-failure-param-alert-text="Account deactivation failed"
data-failure-param-alert-classname="error">
<button class="danger reverse">Delete Account</button>
</form>
</div>
</div>
<div class="col span-3"></div>
</div>
</div>
</li>
<li data-state="/console/account/sessions">
<h2>Sessions</h2>
<div class="box margin-bottom"
data-service="account.listSessions"
data-scope="console"
data-name="sessions"
data-event="load,account.deleteRemoteSession">
<ul data-ls-loop="sessions.sessions" data-ls-as="session" class="list">
<li class="clear">
<span data-ls-if="true != {{session.current}}">
<!-- From remote session (-logout event) -->
<form class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Account Session"
data-service="account.deleteSession"
data-scope="console"
data-event="submit"
data-loading="Loading..."
data-success="trigger"
data-success-param-trigger-events="account.deleteRemoteSession"
data-failure="alert"
data-failure-param-alert-text="Logout from Session Failed"
data-failure-param-alert-classname="error">
<input type="hidden" name="sessionId" data-ls-bind="{{session.$id}}">
<button class="danger">Logout</button>
</form>
</span>
<span data-ls-if="true == {{session.current}}">
<!-- From current session (+logout event) -->
<form class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Account Current Session"
data-service="account.deleteSession"
data-scope="console"
data-event="submit"
data-loading="Loading..."
data-success="trigger,redirect"
data-success-param-trigger-events="account.deleteSession"
data-success-param-redirect-url="/"
data-failure="alert"
data-failure-param-alert-text="Logout from Session Failed"
data-failure-param-alert-classname="error">
<input type="hidden" name="sessionId" data-ls-bind="{{session.$id}}">
<button class="danger">Logout</button>
</form>
</span>
<div class="pull-start margin-end avatar-container">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" data-ls-if="{{session.clientCode|lowercase}} !== 'cli'"/>
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" data-ls-if="{{session.clientCode|lowercase}} === 'cli'" >
<div data-ls-if="{{session.provider}} !== 'email'" class="corner">
<img data-ls-attrs="src=/images/users/{{session.provider}}.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.provider}},alt={{session.provider}}" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
</div>
<span data-ls-if="(!{{log.clientName}})">Unknown</span>
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{session.clientName}}"></span> <span data-ls-bind="{{session.clientVersion}}"></span> on <span data-ls-bind="{{session.deviceModel}}"></span> <span data-ls-bind="{{session.osName}}"></span> <span data-ls-bind="{{session.osVersion}}"></span>
&nbsp;
<span data-ls-if="true == {{session.current}}">
<span class="tag green">Current Session</span>
</span>
<div class="margin-top-small">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.countryName}}"></small>
</div>
</li>
</ul>
</div>
<form class="inline margin-bottom-large"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Account Sessions"
data-service="account.deleteSessions"
data-scope="console"
data-event="submit"
data-success="trigger,redirect"
data-success-param-trigger-events="account.deleteSession"
data-success-param-redirect-url="/"
data-failure="alert"
data-failure-param-alert-text="Logout from All Sessions Failed"
data-failure-param-alert-classname="error">
<input type="hidden" name="id" value="0">
<button class="danger">Logout from all sessions</button>
</form>
</li>
<li data-state="/console/account/activity">
<h2>Activity</h2>
<div
data-service="account.listLogs"
data-scope="console"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}})"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-name="securityLogs"
data-event="load">
<div class="box margin-bottom">
<table class="vertical small">
<thead>
<tr>
<th width="120">Date</th>
<th width="175">Event</th>
<th>Client</th>
<th width="110">Location</th>
<th width="90">IP</th>
</tr>
</thead>
<tbody data-ls-loop="securityLogs.logs" data-ls-as="log">
<tr>
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" data-ls-if="{{log.clientCode|lowercase}} !== 'cli'"/>
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" data-ls-if="{{log.clientCode|lowercase}} === 'cli'" />
<span data-ls-if="(!{{log.clientName}})">Unknown</span>
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></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="account.listLogs"
data-event="submit"
data-scope="console"
data-name="securityLogs"
data-success="state"
data-success-param-state-keys="offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-offset="{{router.params.offset}}" data-total="{{securityLogs.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{securityLogs.total|pageTotal}}"></span>
<form
data-service="account.listLogs"
data-event="submit"
data-scope="console"
data-name="securityLogs"
data-success="state"
data-success-param-state-keys="offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-offset="{{router.params.offset}}" data-total="{{securityLogs.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
</div>
</li>
</ul>
</div>

View file

@ -1,43 +0,0 @@
<?php
$home = $this->getParam('home', '');
$version = $this->getParam('version', '') . '.' . APP_CACHE_BUSTER;
?>
<footer class="clear margin-top-large">
<ul class="copyright pull-start">
<li>
<a class="link-animation-enabled"
data-analytics
data-analytics-event="click"
data-analytics-category="console/footer"
data-analytics-label="GitHub Link"
href="https://github.com/appwrite/appwrite" target="_blank" rel="noopener"><i class="icon-github"></i> GitHub</a>
</li>
<li>
<a class="link-animation-enabled"
data-analytics
data-analytics-event="click"
data-analytics-category="console/footer"
data-analytics-label="Discord Link"
href="https://appwrite.io/discord" target="_blank" rel="noopener"><i class="icon-discord"></i> Discord</a>
</li>
<li>
<a class="link-animation-enabled"
data-analytics
data-analytics-event="click"
data-analytics-category="console/footer"
data-analytics-label="New GitHub Issue"
href="https://github.com/appwrite/appwrite/issues/new?assignees=&labels=bug&template=bug.yaml&title=[<?php echo $version; ?>]%20%F0%9F%90%9B+Bug+Report%3A+" target="_blank" rel="noopener">Open an Issue</a>
</li>
<li>
<a class="link-animation-enabled"
data-analytics
data-analytics-event="click"
data-analytics-category="console/footer"
data-analytics-label="Docs Link"
href="<?php echo $home; ?>/docs" target="_blank" rel="noopener">Docs</a>
</li>
<li>
v:<?php echo $version; ?>
</li>
</ul>
</footer>

View file

@ -1,273 +0,0 @@
<?php
$regions = $this->getParam('regions', []);
?>
<header class="clear" data-version>
<a href="/console" class="logo pull-start">
<img src="/images/appwrite.svg" alt="Appwrite Light Logo" class="force-light" loading="lazy" />
<img src="/images/appwrite-footer-dark.svg" alt="Appwrite Dark Logo" class="force-dark" loading="lazy" />
</a>
<div class="change-project pull-start desktops-only">
<div class="list project-only margin-end-small">
<label class="margin-bottom-no">
<select class="margin-bottom-no"
data-xanalytics-event="change"
data-xanalytics-category="console/header"
data-xanalytics-label="Project Switch"
data-switch
data-ls-bind="{{router.params.project}}"
data-unsync="1"
data-ls-loop="projects.projects" data-ls-as="option" aria-label="Switch Project">
<option data-ls-attrs="value={{option.$id}}" data-ls-bind="{{option.name}}"></option>
</select>
</label>
</div>
<button style="overflow: visible;" class="project-only setup-new tooltip round down pull-end" aria-label="Quick Start" data-tooltip="Create a new project"><i class="icon-plus"></i></button>
</div>
<div class="account-box pull-end"
data-service="account.get"
data-name="account"
data-scope="console"
data-event="load"
data-success="trigger"
data-success-param-trigger-events="account.get"
data-failure="trigger"
data-failure-param-trigger-events="account.deleteSession">
<div class="console-back">
<a href="/console" class="link-return-animation--end">Back to Console &nbsp;<i class="icon-right-open"></i></a>
</div>
<div class="account link pull-end clear">
<img src="" data-ls-attrs="src={{account|avatar}}" alt="User Avatar" class="avatar margin-start pull-end" />
<span class="name pull-end desktops-only" data-ls-bind="{{account.name}}"></span>
</div>
<div class="console-index drop-list bottom end" data-ls-ui-open="" data-button-text="" data-button-aria="Account Options" data-button-icon="" data-button-selector="[data-toggler]" data-button-class="account-button" data-blur="1">
<ul class="margin-top-large arrow-end">
<li>
<a href="/console/account"><i class="icon-user"></i> &nbsp; Your Account</a>
</li>
<li>
<span class="link"><i class="icon-sun-inv force-dark pull-start"></i><i class="icon-moon-inv force-light pull-start"></i> &nbsp; Change Theme
<div class="pull-end switch-theme">
<button data-general-theme
data-analytics
data-analytics-event="click"
data-analytics-category="console/header"
data-analytics-label="Switch Theme">
<i class="icon-sun-inv force-light"></i>
<i class="icon-moon-inv force-dark"></i>
</button>
</div>
</span>
</li>
</ul>
</div>
</div>
<nav class="project-only" data-ls-ui-open="" data-button-class="round icon-btn phones-only tablets-only" data-button-aria="Navigation" data-button-icon="icon-dot-3">
<a class="logo" href="/console"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Logo Link">
<img src="/images/appwrite-nav.svg" loading="lazy" alt="Appwrite Logo" class="nav" />
<img src="/images/appwrite.svg" loading="lazy" alt="Appwrite Light Logo" class="top force-light" loading="lazy" />
<img src="/images/appwrite-footer-dark.svg" loading="lazy" alt="Appwrite Dark Logo" class="top force-dark" loading="lazy" />
</a>
<div data-ui-highlight class="container">
<ul class="links">
<li>
<a data-ls-attrs="href=/console/home?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Home Link">
<i class="icon-home"></i>
Home
</a>
</li>
</ul>
<b class="subtitle">DEVELOP</b>
<ul class="links">
<li>
<a data-ls-attrs="href=/console/databases?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Database Link">
<i class="icon-database"></i>
Database
</a>
</li>
<li>
<a data-ls-attrs="href=/console/storage?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Storage Link">
<i class="icon-folder"></i>
Storage
</a>
</li>
<li>
<a data-ls-attrs="href=/console/users?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Users Link">
<i class="icon-users"></i>
Authentication
</a>
</li>
<li>
<a data-ls-attrs="href=/console/functions?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Functions Link"
class="link-animation-disabled">
<i class="icon-lightning"></i>
Functions
</a>
</li>
</ul>
<b class="subtitle">MANAGE</b>
<ul class="links">
<li>
<a data-ls-attrs="href=/console/webhooks?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Webhooks Links">
<i class="icon-link"></i>
Webhooks
</a>
</li>
<li>
<a data-ls-attrs="href=/console/keys?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="API Keys Link">
<i class="icon-key-inv"></i>
API Keys
</a>
</li>
</ul>
</div>
<ul class="links bottom">
<li>
<a data-ls-attrs="href=/console/settings?project={{router.params.project}}"
data-analytics
data-analytics-event="click"
data-analytics-category="console/navigation"
data-analytics-label="Settings Link">
<i class="icon-cog"></i> Settings</a>
</li>
</ul>
</nav>
</header>
<div class=""
data-service="projects.list"
data-event="load,projects.update"
data-name="projects"
data-scope="console"></div>
<div class="load-screen" data-ls-ui-loader>
<div class="animation"><div></div><div></div><div></div><div></div></div>
<img src="/images/appwrite.svg" alt="Appwrite Light Logo" class="force-light" loading="lazy" />
<img src="/images/appwrite-footer-dark.svg" alt="Appwrite Dark Logo" class="force-dark" loading="lazy" />
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".setup-new" data-button-icon="icon-plus" data-button-class="project-only" data-open-event="create-project">
<h1>Create Project</h1>
<form
data-setup
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project">
<p>Appwrite projects are containers for your resources and apps across different platforms.</p>
<label>Project ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="projects.get"
required
maxlength="36"
class=""
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
name="projectId" />
<label>Name</label>
<input type="text" class="full-width" name="name" required autocomplete="off" maxlength="128" />
<?php if(count($regions) > 1): ?>
<label>Region</label>
<select name="region" class="margin-bottom-xl">
<?php foreach($regions as $key => $region): ?>
<option <?php echo ($region['default'] ?? false) ? 'selected' : '' ?> <?php echo ($region['disabled'] ?? false ) ? 'disabled' : '' ?> value="<?php echo $key ?>"><?php echo $region['name'] ?></option>
<?php endforeach; ?>
</select>
<?php else: ?>
<input type="hidden" name="region" value="<?php echo array_key_first($regions) ?>" class="margin-bottom-xl" />
<?php endif; ?>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<section class="upload-box" x-data x-show="$store.uploader.files().length > 0">
<div class="upload-box-header">
<h4 class="upload-box-title">
<span class="text">Uploading files</span>
<span class="amount" x-text="$store.uploader.files().length"></span>
</h4>
<button class="icon-button" :class="$store.uploader.isOpen ? '' : 'is-open'" aria-label="toggle upload box" @click="$store.uploader.toggle()"><span class="icon-chevron-down" aria-hidden="true"></span></button>
<button class="icon-button" @click="$store.uploader.cancelAll()" aria-label="close upload box"><span class="icon-x" aria-hidden="true"></span></button>
</div>
<div class="upload-box-content" :class="$store.uploader.isOpen ? 'is-open' : ''">
<ul class="upload-box-list">
<template x-if="$store.uploader.files().length > 0">
<template x-for="file in $store.uploader.files()" :key="file.id">
<li x-show="!file.cancelled" class="upload-box-item">
<div class="upload-image u-margin-inline-end-16" :class="file.completed ? 'is-finished' : ''">
<div class="progress"
:style="'--progress-value:' + file.progress"
:aria-valuenow="file.progress"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"></div>
<span x-show="!file.completed" class="icon" x-text="file.progress + '%'"></span>
<span x-show="file.completed" class="icon icon-file"></span>
</div>
<label for="file1" class="file-name trim-inner-text" x-text="file.name" :title="file.name"></label>
<span class="pill is-failed" x-show="file.failed" :title="file.error">failed</span>
<button x-show="file.completed" class="icon-button is-success" aria-label="Uploading"><span class="icon-check"></span></button>
<button x-show="!file.completed" class="icon-button" aria-label="Uploading" @click="$store.uploader.removeFile(file.id)"><span class="icon-x"></span></button>
</li>
</template>
</template>
</ul>
</div>
</section>

View file

@ -1,96 +0,0 @@
<?php
$interval = floor((int)$this->getParam('interval', 0) / 86400);
$method = $this->getParam('method', '');
$params = $this->getParam('params', []);
?>
<div
data-service="<?php echo $method; ?>"
<?php foreach($params as $key => $value): ?>
data-param-<?php echo $key; ?>="<?php echo $value; ?>"
<?php endforeach; ?>
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}})"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-event="load"
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>
</div>
<div data-ls-if="0 != {{logs.total}}">
<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>
<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="<?php echo $method; ?>"
<?php foreach($params as $key => $value): ?>
data-param-<?php echo $key; ?>="<?php echo $value; ?>"
<?php endforeach; ?>
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-scope="sdk"
data-name="logs"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-offset="{{router.params.offset}}" data-total="{{logs.total}}" class="margin-end-small round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{logs.total|pageTotal}}"></span>
<form
data-service="<?php echo $method; ?>"
<?php foreach($params as $key => $value): ?>
data-param-<?php echo $key; ?>="<?php echo $value; ?>"
<?php endforeach; ?>
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-scope="sdk"
data-name="logs"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-offset="{{router.params.offset}}" data-total="{{logs.total}}" class="margin-start-small round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
</div>
</div>

View file

@ -1,113 +0,0 @@
<?php
use Utopia\Database\Database;
$method = $this->getParam('method', '');
$params = $this->getParam('params', []);
$events = $this->getParam('events', '');
$permissions = $this->getParam('permissions', Database::PERMISSIONS);
$data = $this->getParam('data', '');
$form = $this->getParam('form');
$escapedPermissions = \array_map(function ($perm) {
// Alpine won't bind to a parameter named delete
if ($perm == 'delete') {
return 'xdelete';
}
return $perm;
}, $permissions);
?>
<div
x-data="permissionsMatrix"
class="permissions-matrix margin-bottom-large"
data-scope="sdk"
<?php if (!empty($method)): ?>
data-method="<?php echo $method; ?>"
<?php endif; ?>
<?php foreach ($params as $key => $value): ?>
data-param-<?php echo $key; ?>="<?php echo $value; ?>"
<?php endforeach; ?>
<?php if (!empty($events)): ?>
data-events="<?php echo $events; ?>"
<?php endif; ?>
<?php if (!empty($data)): ?>
data-name="<?php echo $data; ?>"
<?php endif; ?>
@reset.window="permissions.length = rawPermissions.length = 0">
<input
type="hidden"
name="permissions"
data-cast-from="csv"
data-cast-to="array"
<?php if (!empty(($data))): ?>
data-ls-bind="{{<?php echo $data ?>.$permissions}}"
<?php endif; ?>
:value="rawPermissions"/>
<datalist id="types">
<option value="user:">
<option value="team:">
<option value="member:">
<option value="users">
<option value="guests">
<option value="any">
</datalist>
<table
class="u-table-layout-normal"
data-ls-attrs="x-init=load({{<?php if (!empty($data)) echo $data . '.$permissions' ?>}})">
<thead x-show="permissions.length > 0">
<tr>
<th>Role</th>
<?php foreach ($permissions as $permission): ?>
<th class="u-no-trim"><?php echo \ucfirst($permission); ?></th>
<?php endforeach; ?>
<th></th>
</tr>
</thead>
<tbody>
<template x-for="(permission, index) in permissions">
<tr>
<td>
<input
required
autocomplete="off"
:id="'<?php echo $form; ?>Input' + index"
name="<?php echo $form; ?>"
form="<?php echo $form ?>"
list="types"
type="text"
x-model="permission.role"
@keydown.enter="prevent($event)"
@keydown="clearPermission(index)"
@keyup="updatePermission(index)"/>
</td>
<?php foreach ($escapedPermissions as $permission): ?>
<td>
<input
type="checkbox"
name="<?php echo $permission ?>"
x-model="permission.<?php echo $permission; ?>"
@click="updatePermission(index)"/>
</td>
<?php endforeach; ?>
<td>
<span class="action" @click="removePermission(index)">
<i class="icon-trash"></i>
</span>
</td>
</tr>
</template>
</tbody>
<tfoot>
<tr>
<td colspan="<?php \count($permissions) + 2 ?>">
<button type="button" class="margin-top reverse" @click="addPermission('<?php echo $form ?>')">Add Role</button>
</td>
</tr>
</tfoot>
</table>
</div>

View file

@ -1,6 +0,0 @@
<div x-data id="upload-modal">
<template x-for="$store.uploader.files as file">
<p x-text="file"></p>
</template>
<button @click="$store.uploader.addFile('file-x')">add</button>
</div>

File diff suppressed because it is too large Load diff

View file

@ -1,323 +0,0 @@
<div
data-service="databases.get"
data-param-database-id="{{router.params.id}}"
data-scope="sdk"
data-event="load,databases.update"
data-name="project-database">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/databases?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Database</a>
<br />
<span data-ls-bind="{{project-database.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-database}}" 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/databases/database?id={{router.params.id}}&project={{router.params.project}}">
<h2>Collections</h2>
<div class="margin-top"
data-service="databases.listCollections"
data-event="load,databases.createCollection,databases.updateCollection,databases.deleteCollection"
data-param-database-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}})"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-collections">
<div data-ls-if="(!{{project-collections.total}})" 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 Collections Found</h3>
<p class="margin-bottom-no">You haven't created any collections for your project yet.</p>
</div>
</div>
<div data-ls-if="0 != {{project-collections.total}}">
<ul data-ls-loop="project-collections.collections" data-ls-as="collection" data-ls-append="" class="tiles cell-3 margin-bottom-small">
<li class="margin-bottom">
<a data-ls-attrs="href=/console/databases/collection?id={{collection.$id}}&databaseId={{router.params.id}}&project={{router.params.project}}" class="box">
<div data-ls-bind="{{collection.name}}" class="text-one-liner margin-bottom text-bold">&nbsp;</div>
<i class="icon-right-open"></i>
</a>
</li>
</ul>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="databases.listCollections"
data-param-database-id="{{router.params.id}}"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-collections"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" 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-collections.total|pageTotal}}"></span>
<form
data-service="databases.listCollections"
data-param-database-id="{{router.params.id}}"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-collections"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Collection">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>New Collection</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Database Collection"
data-service="databases.createCollection"
data-event="submit"
data-scope="sdk"
data-success="alert,reset,redirect,trigger"
data-success-param-alert-text="Collection created successfully"
data-success-param-redirect-url="/console/databases/collection/settings?id={{serviceData.$id}}&databaseId={{router.params.id}}&project={{router.params.project}}"
data-success-param-trigger-events="databases.createCollection"
data-failure="alert"
data-failure-param-alert-text="Failed to create collection"
data-failure-param-alert-classname="error">
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.id}}" />
<label for="collection-id">Collection ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="databases.getCollection"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
name="collectionId" />
<label for="collection-name">Name</label>
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
<input type="hidden" id="collection-permissions" name="permissions" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="collection-documentSecurity" name="documentSecurity" required data-cast-to="boolean" value="false" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</li>
<li data-state="/console/databases/database/usage?project={{router.params.project}}&id={{router.params.id}}">
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="databases.getDatabaseUsage"
data-event="submit"
data-name="usage"
data-param-database-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="databases.getDatabaseUsage"
data-param-database-id="{{router.params.id}}"
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>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="databases.getDatabaseUsage"
data-event="submit"
data-name="usage"
data-param-database-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="databases.getDatabaseUsage"
data-param-database-id="{{router.params.id}}"
data-event="load"
data-name="usage">
<h3 class="margin-bottom-tiny">Objects</h3>
<p class="text-fade">Count of collections and documents 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-forms-chart="Collections=collectionsCount,Documents=documentsCount"
data-show-y-axis="true"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Total Collections <span data-ls-bind="({{usage.collectionsCount|statsGetLast|statsTotal}})"></span></li>
<li>Total Documents <span data-ls-bind="({{usage.documentsCount|statsGetLast|statsTotal}})"></span></li>
</ul>
<h3 class="margin-bottom-tiny">Collections</h3>
<p class="text-fade">Count of collections 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-forms-chart="Created=collectionsCreate,Read=collectionsRead,Updated=collectionsUpdate,Deleted=collectionsDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
<h3 class="margin-bottom-tiny">Documents</h3>
<p class="text-fade">Count of documents 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-forms-chart="Created=documentsCreate,Read=documentsRead,Updated=documentsUpdate,Deleted=documentsDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
</div>
</li>
<li data-state="/console/databases/database/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 Database"
data-service="databases.update"
data-scope="sdk"
data-event="submit"
data-param-database-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated database successfully"
data-success-param-trigger-events="databases.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update database"
data-failure-param-alert-classname="error">
<label>&nbsp;</label>
<div class="box">
<label for="database-name">Name</label>
<input name="name" id="database-name" type="text" autocomplete="off" data-ls-bind="{{project-database.name}}" data-forms-text-direction required placeholder="Database Name" maxlength="128" />
<hr class="margin-top-no" />
<button>Update</button>
</form>
</div>
</div>
<div class="col span-4 sticky-top">
<label>Database ID</label>
<div class="input-copy margin-bottom">
<input id="id" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-database.$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-database.$updatedAt|date}}"></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-database.$createdAt|date}}"></span></li>
</ul>
<form
name="databases.delete" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Database"
data-service="databases.delete"
data-event="submit"
data-param-database-id="{{router.params.id}}"
data-confirm="Are you sure you want to delete this Database?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Database deleted successfully"
data-success-param-trigger-events="databases.delete"
data-success-param-redirect-url="/console/databases?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete database"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Database</button>
</form>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>

View file

@ -1,425 +0,0 @@
<?php
$new = $this->getParam('new', false);
$logs = $this->getParam('logs', null);
$permissions = $this->getParam('permissions', null);
?>
<div
data-service="databases.getCollection"
data-param-collection-id="{{router.params.collectionId}}"
data-param-database-id="{{router.params.databaseId}}"
data-scope="sdk"
data-event="load,databases.updateDocument"
data-name="project-collection">
<div
data-service="databases.getDocument"
data-param-collection-id="{{router.params.collectionId}}"
data-param-database-id="{{router.params.databaseId}}"
data-param-document-id="{{router.params.id}}"
data-scope="sdk"
data-event="load"
data-name="project-document"
data-success="default">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/databases/collection?id={{router.params.collectionId}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> <span data-ls-bind="{{project-collection.name}}"></span></a>
<br />
<span data-ls-if="({{project-document.$id}})" data-ls-bind="Document">&nbsp;&nbsp;</span>
<span data-ls-if="(!{{project-document.$id}})" data-ls-bind="Document">&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-document}}" data-forms-code />
</div>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
<div class="zone xl margin-bottom-no">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/databases/document?id={{router.params.id}}&collectionId={{router.params.collectionId}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
<h2 class="margin-bottom">Overview</h2>
<div class="row responsive">
<div class="col span-8 margin-bottom">
<form id="<?php echo $permissions->getParam('form') ?>"></form>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-service="{{|documentAction}}"
data-name="project-document"
data-scope="sdk"
data-event="submit"
data-failure="alert"
data-failure-param-alert-classname="error"
<?php if($new): ?>
data-analytics-label="Create Database Document"
data-success="trigger,redirect"
data-success-param-trigger-events="databases.createDocument"
data-success-param-redirect-url="/console/databases/collection?id={{project-collection.$id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}"
data-failure-param-alert-text="Failed to create document"
<?php else: ?>
data-analytics-label="Update Database Document"
data-success="trigger,alert"
data-success-param-trigger-events="databases.updateDocument"
data-success-param-alert-text="Your document was updated"
data-failure-param-alert-text="Failed to update document"
<?php endif; ?>
>
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
<?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="databases.getDocument"
required
maxlength="36"
name="documentId"
id="documentId"
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$" />
<?php endif; ?>
<fieldset name="data" data-cast-to="object" data-ls-attrs="x-init=doc = {{project-document}}" x-data="{doc: {}}">
<ul data-ls-attrs="x-init=attributes = {{project-collection.attributes}}.map(function(attr) { if(attr.type === 'datetime' && doc[attr.key]) { doc[attr.key] = isoToLocal(doc[attr.key]) } return attr; })" x-data="{attributes: [], isoToLocal(isoTime) { const date = new Date(isoTime); const localTime = date.toLocaleString('sv').slice(0, 16).replace(' ', 'T'); return localTime; } }">
<template x-for="attr in attributes.filter(a => a.status === 'available')">
<li>
<label>
<div x-text="attr.key" class="margin-bottom-tiny"></div>
<span x-show="attr.required" class="text-size-xs text-danger text-fade">required</span>
<span x-show="!attr.required" class="text-size-xs text-fade">optional</span>
</label>
<template x-if="!attr.array">
<div>
<template x-if="attr.type === 'integer'">
<input
type="number"
step="1"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key]"
data-cast-to="integer" />
</template>
<template x-if="attr.type === 'double'">
<input
type="number"
step="any"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key]"
data-cast-to="float" />
</template>
<template x-if="attr.type === 'boolean'">
<input
class="button switch margin-bottom"
type="checkbox"
:name="attr.key"
:checked="doc[attr.key]" />
</template>
<template x-if="attr.type === 'datetime'">
<input
type="datetime-local"
step=".001"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key]"
data-cast-to="string-datetime" />
</template>
<template x-if="attr.type === 'string' && !attr.format">
<textarea
data-forms-text-resize
data-forms-text-direction
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:maxlength="attr.size"
x-model="doc[attr.key]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'ip'">
<textarea
data-forms-text-resize
data-forms-text-direction
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*$))"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'email'">
<textarea
data-forms-text-resize
data-forms-text-direction
type="email"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'url'">
<textarea
data-forms-text-resize
data-forms-text-direction
type="url"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'enum'">
<select
:required="attr.required"
:name="attr.key"
data-cast-to="string">
<option :disabled="attr.required" selected label=" "></option>
<template x-for="element in attr.elements">
<option
:value="element"
x-text="element"
:selected="doc[attr.key] === element"></option>
</template>
</select>
</template>
</div>
</template>
<template x-if="attr.array">
<div>
<input type="hidden" :required="attr.required" :name="attr.key" data-cast-to="array-empty">
<template x-for="(node, index) in doc[attr.key]">
<div class="row responsive thin margin-bottom-tiny">
<div class="col span-11 margin-bottom-small">
<template x-if="attr.type === 'integer'">
<input
type="number"
step="1"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key][index]"
data-cast-to="integer" />
</template>
<template x-if="attr.type === 'double'">
<input
type="number"
step="any"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:min="attr.min"
:max="attr.max"
x-model="doc[attr.key][index]"
data-cast-to="float" />
</template>
<template x-if="attr.type === 'boolean'">
<input
class="button switch"
:class="(doc[attr.key].length - 1) === index ? 'margin-bottom' : ''"
type="checkbox"
:name="attr.key"
:value="attr.key"
:checked="doc[attr.key][index]" />
</template>
<template x-if="attr.type === 'datetime'">
<input
type="datetime-local"
step=".001"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key][index]"
data-cast-to="string-datetime" />
</template>
<template x-if="attr.type === 'string' && !attr.format">
<textarea
data-forms-text-resize
data-forms-text-direction
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
:maxlength="attr.size"
x-model="doc[attr.key][index]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'ip'">
<textarea
data-forms-text-resize
data-forms-text-direction
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*$))"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key][index]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'email'">
<textarea
data-forms-text-resize
data-forms-text-direction
type="email"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key][index]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'url'">
<textarea
data-forms-text-resize
data-forms-text-direction
type="url"
:placeholder="attr.default"
:name="attr.key"
:required="attr.required"
x-model="doc[attr.key][index]"
data-cast-to="string"></textarea>
</template>
<template x-if="attr.format === 'enum'">
<select
:required="attr.required"
:name="attr.key"
data-cast-to="string">
<option :disabled="attr.required" selected label=" "></option>
<template x-for="element in attr.elements">
<option
:value="element"
x-text="element"
:selected="doc[attr.key][index] === element"></option>
</template>
</select>
</template>
</div>
<div class="col span-1 margin-bottom-small">
<button type="button" class="dark danger small round pull-end" style="margin-top: 10px;" @click="doc[attr.key].splice(index, 1)"><i class="icon-cancel"></i></button>
</div>
</div>
</template>
<button type="button" class="margin-end margin-bottom-small reverse" @click="doc = addAttribute(doc, attr.key)"> Add Attribute </button>
</div>
</template>
</li>
</template>
</ul>
</fieldset>
<div class="toggle margin-bottom" data-ls-if="{{project-collection.documentSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<h3 class="margin-bottom-large">Permissions</h3>
<?php echo $permissions->render() ?>
</div>
<button data-ls-if="({{project-document.$id}})">Update</button>
<button data-ls-if="(!{{project-document.$id}})">Create</button>
</div>
</form>
</div>
<div class="col span-4 sticky-top">
<div data-ls-if="({{project-document.$id}})">
<label>Document ID</label>
<div class="input-copy margin-bottom">
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-document.$id}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
</div>
<label>Collection ID</label>
<div class="input-copy margin-bottom">
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.collectionId}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<label>Database ID</label>
<div class="input-copy margin-bottom">
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.databaseId}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<ul class="margin-bottom-large text-fade text-size-small" data-ls-if="({{project-document.$id}})">
<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"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Document)">
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-document.$updatedAt|date}}"></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-document.$createdAt|date}}"></span></li>
</ul>
<div data-ls-if="({{project-document.$id}})">
<form name="databases.deleteDocument" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Document"
data-service="databases.deleteDocument"
data-event="submit"
data-param-database-id="{{router.params.databaseId}}"
data-param-collection-id="{{router.params.collectionId}}"
data-param-document-id="{{project-document.$id}}"
data-confirm="Are you sure you want to delete this document?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Document deleted successfully"
data-success-param-trigger-events="databases.deleteDocument"
data-success-param-redirect-url="/console/databases/collection?id={{router.params.collectionId}}&project={{router.params.project}}&databaseId={{router.params.databaseId}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete collection"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Document</button>
</form>
</div>
</div>
</div>
</li>
<?php if(!$new): ?>
<li data-state="/console/databases/document/activity?id={{router.params.id}}&collectionId={{router.params.collectionId}}&project={{router.params.project}}&databaseId={{router.params.databaseId}}">
<h2>Activity</h2>
<?php echo $logs->render(); ?>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</div>

View file

@ -1,94 +0,0 @@
<?php
use Appwrite\Utopia\View;
$collection = $this->getParam('collection', null);
$collections = $this->getParam('collections', []);
$rules = $collection->getAttribute('rules', []);
$key = $this->getParam('key', null);
$type = $this->getParam('type', null);
$parent = $this->getParam('parent', true);
$namespace = $this->getParam('namespace', 'project-document');
$array = $this->getParam('array', false);
?>
<?php if($parent): ?>
<input name="documentId" type="hidden" data-ls-bind="{{router.params.id}}" />
<input name="collectionId" type="hidden" data-ls-bind="{{router.params.collectionId}}" />
<?php else: ?>
<?php /*<div class="margin-bottom-small text-size-small" data-ls-if="({{<?php echo $this->escape($namespace); ?>.$id}})">
<span data-ls-bind="Document #{{<?php echo $this->escape($namespace); ?>.$id}}"></span> &nbsp;
<a data-ls-attrs="href=/console/database/document?id={{<?php echo $this->escape($namespace); ?>.$id}}&collection={{<?php echo $this->escape($namespace); ?>.$collection}}&project={{router.params.project}}" class="pull-end" target="_blank">Edit in a new window <i class="icon-link-ext"></i></a>
</div>
<div class="margin-bottom-small text-size-small" data-ls-if="(!{{<?php echo $this->escape($namespace); ?>.$id}})">
Create a new child document
</div> */ ?>
<?php endif; ?>
<fieldset name="<?php echo $this->escape($key); ?>"<?php if(!$array): ?> data-cast-to="object"<?php endif; ?> class="<?php if($type === 'document'): ?> info<?php endif; ?>">
<?php if(!$parent): ?>
<input name="$id" type="hidden" data-ls-bind="{{<?php echo $this->escape($namespace); ?>.$id}}" />
<input name="$collection" type="hidden" data-ls-bind="<?php echo $this->escape($collection->getId()); ?>" />
<input name="$permissions" type="hidden" data-ls-bind="{{<?php echo $this->escape($namespace); ?>.$permissions}}" data-cast-to="json" />
<?php endif; ?>
<ul>
<?php foreach($rules as $rule):
$key = $rule['key'] ?? '';
$label = $rule['label'] ?? '';
$type = $rule['type'] ?? '';
$array = $rule['array'] ?? false;
$required = $rule['required'] ?? false;
$list = $rule['list'] ?? false;
$comp = new View(__DIR__.'/rules/'.$type.'.phtml');
$loop = new View(__DIR__.'/rules/array.phtml');
$comp
->setParam('key', $key)
->setParam('array', $array)
->setParam('required', $required)
->setParam('list', $list)
->setParam('namespace', $namespace.'.'.$key)
->setParam('collections', $collections)
;
$loop
->setParam('type', $type)
->setParam('key', $key)
->setParam('array', $array)
->setParam('required', $required)
->setParam('list', $list)
->setParam('namespace', $namespace.'.'.$key)
->setParam('comp', $comp)
->setParam('collections', $collections)
;
?>
<li>
<label class="margin-bottom-no<?php if($type === 'document'): ?> margin-top-large<?php endif; ?>"<?php if($parent): ?> data-forms-nav-anchor="<?php echo $this->escape($key); ?>"<?php endif; ?>>
<?php echo $this->escape($label); ?>
<?php if($array): ?>
<span class="text-size-small text-fade">&nbsp;(Array)</span>
<?php endif; ?>
<?php if($required): ?>
<div class="text-size-xs text-danger text-fade">&nbsp;required</div>
<?php endif; ?>
<?php if(!$required): ?>
<div class="text-size-xs text-fade">&nbsp;optional</div>
<?php endif; ?>
</label>
<div class="margin-top-small margin-bottom">
<?php if(!$array): ?>
<?php echo $comp->render(); ?>
<?php else: ?>
<?php echo $loop->render(); ?>
<?php endif; ?>
</div>
</li>
<?php endforeach; ?>
</ul>
</fieldset>

View file

@ -1,244 +0,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>
<br />
<span>Database</span>
</h1>
</div>
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/databases?project={{router.params.project}}">
<h2>Databases</h2>
<div class="margin-top"
data-service="databases.list"
data-event="load,databases.create,databases.update,databases.delete"
data-param-search="{{router.params.search}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}})"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-databases">
<div data-ls-if="(!{{project-databases.total}})" 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 Databases Found</h3>
<p class="margin-bottom-no">You haven't created any databases for your project yet.</p>
</div>
</div>
<div data-ls-if="0 != {{project-databases.total}}">
<ul data-ls-loop="project-databases.databases" data-ls-as="database" data-ls-append="" class="tiles cell-3 margin-bottom-small">
<li class="margin-bottom">
<a data-ls-attrs="href=/console/databases/database?id={{database.$id}}&project={{router.params.project}}" class="box">
<div data-ls-bind="{{database.name}}" class="text-one-liner margin-bottom text-bold">&nbsp;</div>
<i class="icon-right-open"></i>
</a>
</li>
</ul>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="databases.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-databases"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-databases.total}}" 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-databases.total|pageTotal}}"></span>
<form
data-service="databases.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-databases"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-databases.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Database">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>New Database</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Database"
data-service="databases.create"
data-event="submit"
data-scope="sdk"
data-success="alert,reset,redirect,trigger"
data-success-param-alert-text="Database created successfully"
data-success-param-redirect-url="/console/databases/database?id={{serviceData.$id}}&project={{router.params.project}}"
data-success-param-trigger-events="databases.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create database"
data-failure-param-alert-classname="error">
<label for="database-id">Database ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="databases.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
name="databaseId" />
<label for="database-name">Name</label>
<input type="text" class="full-width" id="database-name" name="name" required autocomplete="off" maxlength="128" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</li><!-- TODO need to work on usage -->
<li data-state="/console/databases/usage?project={{router.params.project}}">
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="databases.getUsage"
data-event="submit"
data-name="usage"
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="databases.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>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="databases.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>
<h2>Usage</h2>
<div
data-service="databases.getUsage"
data-event="load"
data-name="usage">
<h3 class="margin-bottom-tiny">Objects</h3>
<p class="text-fade">Count of collections and documents 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-forms-chart="Databases=databasesCount,Collections=collectionsCount,Documents=documentsCount"
data-show-y-axis="true"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Total Databases <span data-ls-bind="({{usage.databasesCount|statsGetLast|statsTotal}})"></span></li>
<li>Total Collections <span data-ls-bind="({{usage.collectionsCount|statsGetLast|statsTotal}})"></span></li>
<li>Total Documents <span data-ls-bind="({{usage.documentsCount|statsGetLast|statsTotal}})"></span></li>
</ul>
<h3 class="margin-bottom-tiny">Databases</h3>
<p class="text-fade">Count of databases 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-forms-chart="Created=databasesCreate,Read=databasesRead,Updated=databasesUpdate,Deleted=databasesDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
<h3 class="margin-bottom-tiny">Collections</h3>
<p class="text-fade">Count of collections 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-forms-chart="Created=collectionsCreate,Read=collectionsRead,Updated=collectionsUpdate,Deleted=collectionsDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
<h3 class="margin-bottom-tiny">Documents</h3>
<p class="text-fade">Count of documents 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-forms-chart="Created=documentsCreate,Read=documentsRead,Updated=documentsUpdate,Deleted=documentsDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
</div>
</li>
</ul>
</div>
</div>

View file

@ -1,921 +0,0 @@
<?php
$fileLimit = $this->getParam('fileLimit', 0);
$fileLimitHuman = $this->getParam('fileLimitHuman', 0);
$events = $this->getParam('events', []);
$timeout = $this->getParam('timeout', 900);
$usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
$patterns = [
'documents',
'documents.create',
'documents.update',
'documents.delete',
];
foreach ($events as $name => $event) {
$patterns[] = $name;
foreach ($event as $key => $value) {
if (!\str_starts_with($key, '$')) {
if (!($value['$resource'] ?? false)) {
$patterns[] = "{$name}.{$key}";
} else {
$patterns[] = $key;
foreach ($value as $key2 => $value2) {
if (!\str_starts_with($key2, '$')) {
if (!($value2['$resource'] ?? false)) {
$patterns[] = "{$key}.{$key2}";
}
}
}
}
}
}
}
sort($patterns);
?>
<div
data-service="functions.get"
data-name="project-function"
data-event="load,functions.update,functions.createDeployment,functions.updateDeployment,functions.deleteDeployment,functions.retryBuild"
data-param-function-id="{{router.params.id}}"
data-success="trigger"
data-success-param-trigger-events="functions.get">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/functions?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Functions</a>
<br />
<span data-ls-bind="{{project-function.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-function}}" 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/functions/function?id={{router.params.id}}&project={{router.params.project}}">
<h2>Overview</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<label>&nbsp;</label>
<div class="box margin-bottom-large">
<div class="text-align-center">
<img src="" data-ls-attrs="src=/images/runtimes/{{project-function.runtime|runtimeLogo}}" alt="Function Runtime" class="avatar huge margin-top-negative-xxl" />
<p class="text-fade margin-bottom-small" data-ls-bind="{{project-function.runtime|runtimeName}} {{project-function.runtime|runtimeVersion}}">
</p>
<div data-ls-if="{{project-function.deployment}} !== ''" class="margin-top">
<button data-ls-ui-trigger="execute-now">Execute Now</button> &nbsp; <a data-ls-attrs="href=/console/functions/function/logs?id={{router.params.id}}&project={{router.params.project}}" class="button reverse" style="vertical-align: top;">View Logs</a>
</div>
</div>
</div>
<div class="margin-bottom"
data-service="functions.listDeployments"
data-scope="sdk"
data-event="load,functions.createDeployment,functions.deleteDeployment,functions.updateDeployment,functions.retryBuild"
data-name="project-function-deployments"
data-param-function-id="{{router.params.id}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-success="trigger"
data-success-param-trigger-events="functions.listDeployments">
<h3>Deployments <span class="text-fade text-size-small pull-end margin-top-small" data-ls-bind="{{project-function-deployments.total}} deployments found"></span></h3>
<div data-ls-if="0 == {{project-function-deployments.deployments.length}} || undefined == {{project-function-deployments.deployments.length}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Deployments Found</h3>
<p class="margin-bottom-no">You haven't deployed any deployments for your function yet.</p>
</div>
<div data-ls-if="(0 != {{project-function-deployments.deployments.length}}) && (undefined !== {{project-function-deployments.deployments.length}})">
<div class="box margin-bottom">
<ul data-ls-loop="project-function-deployments.deployments" data-ls-as="deployment" class="list">
<li class="clear">
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-stderr-{{deployment.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Build Error Log</h2>
<div class="margin-bottom">
<input type="hidden" data-ls-bind="{{deployment.buildStderr}}" data-forms-code="bash" data-lang="bash" data-lang-label="Bash" />
</div>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-stdout-{{deployment.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Build Log</h2>
<div class="margin-bottom">
<input type="hidden" data-ls-bind="{{deployment.buildStdout}}" data-forms-code="bash" data-lang="bash" data-lang-label="Bash" />
</div>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
<form data-ls-if="({{deployment.$id}} !== {{project-function.deployment}} && {{deployment.status}} == 'ready')" name="functions.updateDeployment" class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Function Execution"
data-service="functions.updateDeployment"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Deployment updated successfully"
data-success-param-trigger-events="functions.updateDeployment"
data-failure="alert"
data-failure-param-alert-text="Failed to update deployment"
data-failure-param-alert-classname="error">
<input type="hidden" name="deploymentId" data-ls-bind="{{deployment.$id}}">
<button>Activate</button>
</form>
<form data-ls-if="({{deployment.$id}} !== {{project-function.deployment}} && {{deployment.status}} == 'failed')" name="functions.retryBuild" class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Retry Build"
data-service="functions.retryBuild"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Build started successfully"
data-success-param-trigger-events="functions.retryBuild"
data-failure="alert"
data-failure-param-alert-text="Failed to retry build"
data-failure-param-alert-classname="error">
<input type="hidden" name="buildId" data-ls-bind="{{deployment.buildId}}">
<input type="hidden" name="functionId" data-ls-bind="{{router.params.id}}">
<input type="hidden" name="deploymentId" data-ls-bind="{{deployment.$id}}">
<button>Retry Build</button>
</form>
<b data-ls-bind="{{deployment.$id}}"></b> &nbsp;
<span class="text-fade" data-ls-bind="{{deployment.entrypoint}}"></span>
<div class="text-size-small margin-top-small clear">
<span data-ls-if="{{deployment.status}} == 'failed'" style="color: var(--config-color-danger)" class="pull-start" data-ls-bind="{{deployment.status}}"></span>
<span data-ls-if="{{deployment.status}} == 'ready'" style="color: var(--config-color-success)" class="pull-start" data-ls-bind="{{deployment.status}}"></span>
<span data-ls-if="{{deployment.status}} == 'processing' || {{deployment.status}} == 'building'" style="color: var(--config-color-info)" class="pull-start" data-ls-bind="{{deployment.status}}"></span>
<span class="pull-start" data-ls-bind="&nbsp; | &nbsp; Created {{deployment.$createdAt|timeSince}} &nbsp; | &nbsp; {{deployment.size|humanFileSize}}{{deployment.size|humanFileUnit}}"></span>
<span data-ls-if="{{deployment.status}} == 'failed'">&nbsp; | &nbsp;<button type="button" class="link text-size-small" data-ls-ui-trigger="open-stderr-{{deployment.$id}}">Logs</button></span>
<span data-ls-if="{{deployment.status}} == 'ready'">&nbsp; | &nbsp;<button type="button" class="link text-size-small" data-ls-ui-trigger="open-stdout-{{deployment.$id}}">Logs</button></span>
<form data-ls-if="{{deployment.$id}} !== {{project-function.deployment}}" name="functions.deleteDeployment" class="pull-start"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Function Deployment"
data-service="functions.deleteDeployment"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-confirm="Are you sure you want to delete this function deployment?"
data-success="alert,trigger"
data-success-param-alert-text="Function deployment deleted successfully"
data-success-param-trigger-events="functions.deleteDeployment"
data-failure="alert"
data-failure-param-alert-text="Failed to delete function deployment"
data-failure-param-alert-classname="error">
<input type="hidden" name="deploymentId" data-ls-bind="{{deployment.$id}}" />
&nbsp; | &nbsp;<button type="submit" class="link text-danger text-size-small">Delete</button>
</form>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="pull-start">
<button data-ls-ui-trigger="create-deployment">Create Deployment</button>
</div>
<div class="pull-end paging">
<form
data-service="functions.listDeployments"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-function-deployments"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-function-deployments.total}}" 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-function-deployments.total|pageTotal}}"></span>
<form
data-service="functions.listDeployments"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-function-deployments"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-function-deployments.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
</div>
<div class="col span-4 sticky-top margin-bottom">
<label>Function ID</label>
<div class="input-copy margin-bottom">
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-function.$id}}" disabled data-forms-copy>
</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"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Function)">
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-function.$updatedAt|date}}"></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-function.$createdAt|date}}"></span></li>
</ul>
<form name="functions.delete" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Function"
data-service="functions.delete"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-confirm="Are you sure you want to delete this function?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Function deleted successfully"
data-success-param-trigger-events="functions.delete"
data-success-param-redirect-url="/console/functions?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete function"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Function</button>
</form>
</div>
</div>
</li>
<?php if ($usageStatsEnabled): ?>
<li data-state="/console/functions/function/monitors?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="functions.getFunctionUsage"
data-event="submit"
data-name="usage"
data-param-function-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="functions.getFunctionUsage"
data-event="submit"
data-name="usage"
data-param-range="30d"
data-param-function-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="functions.getFunctionUsage"
data-event="submit"
data-name="usage"
data-param-function-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>Monitors</h2>
<div
data-service="functions.getFunctionUsage"
data-event="load"
data-name="usage"
data-param-function-id="{{router.params.id}}">
<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="Executions=executionsTotal" data-height="140" data-show-y-axis="true" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Executions <span data-ls-bind="({{usage.executionsTotal|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="CPU Time (milliseconds)=executionsTime" data-colors="orange" data-height="140" data-show-y-axis="true" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li class="orange">CPU Time <span data-ls-bind="({{usage.executionsTime|statsGetLast|ms2hum}})"></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="Failures=executionsFailure" data-colors="red" data-height="140" data-show-y-axis="true" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li class="red">Errors <span data-ls-bind="({{usage.executionsFailure|statsGetLast|statsTotal}})"></span></li>
</ul>
</div>
</li>
<?php endif;?>
<li data-state="/console/functions/function/logs?id={{router.params.id}}&project={{router.params.project}}">
<div class="text-fade text-size-small pull-end margin-top" data-ls-bind="{{project-function-executions.total}} executions found"></div>
<h2>Logs</h2>
<div
data-service="functions.listExecutions"
data-scope="sdk"
data-event="load,functions.createExecution"
data-name="project-function-executions"
data-param-function-id="{{router.params.id}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-success="trigger"
data-success-param-trigger-events="functions.listExecutions">
<div data-ls-if="0 < {{project-function-executions.executions.length}} && undefined !== {{project-function-executions.executions}}">
<div class="box margin-bottom">
<table class="vertical small">
<thead>
<tr>
<th width="30"></th>
<th width="160">Created</th>
<th width="100">Status</th>
<th width="80">Trigger</th>
<th width="60">Runtime</th>
<th width=""></th>
</tr>
</thead>
<tbody data-ls-loop="project-function-executions.executions" data-ls-as="execution">
<tr>
<td data-title="">
<i class="dot danger" data-ls-if="{{execution.status}} === 'failed'"></i>
<i class="dot info" data-ls-if="{{execution.status}} === 'waiting'"></i>
<i class="dot info" data-ls-if="{{execution.status}} === 'processing'"></i>
<i class="dot success" data-ls-if="{{execution.status}} === 'completed'"></i>
</td>
<td data-title="Date: ">
<span data-ls-bind="{{execution.$createdAt|dateTime}}"></span>
</td>
<td data-title="Status: ">
<span data-ls-bind="{{execution.status}}"></span>
<span class="text-fade text-size-small" data-ls-if="{{execution.statusCode}} < 200 && {{execution.statusCode}} >= 300" data-ls-bind=" exit code: {{execution.statusCode}}"></span>
</td>
<td data-title="Trigger: ">
<span data-ls-bind="{{execution.trigger}}"></span>
</td>
<td data-title="Time: ">
<span data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-ls-bind="{{execution.duration|seconds2hum}}"></span>
<span data-ls-if="{{execution.status}} === 'waiting' || {{execution.status}} === 'processing'">-</span>
</td>
<td data-title="">
<div data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-title="" style="display: flex;">
<button class="desktops-only pull-end link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="phones-only-inline tablets-only-inline link text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-response-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>RESPONSE</h1>
<div class="margin-bottom ide" data-ls-if="({{execution.response.length}})">
<pre data-ls-bind="{{execution.response}}"></pre>
</div>
<div class="margin-bottom" data-ls-if="(!{{execution.response.length}})">
<p>No Response was logged.</p>
</div>
</div>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stdout-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>STDOUT</h1>
<div class="margin-bottom ide" data-ls-if="({{execution.stdout.length}})">
<pre data-ls-bind="{{execution.stdout}}"></pre>
</div>
<div class="margin-bottom" data-ls-if="(!{{execution.stdout.length}})">
<p>No output was logged.</p>
</div>
</div>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stderr-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>STDERR</h1>
<div class="margin-bottom ide" data-ls-if="({{execution.stderr.length}})">
<pre data-ls-bind="{{execution.stderr}}"></pre>
<!-- <input type="hidden" data-ls-bind="{{execution.stderr}}" data-forms-code="bash" /> -->
</div>
<div class="margin-bottom" data-ls-if="(!{{execution.stderr.length}})">
<p>No errors were logged.</p>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="text-align-center paging">
<form
data-service="functions.listExecutions"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-function-executions"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-function-executions.total}}" 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-function-executions.total|pageTotal}}"></span>
<form
data-service="functions.listExecutions"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-function-executions"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-function-executions.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
</div>
<div data-ls-if="(!{{project-function-executions.executions.length}})" 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 Logs Founds</h3>
<p class="margin-bottom-no">Execute your function to view execution logs.</p>
</div>
</div>
</div>
</li>
<li data-state="/console/functions/function/variables?id={{router.params.id}}&project={{router.params.project}}">
<h2
data-service="functions.listVariables"
data-event="load,project.update,functions.createVariable,functions.updateVariable,functions.deleteVariable"
data-name="function-variables"
data-param-function-id="{{router.params.id}}"
data-scope="sdk">Variables</h2>
<div class="box margin-bottom" data-ls-if="0 < {{function-variables.variables.length}} && undefined !== {{function-variables.variables}}">
<ul data-ls-loop="function-variables.variables" data-ls-as="variable" class="list">
<li class="clear">
<div class="pull-end desktops-only">
<button data-ls-ui-trigger="variable-delete-{{variable.$id}}" class="reverse danger margin-end-small">Delete</button>
<button data-ls-ui-trigger="variable-update-{{variable.$id}}">Update</button>
</div>
<div data-ui-modal data-button-hide="on" data-open-event="variable-update-{{variable.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update Variable</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="sdk"
data-analytics-label="Update Function Variable"
data-service="functions.updateVariable"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated variable successfully"
data-success-param-trigger-events="functions.updateVariable"
data-failure="alert,trigger"
data-failure-param-trigger-events="functions.updateVariable"
data-failure-param-alert-text="Failed to update variable"
data-failure-param-alert-classname="error">
<input type="hidden" name="functionId" data-ls-bind="{{router.params.id}}" />
<input type="hidden" name="variableId" data-ls-bind="{{variable.$id}}" />
<label for="name">Name <span class="tooltip large" data-tooltip="The name of the environment variable you want to set"><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="APP_ENV" maxlength="128" data-ls-attrs="id=key-{{variable.$id}}" data-ls-bind="{{variable.key}}" />
<label for="key">Value <span class="tooltip large" data-tooltip="The value of your environment variable"><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="value" required autocomplete="off" placeholder="Production" data-ls-attrs="id=value-{{variable.$id}}}" data-ls-bind="{{variable.value}}" />
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</div>
<form class="pull-end margin-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Function Variable"
data-service="functions.deleteVariable"
data-scope="sdk"
data-event="variable-delete-{{variable.$id}}"
data-confirm="Are you sure you want to delete this variable?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted variable successfully"
data-success-param-trigger-events="functions.deleteVariable"
data-failure="alert"
data-failure-param-alert-text="Failed to delete variable"
data-failure-param-alert-classname="error">
<input type="hidden" name="functionId" data-ls-bind="{{router.params.id}}" />
<input type="hidden" name="variableId" data-ls-bind="{{variable.$id}}" />
</form>
<div>
<span data-ls-bind="{{variable.key}}"></span>
</div>
<div data-ui-modal class="modal box close" data-button-text="Show Value" data-button-class="link pull-start margin-end-small margin-top-tiny">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Variable Value</h1>
<form>
<div class="input-copy">
<textarea disabled data-forms-copy data-ls-bind="{{variable.value}}"></textarea>
</div>
<hr />
<div>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
</form>
</div>
<div class="phones-only-inline tablets-only-inline margin-top-small">
<button class="link" data-ls-ui-trigger="variable-update-{{variable.$id}}">Update</button>
<button class="link danger" data-ls-ui-trigger="variable-delete-{{variable.$id}}">Delete</button>
</div>
</li>
</ul>
</div>
<div data-ls-if="(!{{function-variables.variables.length}})" class="box dashboard margin-bottom">
<div class="margin-bottom-small margin-top-small margin-end margin-start">
<h3 class="margin-bottom-small text-bold">There are currently no variables</h3>
<p class="margin-bottom-no">Add your first variable to store information securely.</p>
</div>
</div>
<div data-ui-modal class="modal box close" data-button-alias=".variable-new">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Create a new variable</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create New Function Variable"
data-service="functions.createVariable"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new variable successfully"
data-success-param-trigger-events="functions.createVariable"
data-failure="alert"
data-failure-param-alert-text="Failed to register variable"
data-failure-param-alert-classname="error">
<input type="hidden" name="functionId" data-ls-bind="{{router.params.id}}" />
<label for="name">Name <span class="tooltip large" data-tooltip="The name of the environment variable you want to set"><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="APP_ENV" maxlength="128" />
<label for="key">Value <span class="tooltip large" data-tooltip="The value of your environment variable"><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="value" required autocomplete="off" placeholder="Production" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<div class="pull-start link variable-new" data-ls-ui-open="" data-button-aria="Add Variable" data-button-text="Add Variable" data-button-class="button" data-blur="1">
</div>
</li>
<li data-state="/console/functions/function/settings?id={{router.params.id}}&project={{router.params.project}}" x-data="events">
<h2>Settings</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<label>&nbsp;</label>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Function"
data-service="functions.update"
data-scope="sdk"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated function successfully"
data-success-param-trigger-events="functions.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update function"
data-failure-param-alert-classname="error">
<div class="box">
<section class="margin-bottom-large">
<label for="name">Name</label>
<input name="name" id="function-name" type="text" autocomplete="off" data-ls-bind="{{project-function.name}}" data-forms-text-direction required placeholder="Function Name" maxlength="128" />
<label for="execute">Execute Access <span class="tooltip small" data-tooltip="Choose who can execute this function using the client API."><i class="icon-info-circled"></i></span> <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="execute" name="execute" data-forms-tags data-cast-to="json" data-ls-bind="{{project-function.execute}}" placeholder="User ID, Team ID or Role" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Add 'any' for wildcard access</div>
<label for="timeout">Timeout (seconds) <span class="tooltip small" data-tooltip="Limit the execution time of your function."><i class="icon-info-circled"></i></span></label>
<input name="timeout" id="function-timeout" type="number" autocomplete="off" data-ls-bind="{{project-function.timeout}}" min="1" max="<?php echo $this->escape($timeout); ?>" data-cast-to="integer" />
<div class="text-size-small text-fade margin-bottom margin-top-negative-small">Max value is <?php echo $this->escape(number_format($timeout)); ?> seconds (<?php echo $this->escape((int) ($timeout / 60)); ?> minutes)</div>
</section>
<section class="margin-bottom-small" data-ls-attrs="x-init=load({{project-function.events}})">
<label class="margin-bottom-small">Events <span class="tooltip small" data-tooltip="Set events that will trigger your function."><i class="icon-info-circled"></i></span></label>
<div>
<template x-for="event in Array.from(events)">
<div class="row events responsive thin margin-bottom-small">
<div class="col span-12 margin-bottom-small">
<span class="text" x-text="event"></span>
<span class="action" @click="removeEvent(event)">
<i class="icon-trash"></i>
</span>
</div>
<input name="events" data-cast-to="array" type="hidden" :value="event"></input>
</div>
</template>
</div>
<button class="margin-end margin-bottom-small reverse" type="button" @click="showModal($refs.modal_function)">Add Event</button>
</section>
<label for="schedule">Schedule (CRON Syntax) <span class="tooltip small" data-tooltip="Set a CRON schedule to trigger this function."><i class="icon-info-circled"></i></span></label>
<input type="text" id="function-schedule" class="full-width" name="schedule" autocomplete="off" data-ls-bind="{{project-function.schedule}}" placeholder="* * * * *" />
<div class="text-size-small text-fade margin-bottom margin-top-negative-small">Leave blank for no schedule</div>
<hr class="margin-bottom margin-top-small" />
<button>Update</button>
</div>
</form>
</div>
<div class="col span-4 sticky-top">
<label>Function ID</label>
<div class="input-copy margin-bottom">
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-function.$id}}" disabled data-forms-copy>
</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"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Function)">
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-function.$updatedAt|date}}"></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-function.$createdAt|date}}"></span></li>
</ul>
<form name="functions.delete" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Function"
data-service="functions.delete"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-confirm="Are you sure you want to delete this function?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Function deleted successfully"
data-success-param-trigger-events="functions.delete"
data-success-param-redirect-url="/console/functions?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete function"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Function</button>
</form>
</div>
</div>
<div x-ref="modal_function" data-ui-modal class="modal box close width-small height-small" data-button-hide="on">
<div>
<form @submit.prevent="addEvent($refs.modal_function)">
<label for="event">
Event
</label>
<select id="event" x-model="selected" @change="setEvent()">
<option value="" selected>Select event</option>
<?php foreach ($patterns as $event) : ?>
<option value="<?php echo $event; ?>"><?php echo $event; ?></option>
<?php endforeach; ?>
</select>
<div x-show="hasResource">
<label x-text="resourceName + ' (optional)'" for="resource"></label>
<input id="resource" type="text" :placeholder="resourceName" x-model="resource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubResource">
<label x-text="subResourceName + ' (optional)'" for="subResource"></label>
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubSubResource">
<label x-text="subSubResourceName + ' (optional)'" for="subSubResource"></label>
<input id="subSubResource" type="text" :placeholder="subSubResourceName" x-model="subSubResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasAttribute">
<label for="attribute">Add Attribute (optional)</label>
<select id="attribute" x-model="attribute">
<option value="*">Select attribute</option>
<template x-for="attr in attributes">
<option :value="attr" x-text="attr"></option>
</template>
</select>
</div>
<button x-show="selected" type="submit">Add Event</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</li>
</ul>
</div>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-hide="on" data-open-event="execute-now">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1 class="margin-bottom">Execute Function</h1>
<form data-ls-if="{{project-function.deployment}} !== ''" name="functions.createExecution" class="margin-top"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Function Execution"
data-service="functions.createExecution"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Function executed successfully"
data-success-param-trigger-events="functions.createExecution"
data-failure="alert"
data-failure-param-alert-text="Failed to execute function"
data-failure-param-alert-classname="error">
<input name="async" data-cast-to="bool" value="true" type="hidden" />
<label for="execution-data">Custom Data</label>
<textarea id="execution-data" name="data" autocomplete="off" class="margin-bottom" placeholder="Data string (optional)"></textarea>
<button type="submit" style="vertical-align: top;">Execute Now</button>
</form>
</div>
<div data-ui-modal class="modal close box sticky-footer" data-button-hide="on" data-open-event="create-deployment">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1 class="margin-bottom-xl">Create a New Deployment</h1>
<ul class="phases padding margin-top-negative-small" data-ui-phases>
<li>
<h2 style="display: none">CLI</h2>
<p><b>Unix</b></p>
<div class="margin-bottom">
<textarea type="hidden" data-ls-bind="appwrite functions createDeployment \
--functionId={{project-function.$id}} \
--activate=true \
--entrypoint='scriptFile' \
--code='.'" data-forms-code="bash" data-lang="bash" data-lang-label="Bash"></textarea>
</div>
<p><b>PowerShell</b></p>
<div class="margin-bottom">
<textarea type="hidden" data-ls-bind="appwrite functions createDeployment `
--functionId={{project-function.$id}} `
--activate=true `
--entrypoint='scriptFile' `
--code='.'" data-forms-code="powershell" data-lang="powershell" data-lang-label="PowerShell"></textarea>
</div>
<p>Learn more about <a href="https://appwrite.io/docs/server/functions#functionsCreateDeployment" target="_blank">creating deployments</a>, installing and using the <a href="https://appwrite.io/docs/command-line" target="_blank">Appwrite CLI</a>.</p>
</li>
<li>
<h2 style="display: none">Manual</h2>
<form class="margin-top-negative"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Function Deployment"
data-service="functions.createDeployment"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created function deployment successfully"
data-success-param-trigger-events="functions.createDeployment"
data-failure="alert"
data-failure-param-alert-text="Failed to create function deployment"
data-failure-param-alert-classname="error">
<input type="hidden" name="functionId" data-ls-bind="{{router.params.id}}" />
<label for="deployment-entrypoint">Entrypoint</label>
<input type="text" id="deployment-entrypoint" name="entrypoint" required autocomplete="off" class="margin-bottom" placeholder="main.js" />
<label for="deployment-code">Gzipped Code (tar.gz file)</label>
<input type="file" name="code" id="deployment-code" size="1" required accept="application/x-gzip,.gz">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div>
<label for="deployment-activate" class="margin-bottom-large"><input type="checkbox" class="margin-start-small" id="deployment-activate" name="activate" /> Activate Deployment after build</label>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</li>
</ul>
</div>

View file

@ -1,216 +0,0 @@
<?php
$runtimes = $this->getParam('runtimes', []);
$usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
?>
<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>
<br />
<span>Functions</span>
</h1>
</div>
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/functions?project={{router.params.project}}">
<h2>Functions</h2>
<div class="zone xl"
data-service="functions.list"
data-scope="sdk"
data-event="load,functions.create,functions.update,functions.delete"
data-name="project-functions"
data-param-project-id="{{router.params.project}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-success="trigger"
data-success-param-trigger-events="functions.list">
<div data-ls-if="0 == {{project-functions.functions.length}} || undefined == {{project-functions.functions.length}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Functions Found</h3>
<p class="margin-bottom-no">You haven't created any functions for your project yet.</p>
</div>
<div data-ls-if="0 != {{project-functions.functions.length}}">
<div class="margin-bottom-small text-align-end text-size-small margin-top-negative text-fade"><span data-ls-bind="{{project-functions.total}}"></span> functions found</div>
<div class="box margin-bottom">
<ul data-ls-loop="project-functions.functions" data-ls-as="function" class="list">
<li class="clear">
<div class="pull-start margin-end avatar-container">
<img src="" data-ls-attrs="src=/images/runtimes/{{function.runtime|runtimeLogo}}?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Function Runtime" class="avatar" loading="lazy" width="60" height="60" />
</div>
<a data-ls-attrs="href=/console/functions/function?id={{function.$id}}&project={{router.params.project}}" class="button pull-end">Settings</a>
<span data-ls-bind="{{function.name}}"></span>
<p class="text-fade margin-bottom-no" data-ls-bind="{{function.runtime|runtimeName}} {{function.runtime|runtimeVersion}}"></p>
</li>
</ul>
</div>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="functions.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-functions"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-users.total}}" 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-functions.total|pageTotal}}"></span>
<form
data-service="functions.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-functions"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-functions.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div class="clear">
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Function">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add Function</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Function"
data-service="functions.create"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset,redirect"
data-success-param-alert-text="Created function successfully"
data-success-param-trigger-events="functions.create"
data-success-param-redirect-url="/console/functions/function?id={{serviceData.$id}}&project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to create function"
data-failure-param-alert-classname="error">
<label for="functionId">Function ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="functions.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
name="functionId" />
<label for="name">Name</label>
<input type="text" id="name" name="name" required autocomplete="off" class="margin-bottom" maxlength="128" />
<label for="runtime">Runtimes</label>
<select name="runtime" id="runtime" required class="margin-bottom-xl">
<?php foreach ($runtimes as $key => $runtime): ?>
<option value="<?php echo $this->escape($key); ?>"><?php echo $this->escape($runtime['name']); ?> <?php echo $this->escape($runtime['version']); ?></option>
<?php endforeach;?>
</select>
<input id="execute" name="execute" value="" hidden />
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
</div>
</div>
</li>
<?php if ($usageStatsEnabled): ?>
<li data-state="/console/functions/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="functions.getUsage"
data-event="submit"
data-name="usage"
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="functions.getUsage"
data-event="submit"
data-name="usage"
data-param-range="30d">
<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="functions.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>
<h2>Usage</h2>
<div
data-service="functions.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="Executions=executionsTotal" data-height="140" data-show-y-axis="true" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Executions <span data-ls-bind="({{usage.executionsTotal|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="CPU Time (milliseconds)=executionsTime" data-colors="orange" data-height="140" data-show-y-axis="true" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li class="orange">CPU Time <span data-ls-bind="({{usage.executionsTime|statsGetLast|ms2hum}})"></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="Failures=executionsFailure" data-colors="red" data-height="140" data-show-y-axis="true" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li class="red">Errors <span data-ls-bind="({{usage.executionsFailure|statsGetLast|statsTotal}})"></span></li>
</ul>
</div>
</li>
<?php endif;?>
</ul>
</div>

View file

@ -1,930 +0,0 @@
<?php
$graph = $this->getParam('graph', false);
$usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
?>
<div class="cover margin-bottom-small">
<div class="zone xxl margin-bottom-xl margin-top-small">
<h1 class="margin-bottom-small"
data-service="projects.get"
data-event="load,project.update,projects.createPlatform,projects.updatePlatform,projects.deletePlatform"
data-name="console-project"
data-param-project-id="{{router.params.project}}"
data-scope="console">
<span class="title" data-ls-bind="{{console-project.name}}">&nbsp;</span>&nbsp;&nbsp;
</h1>
<ul class="desktops-only margin-top-negative-small margin-bottom clear">
<li class="pull-start margin-end margin-bottom-small"><a data-ls-attrs="href=/console/settings?project={{router.params.project}}" class="link-animation-enabled"><i class="icon-cog"></i> &nbsp;Settings</a> &nbsp;&nbsp;</li>
<li class="pull-start margin-end margin-bottom-small"><a data-ls-attrs="href=/console/keys?project={{router.params.project}}" class="link-animation-enabled"><i class="icon-key-inv"></i> &nbsp;API Keys</a> &nbsp;&nbsp;</li>
<li class="pull-start margin-end margin-bottom-small"><a data-ls-attrs="href=/console/webhooks?project={{router.params.project}}" class="link-animation-enabled"><i class="icon-link"></i> &nbsp;Webhooks</a> &nbsp;&nbsp;</li>
</ul>
<div class="margin-bottom phones-only">&nbsp;</div>
</div>
</div>
<div class="zone xxl margin-top-negative-xxxl">
<div class="clear margin-bottom-small margin-top-negative">
<?php if (!$graph && $usageStatsEnabled): ?>
<div class="pull-end">
<form class="margin-start-small inline" data-ls-if="{{usage.range}} !== '24h'"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="Usage 24h"
data-service="projects.getUsage"
data-event="submit"
data-scope="console"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="24h"
data-scope="console">
<button class="tick">24h</button>
</form>
<button class="tick margin-start-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
<form class="margin-start-small inline" data-ls-if="{{usage.range}} !== '30d'"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="Usage 30d"
data-service="projects.getUsage"
data-event="submit"
data-scope="console"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-scope="console">
<button class="tick">30d</button>
</form>
<button class="tick margin-start-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<form class="margin-start-small inline" data-ls-if="{{usage.range}} !== '90d'"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="Usage 90d"
data-service="projects.getUsage"
data-event="submit"
data-scope="console"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="90d"
data-scope="console">
<button class="tick">90d</button>
</form>
<button class="tick margin-start-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
</div>
<?php endif;?>
</div>
<div
data-service="projects.getUsage"
data-event="load"
data-scope="console"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="30d">
<?php if (!$graph && $usageStatsEnabled): ?>
<div class="box dashboard">
<div class="row responsive">
<div class="col span-9">
<div class="chart pull-end">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Requests=requests" />
</div>
<div class="chart-metric">
<div class="value margin-bottom-small"><span class="sum" data-ls-bind="{{usage.requests|statsGetTotal|statsTotal}}">N/A</span></div>
<div class="unit margin-start-no margin-bottom-small">Requests</div>
</div>
</div>
<div class="col span-3">
<div class="value margin-bottom-small">
<span class="sum" data-ls-bind="{{realtime.current|accessProject|statsTotal}}" data-default="0">0</span>
</div>
<div class="unit margin-start-no margin-bottom-small">Connections</div>
<div class="chart-bar margin-top-small margin-bottom-small" data-ls-attrs="data-history={{realtime.history|accessProject}}" data-forms-chart-bars="{{realtime.history|accessProject}}"></div>
<div class="text-fade-dark text-size-small">Activity last 60 seconds</div>
</div>
</div>
</div>
<?php endif; ?>
<div class="box dashboard">
<div class="row responsive">
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.documents|statsGetLast|statsTotal}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Documents</b></div>
</div>
<div class="col span-3">
<div class="value">
<span class="sum" data-ls-bind="{{usage.storage|statsGetLast|humanFileSize}}" data-default="0">0</span>
<span data-ls-bind="{{usage.storage|statsGetLast|humanFileUnit}}" class="text-size-small unit"></span>
</div>
<div class="margin-top-small"><b class="text-size-small unit">Storage</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.users|statsGetLast|statsTotal}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Users</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.executions|statsGetLast|statsTotal}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Executions</b></div>
</div>
</div>
</div>
</div>
<p class="margin-top-small text-size-small"><i class="icon-info-circled"></i> Data is aggregated and updated every 15 minutes</p>
</div>
</div>
</div>
<div class="zone xl margin-top-xl clear" data-ls-if="({{console-project}})">
<h2 class="margin-bottom">Platforms</h2>
<div class="box margin-bottom" data-ls-if="0 < {{console-project.platforms.length}} && undefined !== {{console-project.platforms}}">
<ul data-ls-loop="console-project.platforms" data-ls-as="platform" class="list">
<li class="clear">
<div class="pull-end desktops-only">
<button data-ls-ui-trigger="platform-delete-{{platform.$id}}" class="reverse danger margin-end-small">Delete</button>
<button data-ls-ui-trigger="platform-update-{{platform.$id}}">Update</button>
</div>
<div data-ui-modal data-button-hide="on" data-open-event="platform-update-{{platform.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update Platform</h1>
<div data-ls-template="template-{{platform.type}}-update" data-type="script"></div>
</div>
<form class="pull-end margin-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Platform"
data-service="projects.deletePlatform"
data-scope="console"
data-event="platform-delete-{{platform.$id}}"
data-confirm="Are you sure you want to delete this platform?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted platform successfully"
data-success-param-trigger-events="projects.deletePlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to delete platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
</form>
<div>
<div class="pull-start margin-end avatar-container">
<img src="" data-ls-attrs="src=/images/clients/{{platform.type}}.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Platform Logo" class="avatar" loading="lazy" width="60" height="60" />
<div data-ls-if="{{platform.type}} === 'flutter-ios'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/flutter.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="iOS Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'flutter-android'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/flutter.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Android Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'flutter-linux'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/flutter.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Linux Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'flutter-macos'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/flutter.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="MacOS Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'flutter-windows'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/flutter.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Windows Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'apple-ios'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/apple.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="iOS Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'apple-macos'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/apple.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="macOS Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'apple-watchos'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/apple.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="watchOS Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'apple-tvos'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/apple.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="tvOS Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
</div>
<span class="text-one-liner" data-ls-bind="{{platform.name}}"></span>
</div>
<p class="margin-bottom-no"><small data-ls-bind="{{platform.hostname}}{{platform.key}}"></small></p>
<div class="phones-only-inline tablets-only-inline margin-top-small">
<button class="link" data-ls-ui-trigger="platform-update-{{platform.$id}}">Update</button>
<button class="link danger" data-ls-ui-trigger="platform-delete-{{platform.$id}}">Delete</button>
</div>
</li>
</ul>
</div>
<div data-ls-if="(!{{console-project.platforms.length}})" 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 Platforms Added to Your Project</h3>
<p class="margin-bottom-no">Add your first platform and build your new application.</p>
</div>
</div>
<div class="pull-end desktops-only tablets-only">
<a data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="API Keys Link"
data-ls-attrs="href=/console/keys?project={{router.params.project}}">Manage Your Server API Keys</a>
</div>
<div class="drop-list pull-start" data-ls-ui-open="" data-button-aria="Choose Platform" data-button-text="Add Platform" data-button-class="button" data-blur="1">
<ul>
<li>
<div class="link web-new"><img src="/images/clients/web.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Web Platform Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Web App</div>
</li>
<li>
<div class="link flutter-new"><img src="/images/clients/flutter.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Flutter Platform Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Flutter App</div>
</li>
<li>
<div class="link apple-new"><img src="/images/clients/apple.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="iOS Platform Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Apple App</div>
</li>
<li>
<div class="link android-new"><img src="/images/clients/android.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Android Platform Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Android App</div>
</li>
<li class="disabled">
<div class="link unity-new"><img src="/images/clients/unity.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Unity Platform Logo" class="avatar xxs margin-end-small" loading="lazy" /> New Unity Game <span class="text-fade text-size-small">(soon)</span></div>
</li>
</ul>
</div>
</div>
<div data-ui-modal class="modal box close" data-button-alias=".web-new">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>New Web App</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Web)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to create platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="web" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My Web App" maxlength="128" />
<label for="hostname">Hostname <span class="tooltip large" data-tooltip="The hostname that your website will use to interact with the <?php echo APP_NAME; ?> APIs in production or development environments. No protocol or port number required."><i class="icon-question"></i></span></label>
<input name="hostname" type="text" class="margin-bottom" autocomplete="off" placeholder="yourapp.com" required>
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">You can use * to allow wildcard hostnames or subdomains.</div>
<div class="info margin-top margin-bottom">
<div class="text-bold margin-bottom-small">Next Steps</div>
<p>After adding your new website, install our Web SDK to integrate with your code and read our <a data-ls-attrs="href={{env.HOME}}/docs/getting-started-for-web" target="_blank" rel="noopener">getting started</a> tutorial.</p>
<div class="margin-bottom-no ide" data-lang="bash" data-lang-label="bash">
<pre class="line-numbers"><code class="prism language-bash" data-prism>npm install appwrite</code></pre>
</div>
</div>
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<script type="text/html" id="template-web-update">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Platform (Web)"
data-service="projects.updatePlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
data-failure="alert,trigger"
data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
<label data-ls-attrs="for=name-{{platform.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My Web App" maxlength="128" />
<label for="hostname">Hostname <span class="tooltip large" data-tooltip="The hostname that your website will use to interact with the <?php echo APP_NAME; ?> APIs in production or development environments. No port number required."><i class="icon-question"></i></span></label>
<input name="hostname" type="text" class="margin-bottom" autocomplete="off" placeholder="yourapp.com" data-ls-bind="{{platform.hostname}}" required />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">You can use * to allow wildcard hostnames or subdomains.</div>
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>
<div data-ui-modal class="modal box close" data-button-alias=".android-new">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Register your Android App</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Android)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="android" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My Android App" maxlength="128" />
<label for="key">Package Name <span class="tooltip large" data-tooltip="Your package name is generally the applicationId in your app-level build.gradle file."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<div data-ui-modal class="modal box close width-large" data-button-alias=".flutter-new">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1 class="margin-bottom-xl">Register your Flutter App</h1>
<ul class="phases clear margin-top-negative-small padding" data-ui-phases>
<li>
<h2 style="display: none">&nbsp;&nbsp;iOS&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Flutter / iOS)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="flutter-ios" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My iOS App" maxlength="128" />
<label for="key">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
<li>
<h2 style="display: none">&nbsp;&nbsp;Android&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Flutter / Android)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="flutter-android" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My Android App" maxlength="128" />
<label for="key">Package Name <span class="tooltip large" data-tooltip="Your package name is generally the applicationId in your app-level build.gradle file."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
<li>
<h2 style="display: none">&nbsp;&nbsp;Linux&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Flutter / Linux)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="flutter-linux" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My Linux App" maxlength="128" />
<label for="key">Package Name <span class="tooltip large" data-tooltip="Your application name"><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
<li>
<h2 style="display: none">&nbsp;&nbsp;MacOS&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Flutter / Mac OS)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="flutter-macos" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My Mac OS App" maxlength="128" />
<label for="key">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
<li>
<h2 style="display: none">&nbsp;&nbsp;Windows&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Flutter / Windows)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="flutter-windows" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My Windows App" maxlength="128" />
<label for="key">Package Name <span class="tooltip large" data-tooltip="Your application name"><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</li>
</ul>
</div>
<div data-ui-modal class="modal box close width-large" data-button-alias=".apple-new">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1 class="margin-bottom-xl">Register your Apple App</h1>
<ul class="phases clear margin-top-negative-small padding" data-ui-phases>
<li>
<h2 style="display: none">&nbsp;&nbsp;iOS&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Apple / iOS)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="apple-ios" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My iOS App" maxlength="128" />
<label for="key">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
</form>
</li>
<li>
<h2 style="display: none">&nbsp;&nbsp;macOS&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Apple / macOS)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="apple-macos" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My macOS App" maxlength="128" />
<label for="key">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
</form>
</li>
<li>
<h2 style="display: none">&nbsp;&nbsp;watchOS&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Apple / watchOS)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="apple-watchos" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My watchOS App" maxlength="128" />
<label for="key">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
</form>
</li>
<li>
<h2 style="display: none">&nbsp;&nbsp;tvOS&nbsp;&nbsp;</h2>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Platform (Apple / tvOS)"
data-service="projects.createPlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Registered new platform successfully"
data-success-param-trigger-events="projects.createPlatform"
data-failure="alert"
data-failure-param-alert-text="Failed to register platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="type" data-ls-bind="apple-tvos" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My tvOS App" maxlength="128" />
<label for="key">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="key" required autocomplete="off" placeholder="com.company.appname" />
<hr />
<button type="submit">Register</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Back</button>
</form>
</li>
</ul>
</div>
<script type="text/html" id="template-ios-update">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Platform (iOS)"
data-service="projects.updatePlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
data-failure="alert,trigger"
data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
<label data-ls-attrs="for=name-{{platform.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My iOS App" maxlength="128" />
<label data-ls-attrs="for=key-{{platform.$id}}">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input name="key" type="text" class="margin-bottom" autocomplete="off" placeholder="com.company.appname" data-ls-bind="{{platform.key}}" required />
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>
<script type="text/html" id="template-macos-update">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Platform (macOS)"
data-service="projects.updatePlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
data-failure="alert,trigger"
data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
<label data-ls-attrs="for=name-{{platform.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My macOS App" maxlength="128" />
<label data-ls-attrs="for=key-{{platform.$id}}">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input name="key" type="text" class="margin-bottom" autocomplete="off" placeholder="com.company.appname" data-ls-bind="{{platform.key}}" required />
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Back</button>
</form>
</script>
<script type="text/html" id="template-android-update">
<form
data-analytics
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Platform (Flutter / Android)"
data-service="projects.updatePlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
data-failure="alert,trigger"
data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
<label data-ls-attrs="for=name-{{platform.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My Android App" maxlength="128" />
<label data-ls-attrs="for=key-{{platform.$id}}">Package Name <span class="tooltip large" data-tooltip="Your package name is generally the applicationId in your app-level build.gradle file."><i class="icon-question"></i></span></label>
<input name="key" type="text" class="margin-bottom" autocomplete="off" placeholder="com.company.appname" data-ls-bind="{{platform.key}}" required />
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>
<script type="text/html" id="template-desktop-update">
<form
data-analytics
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Platform (Flutter / Desktop)"
data-service="projects.updatePlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
data-failure="alert,trigger"
data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
<label data-ls-attrs="for=name-{{platform.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My desktop app" maxlength="128" />
<label data-ls-attrs="for=key-{{platform.$id}}">App Name <span class="tooltip large" data-tooltip="Your application name"><i class="icon-question"></i></span></label>
<input name="key" type="text" class="margin-bottom" autocomplete="off" placeholder="appname" data-ls-bind="{{platform.key}}" required />
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Cancel</button>
</form>
</script>
<script type="text/html" id="template-apple-watchos-update">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Platform (watchOS)"
data-service="projects.updatePlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
data-failure="alert,trigger"
data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
<label data-ls-attrs="for=name-{{platform.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My watchOS App" maxlength="128" />
<label data-ls-attrs="for=key-{{platform.$id}}">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input name="key" type="text" class="margin-bottom" autocomplete="off" placeholder="com.company.appname" data-ls-bind="{{platform.key}}" required />
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Back</button>
</form>
</script>
<script type="text/html" id="template-apple-tvos-update">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Platform (tvOS)"
data-service="projects.updatePlatform"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
data-failure="alert,trigger"
data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="platformId" data-ls-bind="{{platform.$id}}" />
<label data-ls-attrs="for=name-{{platform.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My tvOS App" maxlength="128" />
<label data-ls-attrs="for=key-{{platform.$id}}">Bundle ID <span class="tooltip large" data-tooltip="You can find your Bundle Identifier in the General tab for your app's primary target in Xcode."><i class="icon-question"></i></span></label>
<input name="key" type="text" class="margin-bottom" autocomplete="off" placeholder="com.company.appname" data-ls-bind="{{platform.key}}" required />
<hr />
<button type="submit">Update</button> &nbsp; <button data-ls-ui-trigger="modal-close" type="button" class="reverse">Back</button>
</form>
</script>
<script type="text/html" id="template-flutter-ios-update">
<div data-ls-template="template-ios-update" data-type="script"></div>
</script>
<script type="text/html" id="template-flutter-android-update">
<div data-ls-template="template-android-update" data-type="script"></div>
</script>
<script type="text/html" id="template-flutter-linux-update">
<div data-ls-template="template-desktop-update" data-type="script"></div>
</script>
<script type="text/html" id="template-flutter-macos-update">
<div data-ls-template="template-ios-update" data-type="script"></div>
</script>
<script type="text/html" id="template-flutter-windows-update">
<div data-ls-template="template-desktop-update" data-type="script"></div>
</script>
<script type="text/html" id="template-apple-ios-update">
<div data-ls-template="template-ios-update" data-type="script"></div>
</script>
<script type="text/html" id="template-apple-macos-update">
<div data-ls-template="template-macos-update" data-type="script"></div>
</script>

View file

@ -1,130 +0,0 @@
<?php
$home = $this->getParam('home', '');
?>
<div class="cover">
<div class="zone xl">
<h1 class="margin-bottom margin-top">Your Projects</h1>
<p class="margin-bottom margin-top-negative-small">Take advantage of the Appwrite APIs and tools.</p>
<ul class="margin-bottom-xl clear">
<li class="pull-start margin-end margin-bottom-small"><a href="<?php echo $home; ?>/docs" target="_blank" rel="noopener" class="link-animation-enabled"><i class="icon-book-open"></i> &nbsp;Docs</a></li>
<li class="pull-start margin-end margin-bottom-small"><a href="<?php echo $home; ?>/support" target="_blank" rel="noopener" class="link-animation-enabled"><i class="icon-lifebuoy"></i> &nbsp;Support</a></li>
</ul>
</div>
</div>
<section class="zone xl margin-bottom margin-top-negative-xl">
<div class="margin-bottom-xl"
data-service="projects.list"
data-param-search="{{router.params.search}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="console"
data-event="load,projects.create"
data-name="console-projects"
data-success="trigger"
data-success-param-trigger-events="projects.list">
<div data-ls-if="0 == {{console-projects.projects.length}}" class="box margin-bottom" style="display: none">
<h3 class="margin-bottom-small text-bold">Get Started</h3>
<p class="margin-bottom-no">No Projects Found, Create your first project now.</p>
</div>
<ul data-ls-loop="console-projects.projects" data-ls-as="project" data-ls-append="" class="tiles cell-3">
<li class="margin-bottom">
<a data-ls-attrs="href=/console/home?project={{project.$id}}" class="box">
<div data-ls-bind="{{project.name}}" class="text-one-liner margin-bottom-tiny text-bold">&nbsp;</div>
<p data-ls-if="({{project.platforms.length}})" class="text-fade text-size-small" data-ls-bind="{{project.platforms.length}} apps"></p>
<p data-ls-if="(!{{project.platforms.length}})" class="text-fade text-size-small">&nbsp;</p>
<div>
<i class="icon-right-open"></i>
</div>
</a>
</li>
</ul>
<div class="pull-end paging">
<form
data-service="projects.list"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="console"
data-name="console-projects"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{console-projects.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{console-projects.total|pageTotal}}"></span>
<form
data-service="projects.list"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="console"
data-name="console-projects"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{console-projects.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<button data-ls-ui-trigger="create-project">Create Project</button>
</div>
<div class="row responsive">
<div class="col span-6 margin-bottom">
<div class="box line community">
<h3 class="margin-bottom-small text-size-normal">Join The Community</h3>
<p class="text-fade">Join Appwrite growing developers community channels.</p>
<a href="<?php echo APP_SOCIAL_TWITTER; ?>" target="_blank" rel="noopener" title="<?php echo APP_NAME;?> on Twitter"
data-analytics
data-analytics-event="click"
data-analytics-category="console/home"
data-analytics-label="Twitter Link"><i class="icon-twitter"></i></a>
<a href="<?php echo APP_SOCIAL_FACEBOOK; ?>" target="_blank" rel="noopener" title="<?php echo APP_NAME;?> on Facebook"
data-analytics
data-analytics-event="click"
data-analytics-category="console/home"
data-analytics-label="Facebook Link"><i class="icon-facebook"></i></a>
<a href="<?php echo APP_SOCIAL_LINKEDIN; ?>" target="_blank" rel="noopener" title="<?php echo APP_NAME;?> on Linkedin"
data-analytics
data-analytics-event="click"
data-analytics-category="console/home"
data-analytics-label="Linkedin Link"><i class="icon-linkedin"></i></a>
<a href="<?php echo APP_SOCIAL_DISCORD; ?>" target="_blank" rel="noopener" title="<?php echo APP_NAME;?> Discord Server"
data-analytics
data-analytics-event="click"
data-analytics-category="console/home"
data-analytics-label="Discord Link"><i class="icon-discord"></i></a>
<a href="<?php echo APP_SOCIAL_GITHUB; ?>" target="_blank" rel="noopener" title="<?php echo APP_NAME;?> on Github"
data-analytics
data-analytics-type="click"
data-analytics-category="console/home"
data-analytics-label="GitHub Link"><i class="icon-github"></i></a>
</div>
</div>
<div class="col span-6 margin-bottom">
<div class="box line">
<h3 class="margin-bottom-small text-size-normal">Read The Docs</h3>
<p class="text-fade">Take full advantage of Appwrite APIs and tools for your new project.</p>
<a data-ls-attrs="href={{env.HOME}}/docs" target="_blank" rel="noopener"><i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Full Documentation</a>
</div>
</div>
</div>
</section>

View file

@ -1,184 +0,0 @@
<?php
$scopes = $this->getParam('scopes', []);
?>
<div class="cover margin-bottom-large">
<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>
<br />
<span>API Keys</span>
</h1>
</div>
<div class="zone xl"
data-service="projects.listKeys"
data-scope="console"
data-event="load,projects.createKey,projects.updateKey,projects.deleteKey"
data-name="console-keys"
data-param-project-id="{{router.params.project}}"
data-success="trigger"
data-success-param-trigger-events="projects.listKeys">
<div data-ls-if="0 == {{console-keys.total}} || undefined == {{console-keys.total}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No API Keys Found</h3>
<p class="margin-bottom-no">You haven't created any API keys for your project yet.</p>
</div>
<div class="box margin-bottom" data-ls-if="0 != {{console-keys.total}}">
<ul data-ls-loop="console-keys.keys" data-ls-as="key" class="list">
<li class="clear">
<div data-ui-modal class="modal box close" data-button-alias="none" data-open-event="key-update-{{key.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update API Key</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Key"
data-service="projects.updateKey"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated API key successfully"
data-success-param-trigger-events="projects.updateKey"
data-failure="alert"
data-failure-param-alert-text="Failed to update API key"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="keyId" data-ls-bind="{{key.$id}}" />
<label data-ls-attrs="for=name-{{key.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different API keys."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{key.$id}}" name="name" required autocomplete="off" data-ls-bind="{{key.name}}" maxlength="128" />
<section data-forms-select-all>
<label data-ls-attrs="for=scopes-{{key.$id}}">Scopes (<a data-ls-attrs="href={{env.HOME}}/docs/keys" target="_blank" rel="noopener">Learn more</a>)</label>
<div class="row responsive thin">
<?php foreach ($scopes as $i => $scope): ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $scope; ?>">
<input data-ls-attrs="id=scope-<?php echo $scope; ?>" type="checkbox" name="scopes" data-ls-bind="{{key.scopes}}" value="<?php echo $scope; ?>" data-by-key="true" />
&nbsp;
<label class="inline" for="scope-<?php echo $scope; ?>"><?php echo $scope; ?></label>
</div>
<?php if (($i + 1) % 2 === 0): ?>
</div>
<div class="row responsive thin">
<?php endif;?>
<?php endforeach;?>
</div>
</section>
<hr class="margin-top-no" />
<button type="submit">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<form class="pull-end margin-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Key"
data-service="projects.deleteKey"
data-scope="console"
data-event="key-delete-{{key.$id}}"
data-confirm="Are you sure you want to delete this API key?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted API key successfully"
data-success-param-trigger-events="projects.deleteKey"
data-failure="alert"
data-failure-param-alert-text="Failed to delete API key"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="keyId" data-ls-bind="{{key.$id}}" />
</form>
<button class="pull-end desktops-only margin-start-small" data-ls-ui-trigger="key-update-{{key.$id}}">Update</button>
<button class="pull-end reverse desktops-only margin-small" data-ls-ui-trigger="key-delete-{{key.$id}}">Delete</button>
<div class="margin-bottom-tiny"><span data-ls-bind="{{key.name}}"></span> <small>(<span data-ls-bind="{{key.scopes.length}}"></span> scopes granted)</small></div>
<div class="clear">
<div data-ui-modal class="modal box close" data-button-text="Show Secret" data-button-class="link pull-start margin-end-small margin-top-tiny">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>API Key Secret</h1>
<form>
<div class="input-copy">
<textarea disabled style="height: 130px; line-height: 26px" data-forms-copy data-ls-bind="{{key.secret}}"></textarea>
</div>
<hr />
<div>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
</form>
</div>
<button class="link pull-start tablets-only phones-only margin-end-small margin-top-tiny" data-ls-ui-trigger="key-update-{{key.$id}}">Update</button>
<button class="link pull-start tablets-only phones-only margin-end-small margin-top-tiny text-danger" data-ls-ui-trigger="key-delete-{{key.$id}}">Delete</button>
</div>
</li>
</ul>
</div>
<div class="clear">
<div data-ui-modal class="modal box close" data-button-text="Add API Key">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add API Keys</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Key"
data-service="projects.createKey"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created API key successfully"
data-success-param-trigger-events="projects.createKey"
data-failure="alert"
data-failure-param-alert-text="Failed to create API key"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different API keys."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" id="name" name="name" required autocomplete="off" maxlength="128" />
<section data-forms-select-all>
<label for="scopes">Scopes (<a data-ls-attrs="href={{env.HOME}}/docs/keys" target="_blank" rel="noopener">Learn more</a>)</label>
<div class="row responsive thin">
<?php foreach ($scopes as $i => $scope): ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $scope; ?>">
<input type="checkbox" name="scopes" id="<?php echo $scope; ?>" value="<?php echo $scope; ?>" data-by-key="true" />
&nbsp;
<label class="inline" for="<?php echo $scope; ?>"><?php echo $scope; ?></label>
</div>
<?php if (($i + 1) % 2 === 0): ?>
</div>
<div class="row responsive thin">
<?php endif;?>
<?php endforeach;?>
</div>
</section>
<hr class="margin-top-no" />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</div>

View file

@ -1,546 +0,0 @@
<?php
use Utopia\Database\Permission;
use Utopia\Database\Role;
$services = $this->getParam('services', []);
$customDomainsEnabled = $this->getParam('customDomainsEnabled', false);
$customDomainsTarget = $this->getParam('customDomainsTarget', false);
$smtpEnabled = $this->getParam('smtpEnabled', false);
?>
<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>
<br />
<span>Settings</span>
</h1>
</div>
<div class="zone xl"
data-service="projects.get"
data-scope="console"
data-name="console-project"
data-event="load,projects.update"
data-param-project-id="{{router.params.project}}"
data-success="trigger"
data-success-param-trigger-events="projects.get">
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/settings?project={{router.params.project}}">
<h2>Overview</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<label>&nbsp;</label>
<div class="box margin-bottom-large">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project"
data-service="projects.update"
data-scope="console"
data-event="submit"
data-param-project-id="{{router.params.project}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated project successfully"
data-success-param-trigger-events="projects.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update project"
data-failure-param-alert-classname="error">
<input name="$id" type="hidden" data-ls-bind="{{console-project.$id}}" />
<label for="name">Name</label>
<input name="name" id="name" type="text" autocomplete="off" data-ls-bind="{{console-project.name}}" data-forms-text-direction required maxlength="128" />
<label for="logo">Project Logo</label>
<div class="text-align-center clear">
<input type="hidden" name="logo" data-ls-bind="{{console-project.logo}}" data-permissions="<?php echo $this->escape(\json_encode([Permission::read(Role::any()), Permission::update(Role::team('{{console-project.teamId}}')), Permission::delete(Role::team('{{console-project.teamId}}'))])); ?>" data-accept="image/*" data-forms-upload="" data-label-button="Upload" data-preview-alt="Project Logo" data-scope="console" data-default="">
</div>
<hr />
<button class="" type="submit">Update</button>
</form>
</div>
<h3 class="text-danger">Danger Zone</h3>
<div class="box danger">
<p>This is the area where you can delete your project.</p>
<p>By deleting your project you will lose all your project metadata, resources and stats.</p>
<p>PLEASE NOTE: Project deletion is irreversible.</p>
<form class="inline"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project"
data-service="projects.delete"
data-scope="console"
data-event="submit"
data-confirmx="Are you sure you want to delete this project?"
data-success="trigger,redirect"
data-success-param-trigger-events="project.delete,reset,modal-close"
data-success-param-redirect-url="/console"
data-failure="alert,trigger,reset"
data-failure-param-alert-text="Project deletion failed"
data-failure-param-trigger-events="modal-close"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<div data-ui-modal class="modal box close width-small height-small" data-button-text="Delete Project" data-button-class="danger reverse">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h3>Confirm Password</h3>
<hr />
<label>Password</label>
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="" required>
<hr />
<button type="submit" class="margin-bottom-no danger fill">Delete Project</button>
</div>
</form>
</div>
</div>
<div class="col span-4 sticky-top">
<label for="name">Project ID</label>
<div class="input-copy">
<input data-forms-copy type="text" disabled data-ls-bind="{{console-project.$id}}" />
</div>
<label for="name">API Endpoint</label>
<div class="input-copy">
<input data-forms-copy type="text" disabled data-ls-bind="{{env.ENDPOINT}}/v1" />
</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> <a data-ls-attrs="href=/console/settings/domains?project={{router.params.project}}" class=" text-size-small">Add a custom endpoint</a></li>
</ul>
</div>
</div>
</li>
<li data-state="/console/settings/services?project={{router.params.project}}">
<h2>Services</h2>
<p class="text-fade margin-bottom">Choose services you wish to enable or disable.</p>
<ul class="tiles cell-3 margin-bottom-small">
<?php foreach($services as $index => $service):
$key = $service['key'] ?? '';
$name = $service['name'] ?? '';
$icon = $service['icon'] ?? '';
$docs = $service['docsUrl'] ?? '';
?>
<li class="">
<div class="box padding-small margin-bottom clear">
<div class="clear">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project service Status (<?php echo $this->escape($name); ?>)"
data-service="projects.updateServiceStatus"
data-scope="console"
data-event="change"
data-confirm="Are you sure you want to change the status of the <?php echo $this->escape($name); ?> service?"
data-param-project-id="{{router.params.project}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated project service status successfully"
data-success-param-trigger-events="projects.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update project service status settings"
data-failure-param-alert-classname="error">
<input name="service" id="<?php echo $this->escape($key); ?>" type="hidden" autocomplete="off" value="<?php echo $this->escape($key); ?>">
<input name="status" type="hidden" data-forms-switch data-ls-bind="{{console-project.serviceStatusFor<?php echo ucFirst($this->escape($key)); ?>}}" data-cast-to="boolean" class="pull-end" />
</form>
<img src="<?php echo $this->escape($icon); ?>?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="" class="pull-start provider margin-end" />
<span class="text-size-small text-bold"><?php echo $this->escape($name); ?></span>
<?php if($docs): ?>
<p class="margin-bottom-no text-one-liner text-size-small">
<a href="<?php echo $this->escape($docs); ?>" target="_blank" rel="noopener">Docs<i class="icon-link-ext"></i></a>
</p>
<?php endif; ?>
</div>
</div>
</li>
<?php endforeach; ?>
</ul>
</li>
<li data-state="/console/settings/domains?project={{router.params.project}}">
<?php if(!$customDomainsEnabled): ?>
<h2 style="display: none;">Custom Domains</h2>
<div class="box line margin-bottom">
<h3>Enable Custom Domains</h3>
<p>To enable <?php echo APP_NAME; ?>'s custom domain feature, you have to start your server instance with a public accessible domain name.</p>
<p>Start your <?php echo APP_NAME; ?> server container with the <b>_APP_DOMAIN_TARGET</b> environment variable set with a public accessible domain name that resolves to your <?php echo APP_NAME; ?> server setup.</p>
<p class="margin-bottom-no">The <?php echo APP_NAME; ?> server will use your target domain to validate new custom domains and will automatically generate SSL certificates for your new domains using Let'sencrypt Certbot.</p>
</div>
<?php endif; ?>
<?php if($customDomainsEnabled): ?>
<h2>Custom Domains</h2>
<div class="zone xl"
data-service="projects.listDomains"
data-scope="console"
data-event="load,projects.createDomain,projects.updateDomainVerification,projects.deleteDomain"
data-name="console-domains"
data-param-project-id="{{router.params.project}}"
data-success="trigger"
data-success-param-trigger-events="projects.listDomains">
<div data-ls-if="0 == {{console-domains.total}} || undefined == {{console-domains.total}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Custom Domains Added</h3>
<p class="margin-bottom-no">You haven't created any custom domains for your project yet.</p>
</div>
<div class="box margin-bottom" data-ls-if="0 != {{console-domains.total}}">
<table class="vertical">
<thead>
<tr>
<th width="120"></th>
<th width="200">Domain Name</th>
<th width="160">TLS</th>
<th></th>
<th width="80"></th>
</tr>
</thead>
<tbody data-ls-loop="console-domains.domains" data-ls-as="domain">
<tr>
<td data-title="Status">
<span class="text-size-small text-danger" data-ls-if="true !== {{domain.verification}}"><i class="icon-cancel-circled"></i> Unverified&nbsp;</span>
<span class="text-size-small text-info" data-ls-if="true === {{domain.verification}}"><i class="icon-ok-circled"></i> Verified&nbsp;</span>
</td>
<td data-title="Domain: ">
<span class="text-size-small" data-ls-bind="{{domain.domain}}"></span>
</td>
<td data-title="TLS: ">
<span class="text-size-small text-fade" data-ls-if="!{{domain.certificateId}} && false === {{domain.verification}}"> &nbsp;Pending Verification&nbsp;</span>
<span class="text-size-small text-fade" data-ls-if="!{{domain.certificateId}} && true === {{domain.verification}}"> &nbsp;In Progress&nbsp;</span>
<span class="text-size-small text-success" data-ls-if="{{domain.certificateId}}"><i class="icon-ok-circled"></i> Enabled&nbsp;</span>
</td>
<td data-title="">
<button class="link text-size-small" data-ls-if="true === {{domain.verification}}" data-ls-ui-trigger="dns-settings-{{domain.$id}}">DNS Settings</button>
<button class="link text-size-small" data-ls-if="true !== {{domain.verification}}" data-ls-ui-trigger="dns-settings-{{domain.$id}}">Verify Domain</button>
<div data-ui-modal class="modal box close" data-button-alias="none" data-open-event="dns-settings-{{domain.$id}}" xdata-close-event="dns-settings-close-{{domain.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h3 class="margin-bottom" data-ls-if="true === {{domain.verification}}" data-ls-ui-trigger="dns-settings-{{domain.$id}}">DNS Settings</h3>
<h3 class="margin-bottom" data-ls-if="true !== {{domain.verification}}" data-ls-ui-trigger="dns-settings-{{domain.$id}}">Verify Domain</h3>
<hr />
<p>Add the following DNS records to your DNS provider settings to setup and verify your new custom domain.</p>
<ol class="bullets">
<li>
<p>Add a new CNAME record in your DNS providers settings to point your new subdomain to your <?php echo APP_NAME; ?> server with the following value:</p>
<div class="ide margin-bottom-small">
<pre class="line-numbers"><code class="prism language-javascript" data-prism><?php echo $this->print($customDomainsTarget, 'escape'); ?></code></pre>
</div>
<p>For example:</p>
<div class="ide margin-bottom-small">
<pre class="line-numbers"><code class="prism language-javascript" data-prism><?php echo strtolower(APP_NAME); ?>.myapp.com CNAME <?php echo $this->print($customDomainsTarget, 'escape'); ?></code></pre>
</div>
</li>
<li>
Confirm and verify your CNAME record values:
<form class="strip"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Domain Verification"
data-service="projects.updateDomainVerification"
data-scope="console"
data-event="submit"
data-loading="Verifying DNS records..."
data-success="alert,trigger"
data-success-param-alert-text="Verified domain successfully"
data-success-param-trigger-events="projects.updateDomainVerification"
data-failure="alert"
data-failure-param-alert-text="Failed to verify domain, check your DNS records"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="domainId" data-ls-bind="{{domain.$id}}" />
<button class="margin-top-small">Confirm & Verify</button>
</form>
</li>
</ol>
<div class="info margin-bottom">
<h4 class="margin-bottom-small">SSL Certificate</h4>
<p>After verification completes, <?php echo APP_NAME; ?> will automatically generate a secure SSL certificate for your domain. This process may take a few seconds. Certitficate renewals are automatically issued every 60 days.</p>
</div>
<button data-ui-modal-close="" type="button" class="reverse">Close Settings</button>
</div>
</td>
<td data-title="">
<form class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Domain"
data-service="projects.deleteDomain"
data-scope="console"
data-event="submit"
data-confirm="Are you sure you want to delete this domain?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted domain successfully"
data-success-param-trigger-events="projects.deleteDomain"
data-failure="alert"
data-failure-param-alert-text="Failed to delete domain"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="domainId" data-ls-bind="{{domain.$id}}" />
<button class="danger small">Delete</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
<div class="clear">
<div data-ui-modal class="modal box close" data-button-text="Add Domain">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add Domain</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Domain"
data-service="projects.createDomain"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created domain successfully"
data-success-param-trigger-events="projects.createDomain"
data-failure="alert"
data-failure-param-alert-text="Failed to create domain"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<label for="name">Domain Name</label>
<input type="text" class="full-width" id="domain" name="domain" placeholder="appwrite.example.com" required autocomplete="off" title="Enter a valid domain name" pattern="^([a-zA-Z0-9][a-zA-Z0-9-_]*\.)*[a-zA-Z0-9]*[a-zA-Z0-9-_]*[[a-zA-Z0-9]+$" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</div>
<?php endif; ?>
</li>
<li data-state="/console/settings/members?project={{router.params.project}}">
<h2>Members</h2>
<div class="zone xl"
data-service="teams.listMemberships"
data-scope="console"
data-event="load,teams.createMembership,teams.deleteMembership,teams.createMembership.resent"
data-name="members"
data-param-team-id="{{console-project.teamId}}"
data-success="trigger"
data-success-param-trigger-events="teams.listMemberships">
<div class="box margin-bottom">
<ul data-ls-loop="members.memberships" data-ls-as="member" class="list">
<li class="clear">
<form class="pull-end" data-ls-if="{{account.$id}} !== {{member.userId}}"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Membership"
data-service="teams.deleteMembership"
data-scope="console"
data-event="submit"
data-success="alert,trigger"
data-confirm="Are you sure you want to remove that user from the team?"
data-success-param-alert-text="Member Removed Successfully"
data-success-param-trigger-events="teams.deleteMembership"
data-failure="alert"
data-failure-param-alert-text="Failed to Remove Member"
data-failure-param-alert-classname="error">
<input name="teamId" data-ls-attrs="id=leave-teamId-{{member.$id}}" type="hidden" data-ls-bind="{{console-project.teamId}}">
<input name="membershipId" data-ls-attrs="id=leave-membershipId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
<button data-ls-if="1 === {{members.memberships.length}}" class="danger" disabled>Remove</button>
<button data-ls-if="1 < {{members.memberships.length}}" class="danger">Remove</button>
</form>
<form class="pull-end" data-ls-if="{{account.$id}} === {{member.userId}}"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Membership"
data-service="teams.deleteMembership"
data-scope="console"
data-event="submit"
data-success="alert,trigger,redirect"
data-confirm="Are you sure you want to remove that user from the team?"
data-success-param-alert-text="Member Removed Successfully"
data-success-param-trigger-events="teams.deleteMembership"
data-success-param-redirect-url="/console"
data-failure="alert"
data-failure-param-alert-text="Failed to Remove Member"
data-failure-param-alert-classname="error">
<input name="teamId" data-ls-attrs="id=leave-teamId-{{member.$id}}" type="hidden" data-ls-bind="{{console-project.teamId}}">
<input name="membershipId" data-ls-attrs="id=leave-membershipId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
<button data-ls-if="1 === {{members.memberships.length}}" class="danger" disabled>Remove</button>
<button data-ls-if="1 < {{members.memberships.length}}" class="danger">Remove</button>
</form>
<div data-ls-if="false === {{member.confirm}}" class="pull-end margin-end">
<form class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Membership (resend)"
data-service="teams.deleteMembership"
data-scope="console"
data-event="submit"
data-success="trigger"
data-success-param-trigger-events="teams.deleteMembership.resend"
data-failure="alert"
data-failure-param-alert-text="Failed to Resend Invitation"
data-failure-param-alert-classname="error">
<input name="teamId" data-ls-attrs="id=resend-teamId-{{member.$id}}" type="hidden" data-ls-bind="{{console-project.teamId}}">
<input name="membershipId" data-ls-attrs="id=resend-membershipId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
<button class="reverse">Resend</button>
</form>
<form class="pull-end"
data-service="teams.createMembership"
data-scope="console"
data-event="teams.deleteMembership.resend"
data-success="alert,trigger"
data-success-param-alert-text="Invitation sent successfully"
data-success-param-trigger-events="teams.createMembership.resent"
data-failure="alert"
data-failure-param-alert-text="Failed to send inivitation"
data-failure-param-alert-classname="error">
<input name="teamId" type="hidden" data-ls-bind="{{member.teamId}}">
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/join?project={{router.params.project}}" />
<input name="email" type="hidden" data-ls-bind="{{member.userEmail}}">
<input name="name" type="hidden" data-ls-bind="{{member.userName}}">
<input name="roles" type="hidden" data-ls-bind="{{member.roles}}" data-cast-to="json">
</form>
</div>
<img src="" data-ls-attrs="src={{member|avatar}}" data-size="200" alt="User Avatar" class="avatar pull-start margin-end" loading="lazy" width="60" height="60" />
<div class="margin-bottom-tiny">
<span data-ls-bind="{{member.userName}}"></span> &nbsp;&nbsp;<span class="tag" data-ls-bind="{{member.roles.0}}"></span> &nbsp;&nbsp;<span data-ls-if="false === {{member.confirm}}" class="tag red">Pending Approval</span>
</div>
<span class="text-size-small text-fade" data-ls-bind="{{member.userEmail}}"></span>
</li>
</ul>
</div>
<div data-ui-modal class="modal box close width-medium" data-button-text="Invite Member" data-button-class="">
<button type="button" class="close pull-end" data-ui-modal-close><i class="icon-cancel"></i></button>
<h1>Invite Member</h1>
<form name="teams.createTeamMembership"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Membership"
data-service="teams.createMembership"
data-scope="console"
data-event="submit"
data-loading="Sending invitation, please wait..."
data-success="alert,trigger,reset"
data-success-param-alert-text="Invitation sent successfully"
data-success-param-trigger-events="teams.createMembership"
data-failure="alert"
data-failure-param-alert-text="Failed to send invite"
data-failure-param-alert-classname="error">
<input name="teamId" id="team-teamId" type="hidden" data-ls-bind="{{console-project.teamId}}">
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/join?project={{router.params.project}}" />
<label for="email">Email</label>
<input name="email" id="email" type="email" autocomplete="email" required>
<label for="team-name">Name <small>(optional)</small></label>
<input name="name" id="team-name" type="text" autocomplete="name" maxlength="128" />
<label for="roles" style="display: none">Role</label>
<select id="roles" name="roles" required data-ls-loop="env.ROLES" data-ls-as="role" data-cast-to="array" style="display: none">
<option data-ls-attrs="value={{role.type}}" data-ls-bind="{{role.label}}"></option>
</select>
<?php if(!$smtpEnabled): ?>
<div class="box note padding-tiny warning margin-bottom text-align-center">
<i class="icon-warning"></i> SMTP connection is disabled. <a href="https://appwrite.io/docs/email-delivery" target="_blank" rel="noopener">Learn more <i class="icon-link-ext"></i></a>
</div>
<?php endif; ?>
<hr />
<div class="clear">
<button<?php if(!$smtpEnabled): ?> disabled<?php endif; ?>>Send Invite</button>
&nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
</form>
</div>
</div>
</li>
</ul>
</div>
</div>

View file

@ -1,514 +0,0 @@
<?php
$home = $this->getParam('home', '');
$fileLimit = $this->getParam('fileLimit', 0);
$fileLimitHuman = $this->getParam('fileLimitHuman', 0);
$bucketPermissions = $this->getParam('bucketPermissions', null);
$fileCreatePermissions = $this->getParam('fileCreatePermissions', null);
$fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
?>
<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">
<!-- Required for permission input validation -->
<form id="<?php echo $bucketPermissions->getParam('form') ?>"></form>
<form id="<?php echo $fileCreatePermissions->getParam('form') ?>"></form>
<form id="<?php echo $fileUpdatePermissions->getParam('form') ?>"></form>
<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-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
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-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-files">
<div data-ls-if="0 == {{project-files.total}}" 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.total}}">
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-files.total}}"></span> files found</div>
<div class="box margin-bottom">
<table class="vertical" style="overflow: visible">
<thead>
<tr>
<th width="40"></th>
<th>Filename</th>
<th width="140">Type</th>
<th width="100">Size</th>
<th width="120">Created</th>
<th width="52"></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" alt="" />
</td>
<td class="col-name" data-title="Name: " data-ls-attrs="title={{file.name}}" >
<div class="trim-inner-text">
<span data-ls-ui-trigger="modal-file-update-{{file.$id}}" class="link text-one-liner" data-ls-bind="{{file.name}}"></span>
<div data-ls-attrs="data-open-event=modal-file-update-{{file.$id}}" data-button-hide="1" data-ui-modal class="box modal width-xlarge sticky-footer close" >
<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}}">
<hr class="margin-start margin-end" />
<h3 class="margin-bottom">Permissions</h3>
<div class="margin-start margin-end margin-bottom">
<?php echo $fileUpdatePermissions->render(); ?>
</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">
<div data-ls-if="{{file.chunksTotal}} != {{file.chunksUploaded}}" class="preview-box">
<div class="preview-box-image">
<img src="/images/default_preview.svg" width="20" height="20" alt=""/>
</div>
<span class="preview-box-text">Preview not available</span>
</div>
<img data-ls-if="{{file.chunksTotal}} == {{file.chunksUploaded}}" 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.$createdAt|date}}"></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-if="{{file.chunksTotal}} == {{file.chunksUploaded}}" 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>
<span class="pill is-pending" data-ls-if="{{file.chunksTotal}} != {{file.chunksUploaded}}">pending</span>
</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.$createdAt|date}}"></span>
</td>
<td data-title="" class="cell-options-more" style="overflow: visible">
<div class="drop-list end" data-ls-ui-open="" data-button-aria="File Options" data-button-class="icon-dot-3 reset-inner-button" data-blur="1">
<ul class="no-arrow">
<li data-ls-ui-trigger="modal-file-update-{{file.$id}}"><span class="margin-start-small icon icon-edit"></span> <span class="link">Update</span></li>
<li data-ls-ui-trigger="file-delete-{{file.$id}}"><span class="margin-start-small icon icon-trash-2"></span><span class="link">Delete</span></li>
</ul>
</div>
</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-bucket-id="{{router.params.id}}"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-files.total}}" 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.total|pageTotal}}"></span>
<form
data-service="storage.listFiles"
data-event="submit"
data-param-bucket-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-files"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-files.total}}" 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"
x-data
@submit.prevent="$store.uploader.uploadFile($event.target)">
<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">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>
<div class="toggle margin-bottom" data-ls-if="{{project-bucket.fileSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<h3 class="margin-bottom-large">Permissions</h3>
<?php echo $fileCreatePermissions->render() ?>
</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" />
<label for="compression">Compression</label>
<select id="compression" data-ls-bind="{{project-bucket.compression}}" name="compression">
<option value="none">None</option>
<option value="gzip">Gzip</option>
<option value="zstd">Zstd</option>
</select>
<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">Configure the permissions for this bucket.</p>
<hr class="margin-top-small" />
<?php echo $bucketPermissions->render(); ?>
<hr class="margin-top-no" />
<label class="margin-bottom-small">File Security</label>
<div class="row">
<div class="col span-1"><input name="fileSecurity" value="false" type="checkbox" class="margin-top-no" data-ls-bind="{{project-bucket.fileSecurity}}" /></div>
<div class="col span-11">
<b>Enabled</b>
<p class="text-fade margin-top-tiny">With File Security enabled, users will be able to access files for which they have been granted <b>either</b> File or Bucket permissions.</p>
</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"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Bucket)">
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.$updatedAt|date}}"></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.$createdAt|date}}"></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,209 +0,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>
<br />
<span>Storage</span>
</h1>
</div>
<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>Buckets</h2>
<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-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-buckets">
<div data-ls-if="(!{{project-buckets.total}})" 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">You haven't created any buckets for your project yet.</p>
</div>
</div>
<div data-ls-if="0 != {{project-buckets.total}}">
<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>
<i class="icon-right-open"></i>
</a>
</li>
</ul>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="storage.listBuckets"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-buckets"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-buckets.total}}" 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-buckets.total|pageTotal}}"></span>
<form
data-service="storage.listBuckets"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-buckets"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-buckets.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<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>New Bucket</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Storage Bucket"
data-service="storage.createBucket"
data-event="submit"
data-scope="sdk"
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 create bucket"
data-failure-param-alert-classname="error">
<label for="bucket-id">Bucket ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="storage.getBucket"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
name="bucketId"
id="bucketId" />
<label for="bucket-name">Name</label>
<input type="text" class="full-width" id="bucket-name" name="name" required autocomplete="off" maxlength="128" />
<input type="hidden" id="bucket-permissions" name="permissions" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
<input type="hidden" id="bucket-fileSecurity" name="fileSecurity" required value="false" data-cast-to="boolean" />
<hr />
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</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'"
data-service="storage.getUsage"
data-event="submit"
data-name="usage"
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.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>
<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>
<h2>Usage</h2>
<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>
<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>
<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>

View file

@ -1,604 +0,0 @@
<?php
use Appwrite\Utopia\View;
$providers = $this->getParam('providers', []);
$auth = $this->getParam('auth', []);
$smtpEnabled = $this->getParam('smtpEnabled', false);
?>
<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>
<br />
<span>Authentication</span>
</h1>
</div>
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/users?project={{router.params.project}}">
<h2>Users</h2>
<form class="box padding-small margin-bottom search"
data-service="users.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-users"
data-success="state"
data-success-param-state-keys="search,offset">
<div class="row thin responsive">
<div class="col span-10">
<input name="search" id="searchUsers" 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="users.list"
data-event="load,users.create,users.update,users.delete"
data-param-search="{{router.params.search}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-users">
<div data-ls-if="0 == {{project-users.total}}" class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Users Found</h3>
<p class="margin-bottom-no">Create your first user to get started</p>
</div>
<div data-ls-if="0 != {{project-users.total}}">
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-users.total}}"></span> users found</div>
<div class="box margin-bottom">
<table class="vertical">
<thead>
<tr>
<th width="40"></th>
<th width="180">Name</th>
<th>Email</th>
<th width="140">Status</th>
<th width="120">Joined</th>
</tr>
</thead>
<tbody data-ls-loop="project-users.users" data-ls-as="user">
<tr>
<td class="hide">
<img src="" data-ls-attrs="src={{user|avatar}}" data-size="45" alt="User Avatar" class="avatar pull-start" loading="lazy" width="30" height="30" />
</td>
<td data-title="Name: ">
<a data-ls-attrs="href=/console/users/user?id={{user.$id}}&project={{router.params.project}}">
<span data-ls-bind="{{user.name}}" data-ls-attrs="title={{user.name}}"></span>
<span data-ls-if="{{user.name|escape}} === '' && ({{user.email}} !== '' || {{user.phone}} !== '')">Unknown</span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === '' && {{user.phone}} === ''">Anonymous User</span>
</a>
</td>
<td data-title="Email: ">
<span data-ls-bind="{{user.email}}" data-ls-attrs="title={{user.email}}"></span>
</td>
<td data-title="Status: ">
<span data-ls-if="({{user.emailVerification}} || {{user.phoneVerification}}) && {{user.status}} === true">
<span class="tag green">Verified</span>
</span>
<span data-ls-if="!({{user.emailVerification}} || {{user.phoneVerification}}) && {{user.status}} === true">
<span class="tag">Unverified</span>
</span>
<span data-ls-if="{{user.status}} === false">
<span class="tag red">Blocked</span>
</span>
</td>
<td data-title="Created: "><small data-ls-bind="{{user.registration|date}}"></small></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="users.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-users"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-users.total}}" 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-users.total|pageTotal}}"></span>
<form
data-service="users.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-users"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-users.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="box modal close" data-button-text="Add User">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Create User</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create User"
data-service="users.create"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created user successfully"
data-success-param-trigger-events="users.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create user"
data-failure-param-alert-classname="error">
<label for="userId">User ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="users.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
id="userId"
name="userId" />
<label for="user-name">Name</label>
<input type="text" class="full-width" id="user-name" name="name" required autocomplete="off" maxlength="128" />
<label for="user-email">Email</label>
<input type="email" class="full-width" id="user-email" name="email" required autocomplete="off" />
<label for="user-password">Password</label>
<input type="password" class="full-width" id="user-password" name="password" required minlength="8" title="Eight or more characters" autocomplete="off" />
<hr />
<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/users/teams?project={{router.params.project}}">
<h2>Teams</h2>
<form class="box padding-small margin-bottom search"
data-service="teams.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-teams"
data-success="state"
data-success-param-state-keys="search,offset">
<div class="row thin responsive">
<div class="col span-10">
<input name="search" id="searchTeams" 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="teams.list"
data-event="load,teams.create,teams.update,teams.delete"
data-param-search="{{router.params.search}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-teams">
<div data-ls-if="0 == {{project-teams.total}}" class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Teams Found</h3>
<p class="margin-bottom-no">Create your first team to get started</p>
</div>
<div data-ls-if="0 != {{project-teams.total}}">
<div class="margin-bottom-small text-align-end text-size-small text-fade"><span data-ls-bind="{{project-teams.total}}"></span> teams found</div>
<div class="box margin-bottom">
<table class="vertical">
<thead>
<tr>
<th width="40"></th>
<th>Name</th>
<th width="150">Members</th>
<th width="150">Created</th>
</tr>
</thead>
<tbody data-ls-loop="project-teams.teams" data-ls-as="team">
<tr>
<td class="hide">
<img src="" data-ls-attrs="src={{team.name|avatar}}" data-size="45" alt="Collection Avatar" class="avatar margin-end pull-start" loading="lazy" width="30" height="30" />
</td>
<td data-title="Name: ">
<a data-ls-attrs="href=/console/users/teams/team?id={{team.$id}}&project={{router.params.project}}" data-ls-bind="{{team.name}}" data-ls-attrs="title={{team.name}}"></a>
</td>
<td data-title="Members: "><span data-ls-bind="{{team.total}} members"></span></td>
<td data-title="Date Created: "><small data-ls-bind="{{team.$createdAt|date}}"></small></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="pull-end text-align-center paging">
<form
data-service="teams.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-teams"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-teams.total}}" 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-teams.total|pageTotal}}"></span>
<form
data-service="teams.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-teams"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-teams.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
<div data-ui-modal class="box modal close" data-button-text="Add Team">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Create Team</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Team"
data-service="teams.create"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created team successfully"
data-success-param-trigger-events="filter-teams-changed,teams.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create team"
data-failure-param-alert-classname="error">
<label for="teamId">Team ID</label>
<input
type="hidden"
data-custom-id
data-id-type="auto"
data-validator="teams.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
id="teamId"
name="teamId" />
<label for="team-name">Name</label>
<input type="text" class="full-width" id="team-name" name="name" required autocomplete="off" maxlength="128" />
<hr />
<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/users/providers?project={{router.params.project}}">
<p data-ls-if="{{console-project.authLimit}} == 0" class="text-fade text-size-small margin-bottom pull-end">Unlimited Users <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Set Limit</span></p>
<p data-ls-if="{{console-project.authLimit}} != 0" class="text-fade text-size-small margin-bottom pull-end"><span data-ls-bind="{{console-project.authLimit|statsTotal}}"></span> Users allowed <span class="link" data-ls-ui-trigger="project-update-auth-users-limit">Change Limit</span></p>
<h2>Settings</h2>
<div data-ui-modal class="modal close" data-button-alias="none" data-open-event="project-update-auth-users-limit">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Max Allowed Users</h1>
<form data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Users Limit"
data-service="projects.updateAuthLimit"
data-scope="console"
data-event="submit"
data-param-project-id="{{router.params.project}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated project users limit successfully"
data-success-param-trigger-events="projects.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update project users limit"
data-failure-param-alert-classname="error">
<input name="limit" id="users-limit" type="number" data-ls-bind="{{console-project.authLimit}}" data-cast-to="numeric" min="0" max="<?php echo APP_LIMIT_USERS; ?>" />
<div class="info row thin margin-bottom margin-top">
<div class="col span-1">
<i class="icon-info-circled text-sign"></i>
</div>
<div class="col span-11">This limit will prevent new users from signing up for your project, no matter what auth method has been used. You will still be able to create users and team memberships from your Appwrite console. For an unlimited amount of users, set the limit to 0. Max limit is <?php echo number_format(APP_LIMIT_USERS); ?>.</div>
</div>
<button>Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<p class="text-fade margin-bottom">Choose auth methods you wish to use.</p>
<ul class="tiles cell-3 margin-bottom-small">
<?php foreach($auth as $index => $method):
$key = ucfirst($method['key'] ?? '');
$name = $method['name'] ?? '';
$icon = $method['icon'] ?? '';
$docs = $method['docs'] ?? '';
$enabled = $method['enabled'] ?? false;
?>
<li class="">
<div class="box padding-small margin-bottom clear">
<?php if($enabled): ?>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Auth Status (<?php echo $this->escape($name); ?>)"
data-service="projects.updateAuthStatus"
data-scope="console"
data-event="change"
data-param-project-id="{{router.params.project}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated project auth status successfully"
data-success-param-trigger-events="projects.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update project auth status settings"
data-failure-param-alert-classname="error">
<input name="method" id="auth<?php echo $this->escape($key); ?>" type="hidden" autocomplete="off" value="<?php echo $this->escape($index); ?>">
<?php if( !(in_array($key, ['usersAuthMagicURL', 'usersAuthInvites']) && !$smtpEnabled)): ?>
<input name="status" type="hidden" data-forms-switch data-ls-bind="{{console-project.auth<?php echo $this->escape($key); ?>}}" data-cast-to="boolean" class="pull-end" />
<?php endif; ?>
</form>
<?php endif; ?>
<img src="<?php echo $this->escape($icon); ?>?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="Email/Password Logo" class="pull-start provider margin-end" />
<span class="text-size-small"><?php echo $this->escape($name); ?><?php if(!$enabled): ?> <span class="text-fade text-size-xs">soon</span><?php endif; ?>
<?php if( in_array($key, ['usersAuthMagicURL', 'usersAuthInvites']) && !$smtpEnabled): ?>
<p class="margin-bottom-no text-one-liner text-size-small text-danger">
SMTP Disabled
</p>
<?php else: ?>
<?php if($docs): ?>
<p class="margin-bottom-no text-one-liner text-size-small">
<a href="<?php echo $this->escape($docs); ?>" target="_blank" rel="noopener">Docs<i class="icon-link-ext"></i></a>
</p>
<?php endif; ?>
<?php endif; ?>
</div>
</li>
<?php endforeach;?>
</ul>
<h3>OAuth2 Providers</h3>
<div class="margin-bottom margin-top"
data-service="projects.get"
data-event="load,projects.create,projects.update,projects.deleteProject"
data-name="console-project"
data-param-project-id="{{router.params.project}}"
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;}
$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>
<h1><?php echo $this->escape($name); ?> <?php if ($sandbox): ?>Sandbox<?php endif;?> OAuth2 Settings</h1>
<form
autocomplete="off"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project OAuth2 (<?php echo $this->escape($name); ?>)"
data-service="projects.updateOAuth2"
data-scope="console"
data-event="submit"
data-param-project-id="{{router.params.project}}"
data-success="alert,trigger"
data-success-param-alert-text="Updated project OAuth2 settings successfully"
data-success-param-trigger-events="projects.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update project OAuth2 settings"
data-failure-param-alert-classname="error">
<input style="display: none" type="text" /> <?php /** Disabling Chrone Autofill @see https://stackoverflow.com/a/15917221/2299554 */?>
<input style="display: none" type="password" /> <?php /** Disabling Chrone Autofill @see https://stackoverflow.com/a/15917221/2299554 */?>
<input name="provider" id="provider<?php echo $this->escape(ucfirst($provider)); ?>" type="hidden" autocomplete="off" value="<?php echo $this->escape($provider); ?>">
<?php if (!$form): ?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">App ID</label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}">
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret">App Secret</label>
<input name="secret" data-forms-show-secret id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="password" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<?php else: ?>
<?php
$form = new View(__DIR__.'/oauth/'.$this->escape($form));
echo $form
->setParam("provider", $provider)
->render();
?>
<?php endif; ?>
<div class="info row thin margin-bottom margin-top">
<div class="col span-1">
<i class="icon-info-circled text-sign"></i>
</div>
<div class="col span-11">
<p>To complete set up, add this OAuth2 redirect URI to your <?php echo $this->escape(ucfirst($provider)); ?> app configuration.</p>
<div class="input-copy">
<input data-forms-copy type="text" disabled data-ls-bind="{{env.ENDPOINT}}/v1/account/sessions/oauth2/callback/<?php echo $this->escape($provider); ?>/{{router.params.project}}" class="margin-bottom-no" />
</div>
</div>
</div>
<button>Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
<div class="box padding-small margin-bottom">
<span data-ls-if="
{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}} &&
{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<button class="switch on pull-end" data-ls-ui-trigger="provider-update-<?php echo $provider; ?>"></button>
</span>
<span data-ls-if="
!{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}} ||
!{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}">
<button class="switch pull-end" data-ls-ui-trigger="provider-update-<?php echo $this->escape($provider); ?>"></button>
</span>
<img src="/images/users/<?php echo $this->escape(strtolower($provider)); ?>.png?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="<?php echo $this->escape(ucfirst($provider)); ?> Logo" class="pull-start provider margin-end" />
<span class="text-size-small">
<?php echo $this->escape($name); ?> <?php if ($sandbox): ?><span class="text-size-xs text-fade">sandbox</span><?php endif;?> <?php if ($beta): ?><span class="text-size-xs text-fade">beta</span><?php endif;?>
</span>
<p class="margin-bottom-no text-one-liner text-size-small">
<a href="<?php echo $data['developers']; ?>" target="_blank" rel="noopener">OAuth2 Docs<i class="icon-link-ext"></i></a>
</p>
</div>
</li>
<?php endforeach;?>
</ul>
</div>
</li>
<li data-state="/console/users/usage?project={{router.params.project}}">
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="users.getUsage"
data-event="submit"
data-name="usage"
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="users.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>
<form
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="users.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>
<h2>Usage</h2>
<div data-service="users.getUsage" data-event="load" data-name="usage">
<h3 class="margin-bottom-tiny">Users</h3>
<p class="text-fade">Count of users 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-forms-chart="Users=usersCount" data-show-y-axis="true" data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Users <span data-ls-bind="({{usage.usersCount|statsGetLast|statsTotal}})"></span></li>
</ul>
<h3 class="margin-bottom-tiny">Operations</h3>
<p class="text-fade">Count of users 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-forms-chart="Created=usersCreate,Read=usersRead,Updated=usersUpdate,Deleted=usersDelete"
data-show-y-axis="true"
data-colors="create,read,update,delete"
data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large crud">
<li>Created</li>
<li>Read</li>
<li>Updated</li>
<li>Deleted</li>
</ul>
</div>
</li>
</ul>
</div>

View file

@ -1,13 +0,0 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Bundle ID <span class="tooltip" data-tooltip="Attribute internal display name"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="com.company.appname" />
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
<div>
<div class="row thin">
<div class="col span-6"><label>Key ID</label><input id="oauth2AppleKeyId" type="text" placeholder="SHAB13ROFN"></div>
<div class="col span-6"><label>Team ID</label><input id="oauth2AppleTeamId" type="text" placeholder="ELA2CD3AED"></div>
</div><label>P8 File</label><textarea id="oauth2AppleP8" class="margin-bottom-no"></textarea>
</div>

View file

@ -1,12 +0,0 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided by Auth0"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Application you created in Auth0"><i class="icon-info-circled"></i></span></label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">Auth0 Domain<span class="tooltip" data-tooltip="Your Auth0 Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
<input name="auth0Domain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="YOUR_DOMAIN" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -1,12 +0,0 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">authentik Base-Domain<span class="tooltip" data-tooltip="Your authentik Base-Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
<input name="authentikDomain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="auth.example.com" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -1,12 +0,0 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">App ID</label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret">App Secret</label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret}}" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Endpoint">Endpoint (optional)<span class="tooltip" data-tooltip="If you have self-hosted instance, provide the host."><i class="icon-info-circled"></i></span></label>
<input name="endpoint" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Endpoint" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Endpoint}}" placeholder="https://gitlab.com" />
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -1,12 +0,0 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Application (Client) ID<span class="tooltip" data-tooltip="Provided by AzureAD"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Application ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Created by you in AzureAD Portal"><i class="icon-info-circled"></i></span></label>
<input name="appSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>TenantId">Target Tenant<span class="tooltip" data-tooltip="'common', 'organizations', 'consumers' or your TenantId"><i class="icon-info-circled"></i></span><a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints">More info</a></label>
<input name="appSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>TenantId" type="text" autocomplete="off" placeholder="'common', 'organizations', 'consumers' or your TenantId" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -1,14 +0,0 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided by Okta"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Application you created in Okta"><i class="icon-info-circled"></i></span></label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">Okta Domain<span class="tooltip" data-tooltip="Your Okta Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
<input name="oktaDomain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="dev-1337.okta.com" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>AuthorizationServerId">Authorization Server ID<span class="tooltip" data-tooltip="Authorization Server ID for custom authorization servers"><i class="icon-info-circled"></i></span></label>
<input name="authorizationServerId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>AuthorizationServerId" type="text" autocomplete="off" placeholder="default" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />

View file

@ -1,292 +0,0 @@
<div
data-service="teams.get"
data-name="team"
data-event="load,teams.update,teams.deleteMembership,teams.createMembership"
data-param-team-id="{{router.params.id}}"
data-success="trigger"
data-success-param-trigger-events="teams.get">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/users/teams?project={{router.params.project}}" class="back text-size-small"><i class="icon-left-open"></i> Teams</a>
<br />
<span data-ls-bind="{{team.name}}">&nbsp;</span>
<span data-ls-if="{{team.name|escape}} === ''">Unknown</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="{{team}}" 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/users/teams/team?id={{router.params.id}}&project={{router.params.project}}">
<h2>Overview</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom-large">
<label>&nbsp;</label>
<div class="box margin-bottom-large">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Team"
data-service="teams.update"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger"
data-success-param-alert-text="Updated team successfully"
data-success-param-trigger-events="teams.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update team"
data-failure-param-alert-classname="error">
<input data-ls-attrs="id=team-id-{{team.$id}}" name="teamId" type="hidden" disabled data-ls-bind="{{team.$id}}" />
<label for="name">Name</label>
<input data-ls-attrs="id=team-name-{{team.$id}}" name="name" type="text" autocomplete="off" data-ls-bind="{{team.name}}" maxlength="128" />
<hr />
<button>Update</button>
</form>
</div>
<h3 class="margin-bottom">Members</h3>
<div
data-service="teams.listMemberships"
data-event="load,teams.create,teams.update,teams.delete,teams.deleteMembership,teams.createMembership"
data-param-team-id="{{router.params.id}}"
data-param-queries="limit(<?php echo APP_PAGING_LIMIT; ?>),offset({{router.params.offset|orZero}}),orderDesc('')"
data-param-queries-cast-to="array"
data-param-queries-cast-from="csv"
data-scope="sdk"
data-name="project-members">
<div data-ls-if="0 == {{project-members.total}}" class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Memberships Found</h3>
<p class="margin-bottom-no">Add your first team member to get started</p>
</div>
<div data-ls-if="0 != {{project-members.total}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small margin-top-negative text-fade"><span data-ls-bind="{{project-members.total}}"></span> memberships found</div>
<div class="box margin-bottom">
<ul data-ls-loop="project-members.memberships" data-ls-as="member" class="list">
<li class="clear">
<form class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Team Membership"
data-service="teams.deleteMembership"
data-event="submit"
data-success="alert,trigger"
data-confirm="Are you sure you want to remove that user from the team?"
data-success-param-alert-text="Member Removed Successfully"
data-success-param-trigger-events="teams.deleteMembership"
data-failure="alert"
data-failure-param-alert-text="Failed to Remove Member"
data-failure-param-alert-classname="error">
<input name="teamId" data-ls-attrs="id={{member.$id}}" type="hidden" data-ls-bind="{{router.params.id}}">
<input name="membershipId" data-ls-attrs="id=leave-membershipId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
<button class="danger">Remove</button>
</form>
<img src="" data-ls-attrs="src={{member.userName|avatar}}" data-size="200" alt="User Avatar" class="avatar pull-start margin-end" loading="lazy" width="60" height="60" />
<div class="margin-bottom-tiny">
<a data-ls-attrs="href=/console/users/user?id={{member.userId}}&project={{router.params.project}}"><span data-ls-bind="{{member.userName}}"></span></a> &nbsp;&nbsp;
<span data-ls-if="1 == {{member.roles.length}}" class="text-fade tooltip" data-ls-bind="{{member.roles.length}} role" data-ls-attrs="data-tooltip={{member.roles|arraySentence}}"></span>
<span data-ls-if="2 <= {{member.roles.length}}" class="text-fade tooltip" data-ls-bind="{{member.roles.length}} roles" data-ls-attrs="data-tooltip={{member.roles|arraySentence}}"></span>
</div>
<small class="text-size-small text-fade" data-ls-bind="{{member.userEmail}}"></small> &nbsp;&nbsp;<span data-ls-if="false === {{member.confirm}}" class="text-danger text-size-small">Pending Approval</span>
</li>
</ul>
</div>
</div>
<div data-ui-modal class="box modal close" data-button-text="Add Member" data-button-class="pull-start">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add Member</h1>
<form name="teams.createTeamMembership"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Team Membership"
data-service="teams.createMembership"
data-event="submit"
data-loading="Adding user, please wait..."
data-success="alert,trigger,reset"
data-success-param-alert-text="User added successfully"
data-success-param-trigger-events="teams.createMembership"
data-failure="alert"
data-failure-param-alert-text="Failed to add user"
data-failure-param-alert-classname="error">
<input name="teamId" id="team-teamId" type="hidden" data-ls-bind="{{team.$id}}">
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}" />
<label for="email">Email</label>
<input name="email" id="email" type="email" autocomplete="email" required>
<label for="team-name">Name <small>(optional)</small></label>
<input name="name" id="team-name" type="text" autocomplete="name" maxlength="128" />
<label for="roles">Roles</label>
<input type="hidden" id="team-roles" name="roles" data-forms-tags data-cast-to="json" data-ls-bind="<?php echo $this->escape(json_encode(['owner'])); ?>" placeholder="Add any role you like" />
<hr />
<div class="clear">
<button>Add Member</button>
&nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</div>
</form>
</div>
<div class="clear text-align-center paging pull-end">
<form
data-service="teams.listMemberships"
data-event="submit"
data-param-team-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-members"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-back data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-members.total}}" 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-members.total|pageTotal}}"></span>
<form
data-service="teams.listMemberships"
data-event="submit"
data-param-team-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-scope="sdk"
data-name="project-members"
data-success="state"
data-success-param-state-keys="search,offset">
<input type="hidden" name="offset">
<button name="queries" data-cast-to="array" data-cast-from="csv" data-paging-next data-paging-desc data-offset="{{router.params.offset}}" data-total="{{project-members.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
</div>
</div>
<div class="col span-4 sticky-top">
<label>Team ID</label>
<div class="input-copy margin-bottom">
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{team.$id}}" disabled data-forms-copy>
</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"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (Team)">
View as JSON
</button>
</li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{team.$createdAt|date}}"></span></li>
</ul>
<form name="teams.delete" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Team"
data-service="teams.delete"
data-event="submit"
data-confirm="Are you sure you want to delete this team?"
data-param-team-id="{{router.params.id}}"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Team deleted successfully"
data-success-param-trigger-events="users.update"
data-success-param-redirect-url="/console/users/teams?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete team"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Team</button>
</form>
</div>
</div>
</li>
<li data-state="/console/users/teams/team/audit?id={{router.params.id}}&project={{router.params.project}}">
<h2>Activity</h2>
<div
data-service="teams.listLogs"
data-name="logs"
data-param-team-id="{{router.params.id}}"
data-event="load,logs-load">
<div class="box margin-top margin-bottom" data-ls-if="{{logs.logs.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<h3 class="text-bold margin-bottom-no">No logs available.</h3>
</div>
<div class="box" data-ls-if="{{logs.logs.length}} !== 0" style="display: none">
<table class="vertical small">
<thead>
<tr>
<th width="140">Date</th>
<th width="175">Event</th>
<th>Client</th>
<th width="90">Location</th>
<th width="90">IP</th>
</tr>
</thead>
<tbody data-ls-loop="logs.logs" data-ls-as="log">
<tr>
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.clientCode|lowercase}} !== 'cli'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.clientCode|lowercase}} === 'cli'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
<div data-ls-if="(!{{log.clientName}})" class="text-align-center text-fade">Unknown</div>
</td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.countryCode}} !== '--'" 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>
</li>
</ul>
</div>
</div>

View file

@ -1,618 +0,0 @@
<div
data-service="users.get"
data-name="user"
data-event="load,users.update"
data-param-user-id="{{router.params.id}}"
data-success="trigger"
data-success-param-trigger-events="users.get">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/users?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Users</a>
<br />
<span data-ls-bind="{{user.name}}">&nbsp;</span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} !== ''">Unknown</span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === ''">Anonymous User</span>
</h1>
</div>
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-name">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Update Name</h2>
<form name="users.updateName"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Name"
data-service="users.updateName"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User name was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user name"
data-failure-param-alert-classname="error">
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<label for="name">Name</label>
<input name="name" id="name" type="text" autocomplete="off" data-ls-bind="{{user.name}}" required class="full-width" maxlength="128">
<hr />
<footer>
<button type="submit" class="">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-email">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Update Email</h2>
<form name="users.updateEmail"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Email"
data-service="users.updateEmail"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User email was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user email"
data-failure-param-alert-classname="error">
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<label for="email">Email</label>
<input name="email" id="email" type="text" autocomplete="off" data-ls-bind="{{user.email}}" required class="full-width" maxlength="128">
<hr />
<footer>
<button type="submit" class="">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-phone">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Update Phone</h2>
<form name="users.updatePhone"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Phone"
data-service="users.updatePhone"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User phone was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user email"
data-failure-param-alert-classname="error">
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<label for="number">Phone number</label>
<input name="number" id="number" type="text" autocomplete="off" data-ls-bind="{{user.phone}}" pattern="^\+?[1-9]\d{1,14}$" class="full-width" title="Phone number with a leading '+' and maximum of 15 digits (+123456789).">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Phone number with a leading '+' and maximum of 15 digits (+123456789)</div>
<hr />
<footer>
<button type="submit" class="">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-password">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h2>Update Password</h2>
<form name="users.updatePassword"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Password"
data-service="users.updatePassword"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User password was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user password"
data-failure-param-alert-classname="error">
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<label for="password">Password</label>
<input name="password" id="password" type="password" autocomplete="off" required class="full-width" maxlength="128">
<hr />
<footer>
<button type="submit" class="">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</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="{{user}}" 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/users/user?id={{router.params.id}}&project={{router.params.project}}">
<h2>Overview</h2>
<div data-ls-if="{{user.status}} === false" style="display: none" class="box padding-small danger margin-bottom-xxl text-align-center">
This user account is blocked.
</div>
<div class="row responsive margin-top-xl">
<div class="col span-8">
<label>&nbsp;</label>
<div class="box margin-bottom-large" style="padding-bottom: 5px">
<div class="text-align-center">
<img src="" data-ls-attrs="src={{user|avatar}}" data-size="200" alt="User Avatar" class="avatar huge margin-top-negative-xxl" />
<div class="margin-top-small margin-bottom-small" data-ls-bind="Member since {{user.registration|date}}"></div>
<hr class="margin-top-tiny margin-bottom-tiny" data-ls-if="{{user.email}}">
<div class="margin-top-small margin-bottom-small clear" data-ls-if="{{user.email}}">
<span data-ls-bind="{{user.email}}" class="pull-start"></span>
<span data-ls-if="{{user.emailVerification}} === true" class="pull-end">
<span class="tag green">Verified</span>
</span>
<span data-ls-if="{{user.emailVerification}} !== true" class="pull-end">
<span class="tag">Unverified</span>
</span>
</div>
<hr class="margin-top-tiny margin-bottom-tiny" data-ls-if="{{user.phone}}">
<div class="margin-top-small margin-bottom-small clear" data-ls-if="{{user.phone}}">
<span data-ls-bind="{{user.phone}}" class="pull-start"></span>
<span data-ls-if="{{user.phoneVerification}} === true" class="pull-end">
<span class="tag green">Verified</span>
</span>
<span data-ls-if="{{user.phoneVerification}} !== true" class="pull-end">
<span class="tag">Unverified</span>
</span>
</div>
</div>
</div>
<h3 class="margin-bottom">Preferences</h3>
<div class="box margin-bottom"
data-service="users.getPrefs"
data-name="user-prefs"
data-event="load"
data-param-user-id="{{router.params.id}}"
data-success="trigger"
data-success-param-trigger-events="users.getPrefs">
<div data-ls-if="!{{user-prefs|isEmptyObject}}">
<table class="vertical">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody data-ls-loop="user-prefs" data-ls-as="pref">
<tr>
<td data-title="Key: ">
<span data-ls-bind="{{$index}}"></span>
</td>
<td data-title="Value: ">
<span data-ls-bind="{{pref}}"></span>
</td>
</tr>
</tbody>
</table>
</div>
<div data-ls-if="{{user-prefs|isEmptyObject}}">
No user preferences found
</div>
</div>
<h3 class="text-danger">Danger Zone</h3>
<div class="box danger margin-bottom">
<p>This is the area where you can delete this user.</p>
<p>By deleting this user you will lose all data associated with this user.</p>
<p>PLEASE NOTE: User deletion is irreversible.</p>
<form class="inline"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete User"
data-service="users.delete"
data-event="submit"
data-param-user-id="{{router.params.id}}"
data-success="alert,trigger,redirect"
data-confirm="Are you sure you want to delete this user?"
data-success-param-alert-text="Deleted user successfully"
data-success-param-trigger-events="users.delete"
data-success-param-redirect-url="/console/users?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete user"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<button class="danger reverse" type="submit">Delete User</button>
</form>
</div>
</div>
<div class="col span-4 sticky-top">
<label>User ID</label>
<div class="input-copy margin-bottom">
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{user.$id}}" disabled data-forms-copy>
</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-update-name" class="link text-size-small">Update Name</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-email" class="link text-size-small">Update Email</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-phone" class="link text-size-small">Update Phone</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-password" class="link text-size-small">Update Password</button></li>
<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"
data-analytics
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="View as JSON (User)">
View as JSON
</button>
</li>
</ul>
<div data-ls-if="{{user.email}} && {{user.emailVerification}} === false" style="display: none">
<form name="users.updateVerification" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Email Verification Status"
data-service="users.updateEmailVerification"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User verification status was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user verification status"
data-failure-param-alert-classname="error"
>
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<input type="hidden" disabled name="emailVerification" value="true" data-cast-to="boolean">
<button type="submit" class="dark fill">Verify Email</button>
</form>
</div>
<div data-ls-if="{{user.email}} && {{user.emailVerification}} === true" style="display: none">
<form name="users.updateVerification" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Email Verification Status"
data-service="users.updateEmailVerification"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User verification status was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user verification status"
data-failure-param-alert-classname="error"
>
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<input type="hidden" disabled name="emailVerification" value="false" data-cast-to="boolean">
<button type="submit" class="dark fill">Unverify Email</button>
</form>
</div>
<div data-ls-if="{{user.phone}} && {{user.phoneVerification}} === false" style="display: none">
<form name="users.updateVerification" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Phone Verification Status"
data-service="users.updatePhoneVerification"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User verification status was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user verification status"
data-failure-param-alert-classname="error"
>
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<input type="hidden" disabled name="phoneVerification" value="true" data-cast-to="boolean">
<button type="submit" class="dark fill">Verify Phone</button>
</form>
</div>
<div data-ls-if="{{user.phone}} && {{user.phoneVerification}} === true" style="display: none">
<form name="users.updateVerification" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Phone Verification Status"
data-service="users.updatePhoneVerification"
data-scope="sdk"
data-event="submit"
data-success="trigger,alert"
data-success-param-alert-text="User verification status was updated successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to update user verification status"
data-failure-param-alert-classname="error"
>
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
<input type="hidden" disabled name="phoneVerification" value="false" data-cast-to="boolean">
<button type="submit" class="dark fill">Unverify Phone</button>
</form>
</div>
<div data-ls-if="{{user.status}} === true" style="display: none">
<form name="users.updateStatus" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Status"
data-service="users.updateStatus"
data-event="submit"
data-param-user-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Blocked user successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to block user"
data-failure-param-alert-classname="error">
<button name="status" type="submit" class="danger fill" value="false" data-cast-to="boolean">Block Account</button>
</form>
</div>
<div data-ls-if="{{user.status}} === false" style="display: none">
<form name="users.updateStatus" class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update User Status"
data-service="users.updateStatus"
data-event="submit"
data-param-user-id="{{router.params.id}}"
data-success="alert,trigger"
data-success-param-alert-text="Activated user successfully"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to activate user"
data-failure-param-alert-classname="error">
<button name="status" type="submit" class="fill" value="true" data-cast-to="boolean">Activate Account</button>
</form>
</div>
</div>
</div>
</li>
<li data-state="/console/users/user/memberships?id={{router.params.id}}&project={{router.params.project}}">
<h2>Memberships</h2>
<div
data-service="users.listMemberships"
data-name="memberships"
data-param-user-id="{{router.params.id}}"
data-event="load,users.update,teams.deleteMembership">
<div class="box margin-top margin-bottom" data-ls-if="{{memberships.memberships.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<h3 class="text-bold margin-bottom-no">No memberships available.</h3>
</div>
<div data-ls-if="0 != {{memberships.total}}">
<div class="margin-bottom-small margin-end-small text-align-end text-size-small margin-top-negative text-fade"><span data-ls-bind="{{memberships.total}}"></span> memberships found</div>
<div class="box margin-bottom">
<ul data-ls-loop="memberships.memberships" data-ls-as="membership" class="list">
<li class="clear">
<form class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Team Membership"
data-service="teams.deleteMembership"
data-event="submit"
data-success="alert,trigger"
data-confirm="Are you sure you want to remove that user from the team?"
data-success-param-alert-text="Member Removed Successfully"
data-success-param-trigger-events="teams.deleteMembership"
data-failure="alert"
data-failure-param-alert-text="Failed to Remove Member"
data-failure-param-alert-classname="error">
<input name="teamId" data-ls-attrs="id=leave-teamId-{{membership.teamId}}" type="hidden" data-ls-bind="{{membership.teamId}}">
<input name="membershipId" data-ls-attrs="id=leave-membershipId-{{membership.$id}}" type="hidden" data-ls-bind="{{membership.$id}}">
<button class="danger">Remove</button>
</form>
<div class="pull-start margin-end">
<img src="" data-ls-attrs="src={{membership.teamName|avatar}},alt={{membership.teamName}}" data-size="60" class="avatar margin-end pull-start" loading="lazy" width="60" height="60" />
</div>
<a data-ls-attrs="href=/console/users/teams/team?id={{membership.teamId}}&project={{router.params.project}}" data-ls-bind="{{membership.teamName}}"></a>
<div class="margin-top-small">
<span data-ls-if="1 == {{membership.roles.length}}" class="text-fade tooltip" data-ls-bind="{{membership.roles.length}} role" data-ls-attrs="data-tooltip={{membership.roles|arraySentence}}"></span>
<span data-ls-if="2 <= {{membership.roles.length}}" class="text-fade tooltip" data-ls-bind="{{membership.roles.length}} roles" data-ls-attrs="data-tooltip={{membership.roles|arraySentence}}"></span>
<span data-ls-if="false === {{membership.confirm}}" class="text-danger text-size-small">Pending Approval</span>
</div>
</li>
</ul>
</div>
</div>
</div>
</li>
<li data-state="/console/users/user/sessions?id={{router.params.id}}&project={{router.params.project}}">
<h2>Sessions</h2>
<div
data-service="users.listSessions"
data-name="sessions"
data-param-user-id="{{router.params.id}}"
data-event="load,users.update">
<div class="box margin-top margin-bottom" data-ls-if="{{sessions.sessions.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<h3 class="text-bold margin-bottom-no">No sessions available.</h3>
</div>
<div data-ls-if="{{sessions.sessions.length}} !== 0" style="display: none">
<div class="box margin-bottom">
<ul data-ls-loop="sessions.sessions" data-ls-as="session" class="list">
<li class="clear">
<form class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete User Session"
data-service="users.deleteSession"
data-event="submit"
data-loading="Loading..."
data-success="trigger"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to logout session"
data-failure-param-alert-classname="error">
<input type="hidden" name="userId" data-ls-bind="{{router.params.id}}">
<input type="hidden" name="sessionId" data-ls-bind="{{session.$id}}">
<button class="danger">Logout</button>
</form>
<div class="pull-start margin-end avatar-container">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{session.clientCode|lowercase}} !== 'cli'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{session.clientCode|lowercase}} === 'cli'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" />
<div data-ls-if="{{session.provider}} !== 'email'" class="corner">
<img data-ls-attrs="src=/images/users/{{session.provider}}.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.provider}},alt={{session.provider}}" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
</div>
<span data-ls-bind="{{session.clientName}}"></span> <span data-ls-bind="{{session.clientVersion}}"></span> on <span data-ls-bind="{{session.deviceModel}}"></span> <span data-ls-bind="{{session.osName}}"></span> <span data-ls-bind="{{session.osVersion}}"></span>
<div class="margin-top-small">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=120&height=120&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.countryName}}"></small>
</div>
</li>
</ul>
</div>
<form class="inline margin-bottom-large"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete User Sessions"
data-service="users.deleteSessions"
data-param-user-id="{{router.params.id}}"
data-event="submit"
data-success="trigger"
data-success-param-trigger-events="users.update"
data-failure="alert"
data-failure-param-alert-text="Failed to logout all sessions"
data-failure-param-alert-classname="error">
<button class="danger">Logout from all sessions</button>
</form>
</div>
</div>
</li>
<li data-state="/console/users/user/audit?id={{router.params.id}}&project={{router.params.project}}">
<h2>Activity</h2>
<div
data-service="users.listLogs"
data-name="logs"
data-param-user-id="{{router.params.id}}"
data-event="load,logs-load">
<div class="box margin-top margin-bottom" data-ls-if="{{logs.logs.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<h3 class="text-bold margin-bottom-no">No logs available.</h3>
</div>
<div class="box" data-ls-if="{{logs.logs.length}} !== 0" style="display: none">
<table class="vertical small">
<thead>
<tr>
<th width="140">Date</th>
<th width="175">Event</th>
<th>Client</th>
<th width="90">Location</th>
<th width="90">IP</th>
</tr>
</thead>
<tbody data-ls-loop="logs.logs" data-ls-as="log">
<tr>
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.clientCode|lowercase}} !== 'cli'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.clientCode|lowercase}} === 'cli'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
<div data-ls-if="(!{{log.clientName}})" class="text-align-center text-fade">Unknown</div>
</td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-if="{{log.countryCode}} !== '--'" 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>
</li>
</ul>
</div>
</div>

View file

@ -1,66 +0,0 @@
<?php
$events = $this->getParam('events', []);
$patterns = [];
foreach ($events as $name => $event) {
foreach ($event as $key => $value) {
if (!\str_starts_with($key, '$')) {
if (!($value['$resource'] ?? false)) {
$patterns[] = "{$name}.{$key}";
} else {
foreach ($value as $key2 => $value2) {
if (!\str_starts_with($key2, '$')) {
if (!($value2['$resource'] ?? false)) {
$patterns[] = "{$key}.{$key2}";
}
}
}
}
}
}
}
?>
<div class="cover margin-bottom-large">
<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>
<br />
<span>Webhooks</span>
</h1>
</div>
<div class="zone xl"
data-service="projects.listWebhooks"
data-scope="console"
data-event="load,projects.createWebhook,projects.updateWebhook,projects.deleteWebhook"
data-name="console-webhooks"
data-param-project-id="{{router.params.project}}"
data-success="trigger"
data-success-param-trigger-events="projects.listWebhooks">
<div data-ls-if="0 == {{console-webhooks.total}} || undefined == {{console-webhooks.total}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Webhooks Found</h3>
<p class="margin-bottom-no">You haven't created any webhooks for your project yet.</p>
</div>
<div class="box margin-bottom" data-ls-if="0 != {{console-webhooks.total}}">
<ul data-ls-loop="console-webhooks.webhooks" data-ls-as="webhook" class="list">
<li class="clear">
<a data-ls-attrs="href=/console/webhooks/webhook?project={{router.params.project}}&id={{webhook.$id}}" class="button reverse pull-end margin-end">Manage</a>
<span data-ls-bind="{{webhook.name}}"></span> &nbsp; (<span data-ls-bind="{{webhook.events.length}}"></span> events)
<span data-ls-if="false === {{webhook.security}}">
&nbsp; <small class="text-danger">(SSL/TLS Disabled)</small>
</span>
<div class="margin-top-tiny">
<a data-ls-attrs="href={{webhook.url}}" data-ls-bind="{{webhook.url}}" target="_blank" class="text-one-liner"></a>
</div>
</li>
</ul>
</div>
<a data-ls-attrs="href=/console/webhooks/webhook/new?project={{router.params.project}}" class="button">Add Webhook</a>
</div>

View file

@ -1,261 +0,0 @@
<?php
$new = $this->getParam('new', false);
$events = $this->getParam('events', []);
$patterns = [
'documents',
'documents.create',
'documents.update',
'documents.delete',
];
foreach ($events as $name => $event) {
$patterns[] = $name;
foreach ($event as $key => $value) {
if (!\str_starts_with($key, '$')) {
if (!($value['$resource'] ?? false)) {
$patterns[] = "{$name}.{$key}";
} else {
$patterns[] = $key;
foreach ($value as $key2 => $value2) {
if (!\str_starts_with($key2, '$')) {
if (!($value2['$resource'] ?? false)) {
$patterns[] = "{$key}.{$key2}";
}
}
}
}
}
}
}
sort($patterns);
?>
<div
data-service="projects.getWebhook"
data-name="project-webhook"
data-scope="console"
data-event="load,projects.createWebhook,projects.deleteWebhook,projects.updateWebhook,projects.updateWebhookSignature"
data-param-project-id="{{router.params.project}}"
data-param-webhook-id="{{router.params.id}}"
data-success="trigger"
data-success-param-trigger-events="projects.getWebhook">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/webhooks?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Webhooks</a>
<br />
<?php if ($new) : ?>
<span>Add Webhook</span>
<? else : ?>
<span data-ls-bind="{{project-webhook.name}}&nbsp;">&nbsp;</span>
<?php endif; ?>
</h1>
</div>
<div class="zone xl" x-data="events">
<h2 class="margin-top">Settings</h2>
<div class="row responsive">
<div class="col span-8 margin-bottom">
<label>&nbsp;</label>
<div class="box margin-bottom-large">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
<?php if ($new) : ?>
data-success="alert,trigger,redirect"
data-analytics-label="Create Project Webhook"
data-service="projects.createWebhook"
data-success-param-alert-text="Created webhook successfully"
data-success-param-trigger-events="projects.createWebhook"
data-failure-param-alert-text="Failed to create webhook"
data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}"
<?php else : ?>
data-success="alert,trigger"
data-analytics-label="Update Project Webhook"
data-service="projects.updateWebhook"
data-success-param-alert-text="Updated webhook successfully"
data-success-param-trigger-events="projects.updateWebhook"
data-failure-param-alert-text="Failed to update webhook"
<?php endif; ?>
data-scope="console"
data-event="submit"
data-failure="alert"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<?php if (!$new) : ?><input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" /><?php endif; ?>
<label data-ls-attrs="for=name-{{webhook.$id}}">Name</label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{project-webhook.$id}}" name="name" required autocomplete="off" data-ls-bind="{{project-webhook.name}}" maxlength="128" />
<label data-ls-attrs="for=url-{{webhook.$id}}">POST URL</label>
<input type="url" class="full-width" data-ls-attrs="id=url-{{project-webhook.$id}}" name="url" required autocomplete="off" placeholder="https://example.com/callback" data-ls-bind="{{project-webhook.url}}" />
<section class="margin-bottom-small" data-ls-attrs="x-init=load({{project-webhook.events}})">
<label class="margin-bottom-small">Events <span class="tooltip small" data-tooltip="Set events that will trigger your webhook."><i class="icon-info-circled"></i></span></label>
<div>
<template x-for="event in Array.from(events)">
<div class="row events responsive thin margin-bottom-small">
<div class="col span-12 margin-bottom-small">
<span class="text" x-text="event"></span>
<span class="action" @click="removeEvent(event)">
<i class="icon-trash"></i>
</span>
</div>
<input name="events" data-cast-to="array" type="hidden" :value="event"></input>
</div>
</template>
</div>
<button class="margin-end margin-bottom-small reverse" type="button" @click="showModal($refs.modal_webhook)">Add Event</button>
</section>
<div class="margin-bottom toggle" data-ls-ui-open data-button-aria="Advanced Options">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<h2 class="margin-bottom">
Advanced Options
<small class="text-size-small">(optional)</small>
</h2>
<label data-ls-attrs="for=security-{{project-webhook.$id}}" class="margin-bottom-small">
<div class="margin-bottom-small">SSL / TLS (Certificate verification)</div>
<input <?php if ($new): ?>checked<?php else: ?>data-ls-bind="{{project-webhook.security}}"<?php endif; ?> name="security" type="radio" required data-ls-attrs="id=secure-yes-{{project-webhook.$id}}" value="true" data-cast-to="boolean" /> &nbsp; <span>Enabled</span> &nbsp;
<input name="security" type="radio" required data-ls-attrs="id=secure-no-{{project-webhook.$id}}" data-ls-bind="{{project-webhook.security}}" value="false" data-cast-to="boolean" /> &nbsp; <span>Disabled</span> &nbsp;
</label>
<p class="margin-bottom text-size-small text-fade"><span class="text-red">Warning</span>: Untrusted or self-signed certificates may not be secure.
<a href="https://en.wikipedia.org/wiki/Self-signed_certificate" target="_blank" rel="noopener">Learn more<i class="icon-link-ext"></i></a>
</p>
<label>HTTP Authentication <span class="tooltip" data-tooltip="Use to secure your endpoint from untrusted sources"><i class="icon-question"></i></span> &nbsp;<small>(optional)</small></label>
<div class="row responsive thin">
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpUser-{{project-webhook.$id}}">User</label>
<input type="text" class="full-width margin-bottom-no" data-ls-attrs="id=httpUser-{{project-webhook.$id}}" name="httpUser" autocomplete="off" data-ls-bind="{{project-webhook.httpUser}}" />
</div>
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpPass-{{project-webhook.$id}}">Password</label>
<input type="password" data-forms-show-secret class="full-width margin-bottom-no" data-ls-attrs="id=httpPass-{{project-webhook.$id}}" name="httpPass" autocomplete="off" data-ls-bind="{{project-webhook.httpPass}}" />
</div>
</div>
</div>
<footer>
<button type="submit">Update</button>
</footer>
</form>
</div>
</div>
<?php if (!$new) : ?>
<div class="col span-4 sticky-top margin-bottom">
<label>Webhook ID</label>
<div class="input-copy margin-bottom">
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-webhook.$id}}" disabled data-forms-copy>
</div>
<label>Signature Key <span class="tooltip small" data-tooltip="Can be used to validate your Webhooks."><i class="icon-info-circled"></i></span></label>
<div class="input-copy margin-bottom">
<input id="signatureKey" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-webhook.signatureKey}}" disabled data-forms-copy>
</div>
<form
class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Webhook Signature"
data-service="projects.updateWebhookSignature"
data-scope="console"
data-event="submit"
data-confirm="Are you sure you want to generate a new Signature Key?"
data-success="trigger"
data-success-param-alert-text="Updated webhook signature key successfully"
data-success-param-trigger-events="projects.updateWebhookSignature"
data-failure="alert"
data-failure-param-alert-text="Failed to update webhook signature key"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" />
<button class="fill">Update Signature Key</button>
</form>
<form
class="margin-bottom"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Webhook"
data-service="projects.deleteWebhook"
data-scope="console"
data-event="submit"
data-confirm="Are you sure you want to delete this webhook?"
data-success="alert,redirect"
data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}"
data-success-param-alert-text="Deleted webhook successfully"
data-success-param-trigger-events="projects.deleteWebhook"
data-failure="alert"
data-failure-param-alert-text="Failed to delete webhook"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" />
<button class="danger fill">Delete Webhook</button>
</form>
</div>
<?php endif; ?>
<div x-ref="modal_webhook" data-ui-modal class="modal box close width-small height-small" data-button-hide="on">
<div>
<form @submit.prevent="addEvent($refs.modal_webhook)">
<label for="event">
Event
</label>
<select id="event" x-model="selected" @change="setEvent()">
<option value="" selected>Select event</option>
<?php foreach ($patterns as $event) : ?>
<option value="<?php echo $event; ?>"><?php echo $event; ?></option>
<?php endforeach; ?>
</select>
<div x-show="hasResource">
<label x-text="resourceName + ' (optional)'" for="resource"></label>
<input id="resource" type="text" :placeholder="resourceName" x-model="resource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubResource">
<label x-text="subResourceName + ' (optional)'" for="subResource"></label>
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubSubResource">
<label x-text="subSubResourceName + ' (optional)'" for="subSubResource"></label>
<input id="subSubResource" type="text" :placeholder="subSubResourceName" x-model="subSubResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasAttribute">
<label for="attribute">Add Attribute (optional)</label>
<select id="attribute" x-model="attribute">
<option value="*">Select attribute</option>
<template x-for="attr in attributes">
<option :value="attr" x-text="attr"></option>
</template>
</select>
</div>
<button x-show="selected" type="submit">Add Event</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,19 +0,0 @@
<!-- <section class="zone xl">
<form
data-service="auth.confirm"
data-scope="console"
data-event="load"
data-param-token="{{router.params.token}}"
data-param-user-id="{{router.params.userId}}"
data-success="redirect,alert,trigger"
data-success-param-redirect-url="/console"
data-success-alert="Confirmation Completed Successfully"
data-success-triggers="account.update"
data-failure="alert"
data-failure-param-alert-text="Confirmation Failed"
data-failure-param-alert-classname="error">
<h2 class="margin-bottom-small">Account Confirmation in Progress</h2>
<p>Please wait a few seconds while your account is verified.</p>
</form>
</section> -->

View file

@ -1,59 +0,0 @@
<section class="zone medium">
<form class="box margin-top-large"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="home"
data-analytics-label="Update Team Membership Status"
data-service="teams.updateMembershipStatus"
data-scope="console"
data-event="submit"
data-param-team-id="{{router.params.teamId}}"
data-param-membership-id="{{router.params.membershipId}}"
data-param-user-id="{{router.params.userId}}"
data-param-secret="{{router.params.secret}}"
data-success="redirect,alert,trigger"
data-success-param-redirect-url="/console?project={{router.params.project}}"
data-success-param-alert-text="Joined team successfully"
data-success-param-trigger-events="teams.updateMembershipStatus"
data-failure="redirect,alert"
data-success-param-redirect-url="/console"
data-failure-param-alert-text="Failed to join team. Please try again later"
data-failure-param-alert-classname="error">
<div class="text-danger margin-bottom-large" data-ls-if="{{router.params.failure}} == 1">Failed to join team. Please try again later</div>
<h2 class="margin-bottom-small">Invitation</h2>
<p>You have been invited to join a team project on <?php echo APP_NAME; ?></p>
<!-- <div class=""
data-service="companies.getPreview"
data-scope="api"
data-name="api-company"
data-param-id="{{router.params.company}}"
data-event="load"
data-success="trigger"
data-success-triggers="api-company.load">
<div data-ls-if="{{api-company.logo}} !== '' || {{api-company.logo}} !== undefined">
<div data-ls-style="background: {{api-company.theme-color}}; color: {{api-company.theme-color-contrast}}; width: 100px; height: 100px; line-height: 100px; border-radius: 50%; margin: 0 auto; text-align: center">
<img data-ls-attrs="src={{env.API}}/v1/storage/files/{{api-company.logo}}/preview?project={{env.PROJECT}}&height=120" alt="Logo" style="opacity: 0; line-height: 80px; vertical-align: middle; max-width: 80px; max-height: 80px" />
</div>
</div>
</div> -->
<div class="agree margin-top margin-bottom">
<div class="pull-start margin-end-small margin-bottom">
<input type="checkbox" required />
</div>
By accepting the invitation, you agree to the <a href="/policy/terms" target="_blank">Terms and Conditions</a> and <a href="/policy/privacy" target="_blank">Privacy Policy</a>.
</div>
<div class="clear">
<button class="pull-start margin-end">Accept</button>
<a href="/" class="button reverse pull-start">Cancel</a>
</div>
</form>
</section>

View file

@ -1,38 +0,0 @@
<div class="zone large padding margin-top" id="message" style="display: none">
<h1 class="margin-bottom">Missing Redirect URL</h1>
<p>Your Magic URL login flow is missing a proper redirect URL. Please check the
<a href="https://<?php echo APP_DOMAIN; ?>/docs/client/account?sdk=web#createMagicURLSession">Magic URL docs</a>
and send request for new session with a valid redirect URL.</p>
</div>
<script>
setTimeout(function () {
document.getElementById('message').style.display = 'block';
}, 25);
const urlSearchParams = new URLSearchParams(window.location.search);
const {
userId,
secret,
project
} = Object.fromEntries(urlSearchParams.entries());
const formData = new FormData();
formData.append('userId', userId);
formData.append('secret', secret);
const res = fetch(window.location.origin + '/v1/account/sessions/magic-url', {
method: 'PUT',
body: formData,
headers: {
["x-appwrite-project"]: project
}
}).then(response => response.json())
.then(data => {
if(data.$id) {
window.location = 'appwrite-callback-' + project + '://'+window.location.search;
}
})
</script>
<hr />

View file

@ -1,18 +0,0 @@
<div class="zone large padding margin-top" id="message" style="display: none">
<h1 class="margin-bottom">Missing Redirect URL</h1>
<p>Your OAuth login flow is missing a proper redirect URL. Please check the
<a href="https://<?php echo APP_DOMAIN; ?>/docs/client/account?sdk=web#createOAuth2Session">OAuth docs</a>
and send request for new session with a valid callback URL.</p>
</div>
<script>
setTimeout(function () {
document.getElementById('message').style.display = 'block';
}, 25);
const query = new URLSearchParams(window.location.search);
const project = query.get('project');
window.location = 'appwrite-callback-'+project+'://'+window.location.search;
</script>
<hr />

View file

@ -1,43 +0,0 @@
<?php
$smtpEnabled = $this->getParam('smtpEnabled', false);
?>
<div class="zone medium">
<h1 class="zone xl margin-bottom-large margin-top">
Password Recovery
</h1>
<small class="pull-end text-size-small">* All fields are required</small>
<form name="account.createRecovery"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="home"
data-analytics-label="Create Account Recovery"
data-service="account.createRecovery"
data-scope="console"
data-event="submit"
data-success="alert"
data-success-param-alert-text="We have sent you a mail with a password reset link"
data-failure="alert"
data-failure-param-alert-text="Password recovery failed"
data-failure-param-alert-classname="error">
<label>Email</label>
<input name="email" type="email" class="full-width" autocomplete="email" placeholder="me@example.com" required>
<input name="url" type="hidden" data-ls-bind="{{env.ENDPOINT}}/auth/recovery/reset" />
<?php if(!$smtpEnabled): ?>
<div class="box note padding-tiny warning margin-bottom text-align-center">
<i class="icon-warning"></i> SMTP connection is disabled. <a href="https://appwrite.io/docs/email-delivery" target="_blank" rel="noopener">Learn more <i class="icon-link-ext"></i></a>
</div>
<?php endif; ?>
<button type="submit" class="btn btn-primary"<?php if(!$smtpEnabled): ?> disabled<?php endif; ?>><i class="fa fa-sign-in"></i> Recover</button>
</form>
</div>
<div class="zone medium text-align-center">
<a href="/auth/signin">Back to sign in</a>
</div>

View file

@ -1,38 +0,0 @@
<div class="zone medium">
<h1 class="zone xl margin-bottom margin-top">
Password Reset
</h1>
<small class="pull-end text-size-small">* All fields are required</small>
<br />
<br />
<form name="recovery-reset"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="home"
data-analytics-label="Update Account Recovery"
data-service="account.updateRecovery"
data-scope="console"
data-event="submit"
data-success="alert,redirect"
data-success-param-alert-text="Password Reset Completed"
data-success-param-redirect-url="/auth/signin"
data-failure="alert"
data-failure-param-alert-text="Password Reset Failed"
data-failure-param-alert-classname="error">
<input type="hidden" name="userId" data-ls-bind="{{router.params.userId}}">
<input type="hidden" name="secret" data-ls-bind="{{router.params.secret}}">
<label>Password</label>
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
<label>Confirm Password</label>
<input name="passwordAgain" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
<button type="submit" class="btn btn-primary"><i class="fa fa-sign-in"></i> Apply</button>
</form>
</div>

View file

@ -1,53 +0,0 @@
<?php
$root = ($this->getParam('root') !== 'disabled');
?>
<div class="zone medium"
data-service="account.get"
data-name="account"
data-scope="console"
data-event="load"
data-success="redirect"
data-success-param-redirect-url="/console"
data-success-param-trigger-events="account.get">
<div data-ls-if="{{account}} === undefined">
<h1 class="zone xl margin-bottom-large margin-top">
Sign In
</h1>
<div class="text-danger margin-bottom-large" data-ls-if="{{router.params.failure}} >= 1">Login failed. Please check your credentials.</div>
<p>Login using email and password</p>
<form name="account.createEmailSession"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="home"
data-analytics-label="Create Account Session"
data-service="account.createEmailSession"
data-scope="console"
data-event="submit"
data-success="trigger,hide,redirect"
data-success-param-trigger-events="account.createEmailSession"
data-success-param-redirect-url="/console"
data-failure="alert"
data-failure-param-alert-text="Login failed. Please check your credentials."
data-failure-param-alert-classname="error">
<input name="email" type="email" class="full-width" autocomplete="email" placeholder="Email" required>
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="Password" required minlength="8" title="Eight or more characters">
<button>Sign In</button>
</form>
<br />
<br />
<div class="text-line-high-large text-align-center">
<a href="/auth/recovery">Forgot password?</a><?php if(!$root): ?> or don't have an account? <b><a href="/auth/signup">Sign up now</a></b><?php endif; ?>
</div>
</div>
</div>

View file

@ -1,74 +0,0 @@
<?php
$root = ($this->getParam('root') !== 'disabled');
?>
<div class="zone medium signup"
data-service="account.get"
data-name="account"
data-scope="console"
data-event="load"
data-success="redirect"
data-success-param-redirect-url="/console"
data-success-param-trigger-events="account.get">
<h1 class="zone xl margin-bottom-large margin-top">
Sign Up
</h1>
<div class="text-danger margin-bottom-large" data-ls-if="{{router.params.failure}} == 1">Registration Failed. Please try again later</div>
<small class="pull-end text-size-small">* All fields are required</small>
<form name="account.create"
data-analytics
data-newsletter
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="home"
data-analytics-label="Create Account"
data-service="account.create"
data-scope="console"
data-event="submit"
data-success="trigger,hide"
data-success-param-trigger-events="account.create"
data-success-param-redirect-url="/console"
data-failure="alert"
data-failure-param-alert-text="Registration Failed. Please try again later"
data-failure-param-alert-classname="error">
<?php if($root): ?>
<p>Please create your root account</p>
<?php endif; ?>
<input name="userId" type="hidden" value="unique()">
<label>Name</label>
<input name="name" type="text" autocomplete="name" placeholder="" required maxlength="128">
<label>Email</label>
<input name="email" type="email" autocomplete="email" placeholder="" required data-ls-bind="{{router.params.email}}">
<label>Password</label>
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
<div class="agree margin-top-large margin-bottom-large">
<div class="pull-start margin-end-small margin-bottom">
<input type="checkbox" required />
</div>
By signing up, you agree to the <a data-ls-attrs="href={{env.HOME}}/policy/terms" tabindex="-1" target="_blank" rel="noopener">Terms and Conditions</a> and <a data-ls-attrs="href={{env.HOME}}/policy/privacy" target="_blank" tabindex="-1" rel="noopener">Privacy Policy</a>
</div>
<div class="newsletter margin-top margin-bottom-large">
<div class="pull-start margin-end-small margin-bottom">
<input id="newsletter" type="checkbox" />
</div>
Sign up for our <a data-ls-attrs="href={{env.HOME}}/updates" tabindex="-1" target="_blank" rel="noopener">monthly newsletter</a> <span class="text-fade">(optional)</span>.
</div>
<button type="submit">Sign Up</button>
</form>
</div>
<?php if(!$root): ?>
<div class="zone medium text-align-center">
<a href="/auth/signin">Already have an account?</a>
</div>
<?PHP endif; ?>

View file

@ -1,4 +0,0 @@
<?php
$version = $this->getParam('version', '').'.'.APP_CACHE_BUSTER;
?>
<footer>version <?php echo $version; ?></footer>

View file

@ -1,6 +0,0 @@
<div class="logo text-align-center margin-bottom-xl">
<a href="/">
<img src="/images/appwrite.svg" alt="Appwrite Light Logo" class="force-light" loading="lazy" />
<img src="/images/appwrite-footer-dark.svg" alt="Appwrite Dark Logo" class="force-dark" loading="lazy" />
</a>
</div>

View file

@ -1,153 +0,0 @@
<?php
$protocol = $this->getParam('protocol', '');
$domain = $this->getParam('domain', '');
$endpoint = $this->getParam('endpoint', '');
$platforms = $this->getParam('platforms', []);
$version = $this->getParam('version', '0.0.0');
$isDev = $this->getParam('isDev', false);
$litespeed = $this->getParam('litespeed', true);
$analytics = $this->getParam('analytics', 'UA-26264668-9');
$mode = $this->getParam('mode', '');
$canonical = $this->getParam('canonical', '');
$image = $this->getParam('image', '/images/logo.png');
$locale = $this->getParam('locale', null);
$runtimes = $this->getParam('runtimes', null);
if(!empty($platforms)) {
$platforms = array_map(function($platform) {
return [
'key' => $platform['key'],
'name' => $platform['name'],
'languages' => array_map(function($language) {
return [
'key' => $language['key'],
'name' => $language['name'] . (($language['beta']) ? ' (beta)' : ''),
];
}, array_filter($platform['languages'], function($node) {
return ($node['enabled']);
}))
];
}, $platforms);
}
?><!DOCTYPE html><!--
<?php echo $locale->getText('settings.inspire'); ?>
--><html lang="<?php echo $locale->getText('settings.locale'); ?>" class="<?php echo $this->getParam('class', 'none'); ?> <?php echo $mode; ?>">
<head>
<link rel="manifest" href="/manifest.json">
<title><?php echo $this->getParam('title', ''); ?></title>
<meta name="description" content="<?php echo $this->getParam('description', ''); ?>" />
<link rel="stylesheet" media="all" type="text/css" href="/dist/styles/default-<?php echo $locale->getText('settings.direction'); ?>.css?v=<?php echo APP_CACHE_BUSTER; ?>.<?php echo $version; ?>" />
<link rel="icon" type="image/png" href="<?php echo $this->escape($this->getParam('icon', '')); ?>?v=<?php echo APP_CACHE_BUSTER; ?>" />
<link rel="apple-touch-icon" href="/images/apple.png">
<!-- <link rel="preconnect" href="" /> -->
<?php if (!empty($canonical)): ?>
<link rel="canonical" href="<?php echo $this->escape($canonical); ?>" />
<?php endif; ?>
<?php foreach ($this->getParam('prefetch', []) as $prefetch): ?>
<link rel="prefetch" href="<?php echo $this->escape($prefetch); ?>" />
<?php endforeach; ?>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5" />
<meta name="theme-color" content="#f02e65">
<meta property="og:type" content="website" />
<meta property="og:site_name" content="<?php echo APP_NAME; ?>">
<meta property="og:title" content="<?php echo $this->escape($this->getParam('title', '')); ?>" />
<meta property="og:description" content="<?php echo $this->escape($this->getParam('description', '')); ?>" />
<?php if (!empty($canonical)): ?>
<meta property="og:url" content="<?php echo $this->escape($canonical); ?>" />
<?php endif; ?>
<meta property="og:image" content="<?php echo $this->escape($endpoint); ?><?php echo $this->escape($image); ?>?v=<?php echo APP_CACHE_BUSTER; ?>" />
<meta name="twitter:site" content="@<?php echo APP_SOCIAL_TWITTER_HANDLE; ?>">
<meta name="twitter:title" content="<?php echo $this->escape($this->getParam('title', '')); ?>">
<meta name="twitter:image:src" content="<?php echo $this->escape($endpoint); ?><?php echo $this->escape($image); ?>?v=<?php echo APP_CACHE_BUSTER; ?>">
<meta name="twitter:card" content="summary_large_image">
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '<?php echo $analytics; ?>', 'auto', {});
var APP_ENV = {
NAME: '<?php echo APP_NAME; ?>',
VERSION: '<?php echo $this->escape($version); ?>',
CACHEBUSTER: '<?php echo $this->escape($version); ?>/<?php echo APP_CACHE_BUSTER; ?>',
PROTOCOL: '<?php echo $this->escape($protocol); ?>',
ENDPOINT: '<?php echo $this->escape($endpoint); ?>',
DOMAIN: '<?php echo $this->escape($domain); ?>',
HOME: '<?php echo $this->escape($this->getParam('home')); ?>',
SETUP: '<?php echo $this->escape($this->getParam('setup')); ?>',
API: '/v1',
PROJECT: 'console',
RUNTIMES: <?php echo json_encode($runtimes); ?>,
PLATFORMS: <?php echo json_encode($platforms); ?>,
LOCALE: '<?php echo $this->escape($locale->getText('settings.locale')); ?>',
PREFIX: '<?php echo $this->escape($this->getParam('prefix')); ?>',
ROLES: <?php echo json_encode($this->getParam('roles', [])); ?>,
PAGING_LIMIT: <?PHP echo APP_PAGING_LIMIT; ?>
};
<?php if ($litespeed): ?>
document.addEventListener("DOMContentLoaded", function() {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = '/dist/scripts/app-all.js?v=<?php echo APP_CACHE_BUSTER; ?>.<?php echo $this->escape($version); ?>';
script.onload = function() {
window.ls.run(window);
};
head.appendChild(script);
});
<?php endif; ?>
</script>
<?php echo $this->exec($this->getParam('head', [])); ?>
</head>
<body class="theme-light" data-general-scroll-to data-general-scroll-direction>
<script>
let theme = window.localStorage.getItem('user-theme');
if(theme === 'theme-dark') {
document.body.classList.remove('theme-light');
document.body.classList.add('theme-dark');
}
</script>
<?php echo $this->exec($this->getParam('header', [])); ?>
<main data-ls-router data-first-from-server="true" data-acl data-analytics-pageview>
<?php echo $this->exec($this->getParam('body', [])); ?>
</main>
<div class="loader"></div>
<div data-cookies="We are using cookies to make this website easier to use."></div>
<section class="alerts">
<ul data-ls-loop="alerts.list" data-ls-as="alert">
<li data-forms-remove>
<div data-ls-attrs="class={{alert.class}} message">
<i class="icon-cancel" data-forms-run="alert.remove"></i>
<span data-ls-bind="{{alert.text}}"></span>
<span data-ls-if="undefined !== {{alert.link}}">
<a data-ls-attrs="href={{alert.link}}" data-ls-bind="{{alert.label}}" target="_blank" data-remove></a>
</span>
</div>
</li>
</ul>
</section>
<?php echo $this->exec($this->getParam('footer', [])); ?>
<!-- Version <?php echo $this->escape($version); ?> -->
</body>
</html>

View file

@ -1,3 +0,0 @@
<div data-acl data-page-title="<?php echo $this->escape($this->getParam('title', '')); ?>" data-analytics-pageview>
<?php echo $this->exec($this->getParam('body', [])); ?>
</div>

View file

@ -368,6 +368,7 @@ class FunctionsV1 extends Worker
$usage = new Stats($statsd);
$usage
->setParam('projectId', $project->getId())
->setParam('projectInternalId', $project->getInternalId())
->setParam('functionId', $function->getId())
->setParam('executions.{scope}.compute', 1)
->setParam('executionStatus', $execution->getAttribute('status', ''))

View file

@ -52,7 +52,7 @@
"utopia-php/database": "0.28.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/framework": "0.22.*",
"utopia-php/framework": "0.25.*",
"utopia-php/image": "0.5.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",

32
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4ac687daa09a38688f27be6959ea42a5",
"content-hash": "a2a79fc2e9ebdd4d05afa6632c642686",
"packages": [
{
"name": "adhocore/jwt",
@ -1945,16 +1945,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.22.1",
"version": "0.25.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df"
"reference": "c524f681254255c8204fbf7919c53bf3b4982636"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/9f35d36ed4b8fa1c92962c77ef02b49c2f5919df",
"reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/c524f681254255c8204fbf7919c53bf3b4982636",
"reference": "c524f681254255c8204fbf7919c53bf3b4982636",
"shasum": ""
},
"require": {
@ -1974,12 +1974,6 @@
"license": [
"MIT"
],
"authors": [
{
"name": "Eldad Fux",
"email": "eldad@appwrite.io"
}
],
"description": "A simple, light and advanced PHP framework",
"keywords": [
"framework",
@ -1988,9 +1982,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.22.1"
"source": "https://github.com/utopia-php/framework/tree/0.25.0"
},
"time": "2022-10-07T14:51:40+00:00"
"time": "2022-11-02T09:49:57+00:00"
},
{
"name": "utopia-php/image",
@ -2965,16 +2959,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.1",
"version": "v4.15.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900"
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"shasum": ""
},
"require": {
@ -3015,9 +3009,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
},
"time": "2022-09-04T07:30:47+00:00"
"time": "2022-11-12T15:38:23+00:00"
},
{
"name": "phar-io/manifest",

View file

@ -1,186 +0,0 @@
// Include gulp
const { src, dest, series } = require('gulp');
// Plugins
const gulpConcat = require('gulp-concat');
const gulpJsmin = require('gulp-jsmin');
const gulpLess = require('gulp-less');
const gulpCleanCSS = require('gulp-clean-css');
// Config
const configApp = {
mainFile: 'app.js',
src: [
'public/scripts/dependencies/litespeed.js',
'public/scripts/dependencies/alpine.js',
'public/scripts/init.js',
'public/scripts/services/alerts.js',
'public/scripts/services/api.js',
'public/scripts/services/console.js',
'public/scripts/services/date.js',
'public/scripts/services/env.js',
'public/scripts/services/form.js',
'public/scripts/services/markdown.js',
'public/scripts/services/rtl.js',
'public/scripts/services/sdk.js',
'public/scripts/services/search.js',
'public/scripts/services/timezone.js',
'public/scripts/services/realtime.js',
'public/scripts/routes.js',
'public/scripts/filters.js',
'public/scripts/app.js',
'public/scripts/upload-modal.js',
'public/scripts/events.js',
'public/scripts/permissions-matrix.js',
'public/scripts/views/service.js',
'public/scripts/views/analytics/event.js',
'public/scripts/views/analytics/activity.js',
'public/scripts/views/analytics/pageview.js',
'public/scripts/views/forms/clone.js',
'public/scripts/views/forms/add.js',
'public/scripts/views/forms/chart.js',
'public/scripts/views/forms/chart-bar.js',
'public/scripts/views/forms/code.js',
'public/scripts/views/forms/color.js',
'public/scripts/views/forms/copy.js',
'public/scripts/views/forms/custom-id.js',
'public/scripts/views/forms/document.js',
'public/scripts/views/forms/duplications.js',
'public/scripts/views/forms/document-preview.js',
'public/scripts/views/forms/filter.js',
'public/scripts/views/forms/headers.js',
'public/scripts/views/forms/key-value.js',
'public/scripts/views/forms/move-down.js',
'public/scripts/views/forms/move-up.js',
'public/scripts/views/forms/nav.js',
'public/scripts/views/forms/oauth-custom.js',
'public/scripts/views/forms/password-meter.js',
'public/scripts/views/forms/pell.js',
'public/scripts/views/forms/required.js',
'public/scripts/views/forms/remove.js',
'public/scripts/views/forms/run.js',
'public/scripts/views/forms/select-all.js',
'public/scripts/views/forms/selected.js',
'public/scripts/views/forms/show-secret.js',
'public/scripts/views/forms/switch.js',
'public/scripts/views/forms/tags.js',
'public/scripts/views/forms/text-count.js',
'public/scripts/views/forms/text-direction.js',
'public/scripts/views/forms/text-resize.js',
'public/scripts/views/forms/upload.js',
'public/scripts/views/general/cookies.js',
'public/scripts/views/general/copy.js',
'public/scripts/views/general/page-title.js',
'public/scripts/views/general/scroll-to.js',
'public/scripts/views/general/scroll-direction.js',
'public/scripts/views/general/setup.js',
'public/scripts/views/general/switch.js',
'public/scripts/views/general/theme.js',
'public/scripts/views/general/version.js',
'public/scripts/views/paging/back.js',
'public/scripts/views/paging/next.js',
'public/scripts/views/ui/highlight.js',
'public/scripts/views/ui/loader.js',
'public/scripts/views/ui/modal.js',
'public/scripts/views/ui/open.js',
'public/scripts/views/ui/phases.js',
'public/scripts/views/ui/trigger.js',
],
dest: './public/dist/scripts'
};
const configDep = {
mainFile: 'app-dep.js',
src: [
'public/scripts/dependencies/appwrite.js',
'node_modules/chart.js/dist/chart.js',
'node_modules/markdown-it/dist/markdown-it.js',
'node_modules/pell/dist/pell.js',
'node_modules/turndown/dist/turndown.js',
// PrismJS Core
'node_modules/prismjs/components/prism-core.min.js',
// PrismJS Languages
'node_modules/prismjs/components/prism-markup.min.js',
'node_modules/prismjs/components/prism-css.min.js',
'node_modules/prismjs/components/prism-clike.min.js',
'node_modules/prismjs/components/prism-javascript.min.js',
'node_modules/prismjs/components/prism-bash.min.js',
'node_modules/prismjs/components/prism-csharp.min.js',
'node_modules/prismjs/components/prism-dart.min.js',
'node_modules/prismjs/components/prism-go.min.js',
'node_modules/prismjs/components/prism-graphql.min.js',
'node_modules/prismjs/components/prism-http.min.js',
'node_modules/prismjs/components/prism-java.min.js',
'node_modules/prismjs/components/prism-json.min.js',
'node_modules/prismjs/components/prism-kotlin.min.js',
'node_modules/prismjs/components/prism-markup-templating.min.js',
'node_modules/prismjs/components/prism-php.min.js',
'node_modules/prismjs/components/prism-powershell.min.js',
'node_modules/prismjs/components/prism-python.min.js',
'node_modules/prismjs/components/prism-ruby.min.js',
'node_modules/prismjs/components/prism-swift.min.js',
'node_modules/prismjs/components/prism-typescript.min.js',
'node_modules/prismjs/components/prism-yaml.min.js',
// PrismJS Plugins
'node_modules/prismjs/plugins/line-numbers/prism-line-numbers.min.js',
],
dest: './public/dist/scripts'
};
const config = {
mainFile: 'app-all.js',
src: [
'public/dist/scripts/app-dep.js',
'public/dist/scripts/app.js'
],
dest: './public/dist/scripts'
};
function lessLTR() {
return src('./public/styles/default-ltr.less')
.pipe(gulpLess())
.pipe(gulpCleanCSS({ compatibility: 'ie8' }))
.pipe(dest('./public/dist/styles'));
}
function lessRTL() {
return src('./public/styles/default-rtl.less')
.pipe(gulpLess())
.pipe(gulpCleanCSS({ compatibility: 'ie8' }))
.pipe(dest('./public/dist/styles'));
}
function concatApp() {
return src(configApp.src)
.pipe(gulpConcat(configApp.mainFile))
.pipe(gulpJsmin())
.pipe(dest(configApp.dest));
}
function concatDep() {
return src(configDep.src)
.pipe(gulpConcat(configDep.mainFile))
.pipe(gulpJsmin())
.pipe(dest(configDep.dest));
}
function concat() {
return src(config.src)
.pipe(gulpConcat(config.mainFile))
.pipe(dest(config.dest));
}
exports.import = series(concatDep);
exports.less = series(lessLTR, lessRTL);
exports.build = series(concatApp, concat);

9673
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,26 +0,0 @@
{
"name": "appwrite-server",
"version": "0.1.0",
"license": "BSD-3-Clause",
"repository": "public",
"scripts": {
"build": "npm run gulp:less && npm run gulp:import && npm run gulp:build",
"gulp:less": "gulp less",
"gulp:import": "gulp import",
"gulp:build": "gulp build"
},
"devDependencies": {
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",
"gulp-jsmin": "^0.1.5",
"gulp-less": "^5.0.0"
},
"dependencies": {
"chart.js": "^3.8.2",
"markdown-it": "^13.0.1",
"pell": "^1.0.6",
"prismjs": "^1.28.0",
"turndown": "^7.1.1"
}
}

View file

@ -7,6 +7,8 @@
<ini name="memory_limit" value="4096M"/>
<!-- Exclude SDK's for performance reasons -->
<exclude-pattern>./app/sdks</exclude-pattern>
<!-- Exclude console -->
<exclude-pattern>./app/console</exclude-pattern>
<!-- Ignore max line width -->
<rule ref="Generic.Files.LineLength">
<exclude-pattern>*</exclude-pattern>

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 507 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,324 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<defs >
<font id="Poppins" horiz-adv-x="841" ><font-face
font-family="Poppins Thin"
units-per-em="1000"
panose-1="0 0 3 0 0 0 0 0 0 0"
ascent="1050"
descent="-350"
alphabetic="0" />
<glyph unicode=" " glyph-name="space" horiz-adv-x="290" />
<glyph unicode="!" glyph-name="exclam" horiz-adv-x="224" d="M122 704L116 167H104L98 704H122ZM137 49V0H87V49H137Z" />
<glyph unicode="&quot;" glyph-name="quotedbl" horiz-adv-x="196" d="M71 793L64 614H48L40 793H71ZM156 793L149 614H133L125 793H156Z" />
<glyph unicode="#" glyph-name="numbersign" horiz-adv-x="743" d="M540 465L498 266H653V248H494L441 0H421L474 248H223L170 0H150L203 248H40V266H207L249 465H90V483H253L308 740H328L273 483H524L579 740H599L544 483H703V465H540ZM520 465H269L227 266H478L520
465Z" />
<glyph unicode="$" glyph-name="dollar" horiz-adv-x="536" d="M342 348T382 329T449 271T476 169Q476 125 457 85T394 19T285 -7H278V-87H258V-6Q203 -1 160 24T93 88T63 169H83Q87 138 107 104T165 44T258 12V353Q193 372 154 390T87 445T60 543Q60 616 109
663T250 711H258V800H278V710Q356 702 401 658T459 559H439Q428 603 388 643T278 692V367Q342 348 382 329ZM80 490T103 458T163 408T258 373V693H250Q173 693 127 654T80 543Q80 490 103 458ZM368 11T412 56T456 169Q456 224 432 258T372 311T278 347V11H285Q368
11 412 56Z" />
<glyph unicode="%" glyph-name="percent" horiz-adv-x="529" d="M222 711T258 675T294 582Q294 526 258 490T167 453Q112 453 76 489T40 582Q40 638 76 674T167 711Q222 711 258 675ZM487 704L64 0H41L464 704H487ZM60 534T90 503T167 471Q214 471 244 502T274
582Q274 630 244 661T167 693Q120 693 90 662T60 582Q60 534 90 503ZM417 250T453 214T489 121Q489 65 453 29T362 -8Q307 -8 271 28T235 121Q235 177 271 213T362 250Q417 250 453 214ZM255 73T285 42T362 10Q409 10 439 41T469 121Q469 169 439 200T362 232Q315
232 285 201T255 121Q255 73 285 42Z" />
<glyph unicode="&amp;" glyph-name="ampersand" horiz-adv-x="672" d="M638 0L400 255Q413 236 418 220T423 180Q423 126 399 84T333 17T237 -7Q184 -7 139 17T67 86T40 194Q40 256 63 299T126 364T212 386Q241 386 265 379T305 359L261 404Q221 445 201 480T181
566Q181 604 198 637T251 690T337 711Q420 711 461 663T502 548Q502 530 501 521H481Q482 530 482 546Q482 614 443 653T337 693Q273 693 237 656T201 566Q201 523 217 493T265 428L662 0H638ZM285 13T322 34T381 94T403 180Q403 232 376 274T305 341T217 366Q148
366 104 322T60 194Q60 138 84 97T149 35T237 13Q285 13 322 34Z" />
<glyph unicode="&apos;" glyph-name="quotesingle" horiz-adv-x="119" d="M79 793L68 614H52L40 793H79Z" />
<glyph unicode="(" glyph-name="parenleft" horiz-adv-x="357" d="M60 598T130 747T317 987H337V984Q207 881 144 733T80 403Q80 222 143 74T337 -178V-181H317Q201 -91 131 58T60 403Q60 598 130 747Z" />
<glyph unicode=")" glyph-name="parenright" horiz-adv-x="357" d="M297 208T227 59T40 -181H20V-178Q150 -75 213 73T277 403Q277 584 214 732T20 984V987H40Q156 897 226 748T297 403Q297 208 227 59Z" />
<glyph unicode="*" glyph-name="asterisk" horiz-adv-x="429" d="M364 653L389 608L228 544L389 480L366 437L229 531L245 360H194L206 531L65 434L40 479L199 543L40 609L67 656L205 559L194 728H244L229 559L364 653Z" />
<glyph unicode="+" glyph-name="plus" horiz-adv-x="589" d="M549 371H304V125H284V371H40V389H284V635H304V389H549V371Z" />
<glyph unicode="," glyph-name="comma" horiz-adv-x="120" d="M90 7T90 -2Q90 -86 25 -86V-68Q68 -68 68 -14V-7H37V31H88Q90 7 90 -2Z" />
<glyph unicode="-" glyph-name="hyphen" horiz-adv-x="444" d="M585 389V371H221V389H585Z" />
<glyph unicode="." glyph-name="period" horiz-adv-x="113" d="M81 49V0H31V49H81Z" />
<glyph unicode="/" glyph-name="slash" horiz-adv-x="359" d="M329 935L50 -156H30L307 935H329Z" />
<glyph unicode="0" glyph-name="zero" horiz-adv-x="588" d="M70 240T88 162T155 39T294 -7Q383 -7 432 38T500 162T518 354Q518 466 500 543T433 666T294 711Q204 711 155 666T88 544T70 354Q70 240 88 162ZM498 245T482 171T422 54T294 11Q211 11 167 54T106
171T90 354Q90 462 106 535T166 650T294 693Q377 693 421 651T482 535T498 354Q498 245 482 171Z" />
<glyph unicode="1" glyph-name="one" horiz-adv-x="226" d="M15 686V704H141V0H121V686H15Z" />
<glyph unicode="2" glyph-name="two" horiz-adv-x="567" d="M245 137T359 267T473 515Q473 593 431 643T293 693Q196 693 145 635T86 480H66Q74 588 133 649T293 711Q378 711 435 664T493 515Q493 426 430 331T274 155T88 18H517V0H50V11Q245 137 359 267Z" />
<glyph unicode="3" glyph-name="three" horiz-adv-x="575" d="M391 711T443 662T495 537Q495 473 454 426T320 370V364Q408 344 458 301T508 179Q508 98 455 46T296 -7Q197 -7 134 46T53 197H73Q88 115 145 64T296 13Q390 13 439 59T488 179Q488 266 419 311T226
357H209V376H227Q475 376 475 537Q475 607 428 650T297 693Q215 693 156 650T86 516H66Q76 607 136 659T297 711Q391 711 443 662Z" />
<glyph unicode="4" glyph-name="four" horiz-adv-x="574" d="M20 190V206L417 704H437V208H559V190H437V0H417V190H20ZM417 672L45 208H417V672Z" />
<glyph unicode="5" glyph-name="five" horiz-adv-x="592" d="M503 686H101V366Q130 409 186 435T308 461Q424 461 480 397T537 236Q537 170 513 115T437 27T304 -7Q210 -7 147 46T70 192H90Q107 105 164 59T303 13Q409 13 463 74T517 236Q517 332 463 387T308
443Q239 443 184 414T99 333H81V704H503V686Z" />
<glyph unicode="6" glyph-name="six" horiz-adv-x="614" d="M494 604T448 649T320 694Q208 694 149 613T89 350Q89 315 90 297Q103 375 167 421T322 468Q433 468 493 403T554 224Q554 162 531 110T457 26T327 -7Q178 -7 124 86T70 339Q70 518 128 615T320 712Q410
712 462 661T525 522H505Q494 604 448 649ZM96 185T120 132T196 45T327 11Q421 11 477 68T534 224Q534 334 478 392T322 450Q266 450 215 427T130 358T96 245Q96 185 120 132Z" />
<glyph unicode="7" glyph-name="seven" horiz-adv-x="502" d="M462 686L182 0H159L443 686H10V704H462V686Z" />
<glyph unicode="8" glyph-name="eight" horiz-adv-x="608" d="M75 613T136 662T304 711Q411 711 472 662T533 532Q533 469 496 424T380 362Q461 348 507 300T553 177Q553 83 488 29T304 -25Q186 -25 121 29T55 177Q55 251 101 299T228 362Q149 378 112 423T75
532Q75 613 136 662ZM95 456T151 414T304 371Q401 371 457 413T513 532Q513 610 457 651T304 693Q208 693 152 652T95 532Q95 456 151 414ZM75 89T137 41T304 -7Q409 -7 471 41T533 177Q533 267 469 310T304 353Q204 353 140 310T75 177Q75 89 137 41Z" />
<glyph unicode="9" glyph-name="nine" horiz-adv-x="614" d="M120 100T166 55T294 10Q406 10 465 91T525 354Q525 389 524 407Q511 329 447 283T292 236Q181 236 121 301T60 480Q60 542 83 594T157 678T287 711Q436 711 490 618T544 365Q544 186 486 89T294 -8Q204
-8 152 43T89 182H109Q120 100 166 55ZM518 519T494 572T418 659T287 693Q193 693 137 636T80 480Q80 370 136 312T292 254Q348 254 399 277T484 346T518 459Q518 519 494 572Z" />
<glyph unicode=":" glyph-name="colon" horiz-adv-x="113" d="M81 503V454H31V503H81ZM81 49V0H31V49H81Z" />
<glyph unicode=";" glyph-name="semicolon" horiz-adv-x="140" d="M110 504V455H60V504H110ZM110 7T110 -2Q110 -86 45 -86V-68Q88 -68 88 -14V-7H57V31H108Q110 7 110 -2Z" />
<glyph unicode="&lt;" glyph-name="less" horiz-adv-x="381" d="M316 143L40 380L316 618H341L65 380L341 143H316Z" />
<glyph unicode="=" glyph-name="equal" horiz-adv-x="564" d="M524 478V460H40V478H524ZM524 301V283H40V301H524Z" />
<glyph unicode="&gt;" glyph-name="greater" horiz-adv-x="381" d="M40 618H65L341 380L65 143H40L316 380L40 618Z" />
<glyph unicode="?" glyph-name="question" horiz-adv-x="482" d="M333 711T387 658T442 517Q442 401 374 350T181 297V152H161V315H177Q286 315 354 357T422 517Q422 599 374 646T242 693Q156 693 103 646T50 522H30Q30 581 58 623T135 688T242 711Q333 711 387
658ZM197 49V0H147V49H197Z" />
<glyph unicode="@" glyph-name="at" horiz-adv-x="1024" d="M600 553T651 522T727 437T752 320Q752 294 747 262L718 93Q715 78 715 66Q715 33 733 20T783 6Q830 6 869 45T931 155T954 321Q954 446 907 540T768 687T544 740Q399 740 292 674T129 492T72 233Q72
6 192 -104T520 -220L517 -239Q300 -235 175 -117T50 233Q50 372 106 492T274 685T544 759Q686 759 782 703T926 547T974 321Q974 226 949 150T880 31T783 -12Q699 -12 699 68Q699 98 709 137L707 138Q670 67 612 28T474 -12Q410 -12 361 19T284 106T256 235Q256
325 292 398T390 512T526 553Q600 553 651 522ZM452 533T395 494T307 387T276 235Q276 172 299 120T366 37T474 6Q546 6 604 45T697 155T731 315Q731 411 681 472T526 533Q452 533 395 494Z" />
<glyph unicode="A" glyph-name="A" horiz-adv-x="601" d="M475 194H127L53 0H30L291 683H311L571 0H548L475 194ZM468 212L301 651L134 212H468Z" />
<glyph unicode="B" glyph-name="B" horiz-adv-x="560" d="M424 354T467 305T510 188Q510 105 453 53T287 0H85V704H287Q392 704 443 657T494 532Q494 461 456 420T357 366Q424 354 467 305ZM105 376H293Q381 376 427 417T474 532Q474 602 428 643T287 684H105V376ZM383
20T436 65T490 188Q490 265 435 310T285 356H105V20H287Q383 20 436 65Z" />
<glyph unicode="C" glyph-name="C" horiz-adv-x="769" d="M511 711T591 654T709 496H689Q657 585 580 639T388 693Q302 693 232 652T122 533T81 351Q81 248 121 171T232 53T388 11Q503 11 580 64T689 207H709Q672 106 592 50T388 -7Q294 -7 219 38T102 165T60
351Q60 456 102 537T219 665T388 711Q511 711 591 654Z" />
<glyph unicode="D" glyph-name="D" horiz-adv-x="702" d="M642 184T547 92T270 0H85V704H270Q452 704 547 612T642 352Q642 184 547 92ZM444 18T533 106T622 352Q622 510 533 598T270 686H105V18H270Q444 18 533 106Z" />
<glyph unicode="E" glyph-name="E" horiz-adv-x="479" d="M105 686V361H399V343H105V18H429V0H85V704H429V686H105Z" />
<glyph unicode="F" glyph-name="F" horiz-adv-x="469" d="M429 704V686H105V361H399V343H105V0H85V704H429Z" />
<glyph unicode="G" glyph-name="G" horiz-adv-x="792" d="M510 710T591 652T709 495H689Q657 583 580 637T396 692Q308 692 236 651T123 532T81 351Q81 248 122 171T236 52T396 10Q482 10 553 50T668 165T713 341H381V359H732V351Q732 247 689 165T570 38T396
-8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q510 710 591 652Z" />
<glyph unicode="H" glyph-name="H" horiz-adv-x="649" d="M564 704V0H544V353H105V0H85V704H105V371H544V704H564Z" />
<glyph unicode="I" glyph-name="I" horiz-adv-x="190" d="M105 704V0H85V704H105Z" />
<glyph unicode="J" glyph-name="J" horiz-adv-x="426" d="M346 704V143Q346 78 306 36T193 -7Q115 -7 73 41T30 173H50Q50 103 84 57T193 11Q264 11 295 50T326 143V704H346Z" />
<glyph unicode="K" glyph-name="K" horiz-adv-x="494" d="M117 352L474 0H447L105 336V0H85V704H105V366L447 704H474L117 352Z" />
<glyph unicode="L" glyph-name="L" horiz-adv-x="395" d="M105 18H375V0H85V704H105V18Z" />
<glyph unicode="M" glyph-name="M" horiz-adv-x="797" d="M712 684V0H692V635L409 0H389L105 633V0H85V684H105L399 27L692 684H712Z" />
<glyph unicode="N" glyph-name="N" horiz-adv-x="647" d="M562 0H542L105 667V0H85V703H105L542 37V703H562V0Z" />
<glyph unicode="O" glyph-name="O" horiz-adv-x="792" d="M493 710T569 665T689 537T732 351Q732 247 689 165T570 38T396 -8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q493 710 569 665ZM308 692T236 651T123 532T81 351Q81 248 122 171T236
52T396 10Q484 10 556 51T669 170T711 351Q711 454 670 531T556 650T396 692Q308 692 236 651Z" />
<glyph unicode="P" glyph-name="P" horiz-adv-x="528" d="M105 317V0H85V704H263Q379 704 433 653T488 510Q488 413 431 365T263 317H105ZM371 338T419 383T468 510Q468 596 420 640T263 684H105V338H263Q371 338 419 383Z" />
<glyph unicode="Q" glyph-name="Q" horiz-adv-x="792" d="M708 -149L506 9Q453 -8 396 -8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q493 710 569 665T689 537T732 351Q732 233 678 145T530 18L743 -149H708ZM81 248T122 171T236 52T396 10Q484
10 556 51T669 170T711 351Q711 454 670 531T556 650T396 692Q308 692 236 651T123 532T81 351Q81 248 122 171Z" />
<glyph unicode="R" glyph-name="R" horiz-adv-x="535" d="M470 0L255 307H105V0H85V704H263Q378 704 433 651T488 505Q488 411 435 361T278 307L495 0H470ZM105 328H263Q468 328 468 505Q468 591 420 637T263 684H105V328Z" />
<glyph unicode="S" glyph-name="S" horiz-adv-x="536" d="M476 125T457 85T394 19T285 -7Q222 -7 174 18T97 83T63 169H83Q88 135 110 99T177 37T285 11Q368 11 412 56T456 169Q456 227 430 262T365 315T261 352Q195 371 155 389T88 444T60 543Q60 616 109 663T250
711Q340 711 393 665T459 559H439Q432 589 410 619T348 671T250 693Q173 693 127 654T80 543Q80 488 105 455T168 405T270 369Q337 350 378 331T447 273T476 169Q476 125 457 85Z" />
<glyph unicode="T" glyph-name="T" horiz-adv-x="470" d="M450 704V686H245V0H225V686H20V704H450Z" />
<glyph unicode="U" glyph-name="U" horiz-adv-x="629" d="M100 704V267Q100 133 158 72T317 11Q415 11 472 72T529 267V704H549V267Q549 127 488 60T317 -7Q208 -7 144 60T80 267V704H100Z" />
<glyph unicode="V" glyph-name="V" horiz-adv-x="619" d="M54 704L308 35L565 704H589L319 0H299L30 704H54Z" />
<glyph unicode="W" glyph-name="W" horiz-adv-x="904" d="M869 704L680 0H660L452 668L244 0H224L35 704H59L235 43L440 704H464L669 43L845 704H869Z" />
<glyph unicode="X" glyph-name="X" horiz-adv-x="500" d="M261 352L470 0H447L247 338L53 0H30L236 357L30 704H53L250 371L447 704H470L261 352Z" />
<glyph unicode="Y" glyph-name="Y" horiz-adv-x="518" d="M488 704L270 295V0H250V295L30 704H54L259 317L464 704H488Z" />
<glyph unicode="Z" glyph-name="Z" horiz-adv-x="487" d="M57 19H452V0H35V19L421 684H48V704H442V684L57 19Z" />
<glyph unicode="[" glyph-name="bracketleft" horiz-adv-x="219" d="M179 935V917H105V-137H179V-155H85V935H179Z" />
<glyph unicode="\" glyph-name="backslash" horiz-adv-x="474" d="M384 -156L30 935H53L405 -156H384Z" />
<glyph unicode="]" glyph-name="bracketright" horiz-adv-x="219" d="M40 -155V-137H114V917H40V935H134V-155H40Z" />
<glyph unicode="^" glyph-name="asciicircum" horiz-adv-x="552" d="M60 174H40L270 683H282L512 174H492L277 653L60 174Z" />
<glyph unicode="_" glyph-name="underscore" horiz-adv-x="533" d="M493 -30V-48H40V-30H493Z" />
<glyph unicode="`" glyph-name="grave" horiz-adv-x="267" d="M227 606V586L40 716V740L227 606Z" />
<glyph unicode="a" glyph-name="a" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190 110 132T194
42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498Z" />
<glyph unicode="b" glyph-name="b" horiz-adv-x="671" d="M425 547T485 514T578 418T611 270Q611 185 578 123T485 27T348 -7Q255 -7 191 43T104 174V0H85V740H104V366Q126 447 190 497T348 547Q425 547 485 514ZM278 529T223 497T136 406T104 270Q104 193 135
135T222 44T348 11Q422 11 476 42T561 131T591 270Q591 350 561 408T477 498T348 529Q278 529 223 497Z" />
<glyph unicode="c" glyph-name="c" horiz-adv-x="628" d="M423 547T488 495T568 357H548Q535 437 473 483T320 529Q255 529 201 501T113 414T80 270Q80 184 113 126T200 40T320 11Q411 11 473 57T548 183H568Q554 98 489 46T320 -7Q245 -7 186 26T94 122T60 270Q60
355 93 418T186 514T320 547Q423 547 488 495Z" />
<glyph unicode="d" glyph-name="d" horiz-adv-x="671" d="M416 547T480 497T567 366V740H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190 110 132T194
42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498Z" />
<glyph unicode="e" glyph-name="e" horiz-adv-x="628" d="M411 11T473 57T548 183H568Q554 98 489 46T320 -7Q245 -7 186 26T94 122T60 270Q60 355 93 418T186 514T320 547Q399 547 455 514T539 427T568 315Q568 285 564 261H80Q81 178 114 122T202 39T320 11Q411
11 473 57ZM256 529T202 502T115 418T80 279H550Q555 362 524 418T438 501T320 529Q256 529 202 502Z" />
<glyph unicode="f" glyph-name="f" horiz-adv-x="318" d="M196 720T162 687T128 578V540H283V519H128V0H108V519H20V540H108V578Q108 660 148 700T283 740V720Q196 720 162 687Z" />
<glyph unicode="g" glyph-name="g" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V-20Q586 -95 553 -150T466 -235T347 -264Q249 -264 185 -217T96 -88H116Q139 -161 197 -203T347 -246Q409 -246 459 -218T538 -139T567 -20V174Q545 93 481 43T323 -7Q246
-7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190 110 132T194 42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498Z" />
<glyph unicode="h" glyph-name="h" horiz-adv-x="614" d="M412 552T473 493T534 315V0H515V317Q515 423 462 478T317 534Q221 534 163 472T105 287V0H85V740H105V388Q124 470 182 511T317 552Q412 552 473 493Z" />
<glyph unicode="i" glyph-name="i" horiz-adv-x="190" d="M69 717T76 724T95 732Q106 732 113 725T121 705Q121 693 114 686T95 678Q84 678 77 685T69 705Q69 717 76 724ZM105 540V0H85V540H105Z" />
<glyph unicode="j" glyph-name="j" horiz-adv-x="200" d="M94 678T87 685T79 705Q79 717 86 724T105 732Q116 732 123 725T131 705Q131 693 124 686T105 678Q94 678 87 685ZM-25 -234H11Q55 -234 75 -215T95 -146V540H115V-152Q115 -201 91 -227T11 -254H-25V-234Z" />
<glyph unicode="k" glyph-name="k" horiz-adv-x="395" d="M354 0L105 253V0H85V740H105V293L348 540H378L114 274L385 0H354Z" />
<glyph unicode="l" glyph-name="l" horiz-adv-x="190" d="M105 740V0H85V740H105Z" />
<glyph unicode="m" glyph-name="m" horiz-adv-x="1003" d="M808 552T865 493T923 315V0H904V317Q904 423 854 478T718 534Q625 534 570 472T514 287V0H495V317Q495 423 445 478T309 534Q216 534 161 472T105 287V0H85V540H105V389Q124 470 180 511T309 552Q386
552 440 508T509 376Q525 463 583 507T718 552Q808 552 865 493Z" />
<glyph unicode="n" glyph-name="n" horiz-adv-x="614" d="M412 552T473 493T534 315V0H515V317Q515 423 462 478T317 534Q221 534 163 472T105 287V0H85V540H105V388Q124 470 182 511T317 552Q412 552 473 493Z" />
<glyph unicode="o" glyph-name="o" horiz-adv-x="647" d="M399 547T459 514T553 418T587 270Q587 185 553 123T459 27T324 -7Q249 -7 189 26T95 122T60 270Q60 355 94 417T189 513T324 547Q399 547 459 514ZM259 529T204 501T114 414T80 270Q80 185 114 127T203
40T324 11Q389 11 444 39T533 126T567 270Q567 356 533 414T444 500T324 529Q259 529 204 501Z" />
<glyph unicode="p" glyph-name="p" horiz-adv-x="671" d="M425 547T485 514T578 418T611 270Q611 185 578 123T485 27T348 -7Q255 -7 191 43T104 174V-254H85V540H104V366Q126 447 190 497T348 547Q425 547 485 514ZM278 529T223 497T136 406T104 270Q104 193
135 135T222 44T348 11Q422 11 476 42T561 131T591 270Q591 350 561 408T477 498T348 529Q278 529 223 497Z" />
<glyph unicode="q" glyph-name="q" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V-254H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190 110 132T194
42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498Z" />
<glyph unicode="r" glyph-name="r" horiz-adv-x="338" d="M137 552T318 552V530H309Q224 530 165 481T105 321V0H85V540H105V409Q137 552 318 552Z" />
<glyph unicode="s" glyph-name="s" horiz-adv-x="483" d="M310 547T360 503T419 382H399Q392 444 349 486T230 529Q163 529 125 496T86 407Q86 366 107 343T160 308T245 283Q304 269 339 256T398 213T423 133Q423 71 380 32T266 -7Q180 -7 125 37T60 158H80Q86
95 134 53T267 11Q331 11 367 45T403 133Q403 176 381 201T326 239T239 264Q182 277 148 290T90 331T66 407Q66 471 111 509T230 547Q310 547 360 503Z" />
<glyph unicode="t" glyph-name="t" horiz-adv-x="338" d="M128 520V142Q128 93 139 67T174 31T241 20H298V0H239Q169 0 139 32T108 142V520H20V540H108V678H128V540H298V520H128Z" />
<glyph unicode="u" glyph-name="u" horiz-adv-x="599" d="M514 540V0H494V152Q475 70 417 29T282 -12Q187 -12 126 47T65 225V540H84V223Q84 117 137 62T282 6Q378 6 436 68T494 253V540H514Z" />
<glyph unicode="v" glyph-name="v" horiz-adv-x="515" d="M258 35L473 540H495L265 0H250L20 540H42L258 35Z" />
<glyph unicode="w" glyph-name="w" horiz-adv-x="803" d="M783 540L613 0H598L401 503L205 0H190L20 540H42L199 41L393 540H410L604 41L761 540H783Z" />
<glyph unicode="x" glyph-name="x" horiz-adv-x="416" d="M372 0L207 257L42 0H20L195 275L24 540H48L208 292L369 540H391L221 272L396 0H372Z" />
<glyph unicode="y" glyph-name="y" horiz-adv-x="503" d="M42 540L260 29L461 540H483L175 -254H150L249 -1L20 540H42Z" />
<glyph unicode="z" glyph-name="z" horiz-adv-x="407" d="M51 18H377V0H30V18L345 522H40V540H367V522L51 18Z" />
<glyph unicode="{" glyph-name="braceleft" horiz-adv-x="293" d="M113 358T143 313T173 208Q173 187 170 166T161 124Q154 89 150 63T146 1Q146 -72 182 -128T263 -213V-217H243Q221 -204 195 -176T147 -100T126 7Q126 37 129 61T141 124Q146 145 149 167T153
208Q153 258 132 298T40 360V384Q111 406 132 446T153 536Q153 555 150 577T141 620Q133 658 130 682T126 737Q126 797 147 844T194 919T243 961H263V957Q218 928 182 872T146 743Q146 707 150 681T161 620Q166 600 169 579T173 536Q173 477 143 432T52 373V371Q113
358 143 313Z" />
<glyph unicode="|" glyph-name="bar" horiz-adv-x="190" d="M105 -86H85V778H105V-86Z" />
<glyph unicode="}" glyph-name="braceright" horiz-adv-x="303" d="M190 386T160 431T130 536Q130 557 133 578T142 620Q149 655 153 681T157 743Q157 816 121 872T40 957V961H60Q82 948 108 920T156 844T177 737Q177 707 174 683T162 620Q157 599 154 577T150
536Q150 486 171 446T263 384V360Q192 338 171 298T150 208Q150 190 153 168T162 124Q170 86 173 62T177 7Q177 -53 156 -100T109 -175T60 -217H40V-213Q85 -184 121 -128T157 1Q157 37 153 63T142 124Q137 144 134 165T130 208Q130 267 160 312T251 371V373Q190
386 160 431Z" />
<glyph unicode="~" glyph-name="asciitilde" horiz-adv-x="412" d="M151 367T169 359T213 332Q237 316 252 308T286 300Q311 300 328 319T352 367H372Q364 326 342 303T283 279Q260 279 243 288T200 314Q176 330 161 338T126 346Q101 346 84 327T60 279H40Q48
320 70 343T129 367Q151 367 169 359Z" />
<glyph unicode="&#xa0;" glyph-name="uni00A0" horiz-adv-x="290" />
<glyph unicode="&#xa1;" glyph-name="exclamdown" horiz-adv-x="224" d="M122 -152L116 385H104L98 -152H122ZM137 503V552H87V503H137Z" />
<glyph unicode="&#xa2;" glyph-name="cent" horiz-adv-x="588" d="M382 41T438 87T508 213H528Q514 128 455 76T302 23V-93H282V24Q217 28 167 58T89 143T60 270Q60 342 88 396T167 481T282 516V623H302V517Q396 516 455 464T528 327H508Q495 407 439 453T300
499Q241 499 191 474T111 398T80 270Q80 194 110 143T191 66T300 41Q382 41 438 87Z" />
<glyph unicode="&#xa3;" glyph-name="sterling" horiz-adv-x="540" d="M212 153T192 110T128 20H504V0H111L107 19Q139 59 155 82T182 135T194 202Q194 233 187 262T166 328H50V345H160Q138 402 127 440T116 524Q116 577 139 619T205 686T304 711Q392 711 446
657T510 521H490Q482 596 432 644T304 693Q234 693 185 650T136 524Q136 485 145 450T172 366Q178 352 180 345H373V328H187Q212 258 212 207Q212 153 192 110Z" />
<glyph unicode="&#xa4;" glyph-name="currency" horiz-adv-x="501" d="M401 310T365 263L427 203L413 190L353 250Q333 228 306 217T250 205Q187 205 145 249L86 190L72 203L134 263Q100 308 100 377Q100 444 135 492L72 553L86 566L147 505Q189 549 250 549Q308
549 352 505L413 566L427 553L364 491Q401 444 401 377Q401 310 365 263ZM285 223T315 241T363 295T381 377Q381 423 363 458T315 512T250 531Q193 531 157 489T120 377Q120 306 156 265T250 223Q285 223 315 241Z" />
<glyph unicode="&#xa5;" glyph-name="yen" horiz-adv-x="563" d="M322 346H495V328H313L295 295V229H495V209H295V0H275V209H75V229H275V295L257 328H75V346H248L55 704H79L284 317L489 704H513L322 346Z" />
<glyph unicode="&#xa6;" glyph-name="brokenbar" horiz-adv-x="190" d="M85 778H105V428H85V778ZM105 -86H85V265H105V-86Z" />
<glyph unicode="&#xa7;" glyph-name="section" horiz-adv-x="566" d="M347 747T403 704T471 581H451Q448 616 427 650T364 706T259 729Q188 729 146 702T103 620Q103 570 142 539T274 486Q348 469 397 449T476 393T506 300Q506 258 483 221T413 161T305 138Q285
138 274 139V135Q366 118 424 85T483 -17Q483 -74 438 -110T312 -147Q222 -147 164 -104T95 19H115Q124 -43 171 -86T312 -129Q381 -129 422 -101T463 -17Q463 32 424 62T292 114Q218 131 169 151T90 207T60 300Q60 342 83 379T153 439T261 462Q281 462 292 461V465Q202
483 143 517T83 620Q83 676 130 711T259 747Q347 747 403 704ZM217 447T172 427T103 374T80 300Q80 234 134 194T286 153Q349 153 394 173T463 226T486 300Q486 366 432 406T280 447Q217 447 172 427Z" />
<glyph unicode="&#xa8;" glyph-name="dieresis" horiz-adv-x="319" d="M63 669T71 678T93 687Q105 687 114 678T123 657Q123 645 114 636T93 627Q80 627 72 636T63 657Q63 669 71 678ZM197 669T205 678T227 687Q239 687 247 678T256 657Q256 645 248 636T227 627Q214
627 206 636T197 657Q197 669 205 678Z" />
<glyph unicode="&#xa9;" glyph-name="copyright" horiz-adv-x="803" d="M302 702T224 658T103 534T60 352Q60 250 103 171T224 47T402 2Q502 2 579 46T700 170T743 352Q743 454 700 533T580 657T402 702Q302 702 224 658ZM498 684T571 641T683 523T723 352Q723
257 684 182T571 63T402 20Q305 20 232 63T120 181T80 352Q80 447 119 522T232 641T402 684Q498 684 571 641ZM476 590T532 550T608 435H587Q567 500 517 535T405 570Q353 570 309 545T238 471T211 351Q211 282 238 233T309 159T405 134Q467 134 517 169T587 269H608Q588
195 532 155T405 114Q347 114 298 141T220 223T191 353Q191 428 220 481T298 562T405 590Q476 590 532 550Z" />
<glyph unicode="&#xaa;" glyph-name="ordfeminine" horiz-adv-x="433" d="M260 709T298 680T351 600V703H371V354H351V456Q337 405 299 376T202 347Q129 347 85 397T40 528Q40 609 84 659T202 709Q260 709 298 680ZM140 691T100 647T60 528Q60 454 100 410T202
365Q268 365 309 409T351 528Q351 602 310 646T202 691Q140 691 100 647Z" />
<glyph unicode="&#xab;" glyph-name="guillemotleft" horiz-adv-x="374" d="M152 120L45 295L152 470H179L70 295L179 120H152ZM297 120L190 295L297 470H324L215 295L324 120H297Z" />
<glyph unicode="&#xac;" glyph-name="logicalnot" horiz-adv-x="708" d="M663 424V255H645V404H59V424H663Z" />
<glyph unicode="&#xad;" glyph-name="uni00AD" horiz-adv-x="444" d="M585 389V371H221V389H585Z" />
<glyph unicode="&#xae;" glyph-name="registered" horiz-adv-x="787" d="M492 703T567 660T685 538T727 353Q727 251 685 171T568 46T394 1Q296 1 220 46T102 171T60 353Q60 458 102 537T220 660T394 703Q492 703 567 660ZM489 28T559 68T668 183T707 353Q707
452 669 527T560 644T394 685Q299 685 228 644T119 528T80 353Q80 256 118 183T228 69T394 28Q489 28 559 68ZM562 414T524 383T414 350L560 140H536L392 350H289V140H269V598H410Q486 598 524 566T562 475Q562 414 524 383ZM542 369T542 475Q542 527 511 553T410
580H289V369H410Q542 369 542 475Z" />
<glyph unicode="&#xaf;" glyph-name="overscore" horiz-adv-x="389" d="M349 677V659H40V677H349Z" />
<glyph unicode="&#xb0;" glyph-name="degree" horiz-adv-x="356" d="M245 691T285 644T326 519Q326 442 286 395T178 347Q111 347 71 394T30 519Q30 596 70 643T178 691Q245 691 285 644ZM119 673T85 632T50 519Q50 447 84 406T178 365Q237 365 271 406T306 519Q306
591 272 632T178 673Q119 673 85 632Z" />
<glyph unicode="&#xb1;" glyph-name="plusminus" horiz-adv-x="589" d="M549 409V391H304V165H284V391H40V409H284V635H304V409H549ZM40 109H549V91H40V109Z" />
<glyph unicode="&#xb2;" glyph-name="twosuperior" horiz-adv-x="301" d="M261 370V352H40V362L154 475Q191 512 210 541T229 610Q229 650 207 669T151 689Q111 689 88 666T60 598H40Q43 645 71 676T151 707Q195 707 222 681T249 610Q249 567 227 533T160 454L72
370H261Z" />
<glyph unicode="&#xb3;" glyph-name="threesuperior" horiz-adv-x="302" d="M201 707T231 681T261 611Q261 580 244 558T194 527V525Q225 516 243 494T262 439Q262 402 234 375T152 347Q52 347 40 444H60Q65 401 89 383T152 365Q195 365 218 385T242 439Q242 475
216 495T149 516H88V534H152Q192 534 216 554T241 611Q241 645 218 666T155 687Q115 687 92 669T61 619H41Q48 658 77 682T155 707Q201 707 231 681Z" />
<glyph unicode="&#xb4;" glyph-name="acute" horiz-adv-x="267" d="M227 716L40 586V606L227 740V716Z" />
<glyph unicode="&#xb5;" glyph-name="uni00B5" horiz-adv-x="619" d="M534 540V0H514V150Q495 69 440 29T314 -12Q241 -12 184 25T104 148V-254H85V540H104V263Q104 136 161 71T314 6Q405 6 459 68T514 253V540H534Z" />
<glyph unicode="&#xb6;" glyph-name="paragraph" horiz-adv-x="489" d="M404 0H384V666H276V0H256V297H255Q140 297 80 349T20 490Q20 580 80 632T255 684H404V0Z" />
<glyph unicode="&#xb7;" glyph-name="middot" horiz-adv-x="113" d="M81 384V335H31V384H81Z" />
<glyph unicode="&#xb8;" glyph-name="cedilla" horiz-adv-x="249" d="M144 -85T176 -106T209 -168Q209 -207 182 -230T103 -254H40V-234H100Q189 -234 189 -168Q189 -141 168 -124T100 -107H63V7H83V-89Q144 -85 176 -106Z" />
<glyph unicode="&#xb9;" glyph-name="onesuperior" horiz-adv-x="154" d="M40 686V704H114V352H94V686H40Z" />
<glyph unicode="&#xba;" glyph-name="ordmasculine" horiz-adv-x="412" d="M255 709T293 686T352 622T374 526Q374 472 353 431T293 366T207 343Q159 343 121 366T62 430T40 526Q40 580 61 621T121 686T207 709Q255 709 293 686ZM143 689T102 646T60 526Q60 450
101 407T207 363Q271 363 312 406T354 526Q354 602 313 645T207 689Q143 689 102 646Z" />
<glyph unicode="&#xbb;" glyph-name="guillemotright" horiz-adv-x="374" d="M160 295L50 470H78L184 295L78 120H50L160 295ZM305 295L195 470H223L329 295L223 120H195L305 295Z" />
<glyph unicode="&#xbc;" glyph-name="onequarter" horiz-adv-x="512" d="M425 704L77 0H57L405 704H425ZM60 686V704H134V352H114V686H60ZM472 76H417V0H397V76H225V94L402 355H417V96H472V76ZM397 309L249 96H397V309Z" />
<glyph unicode="&#xbd;" glyph-name="onehalf" horiz-adv-x="593" d="M408 704L60 0H40L388 704H408ZM117 686H63V704H137V352H117V686ZM553 17V-1H332V9L446 122Q483 159 502 188T521 257Q521 297 499 316T443 336Q403 336 380 313T352 245H332Q335 292 363 323T443
354Q487 354 514 328T541 257Q541 214 519 180T452 101L364 17H553Z" />
<glyph unicode="&#xbe;" glyph-name="threequarters" horiz-adv-x="532" d="M218 707T248 681T278 611Q278 580 261 558T211 527V525Q242 516 260 494T279 439Q279 402 251 375T169 347Q69 347 57 444H77Q82 401 106 383T169 365Q212 365 235 385T259 439Q259
475 233 495T166 516H105V534H169Q209 534 233 554T258 611Q258 645 235 666T172 687Q132 687 109 669T78 619H58Q65 658 94 682T172 707Q218 707 248 681ZM475 704L127 0H107L455 704H475ZM492 76H437V0H417V76H245V94L422 355H437V96H492V76ZM417 309L269 96H417V309Z"
/>
<glyph unicode="&#xbf;" glyph-name="questiondown" horiz-adv-x="462" d="M139 -174T85 -121T30 20Q30 136 98 187T291 240V385H311V222H295Q186 222 118 180T50 20Q50 -62 98 -109T230 -156Q316 -156 369 -109T422 15H442Q442 -44 414 -86T337 -151T230 -174Q139
-174 85 -121ZM275 488V537H325V488H275Z" />
<glyph unicode="&#xc0;" glyph-name="Agrave" horiz-adv-x="601" d="M475 194H127L53 0H30L291 683H311L571 0H548L475 194ZM468 212L301 651L134 212H468ZM294 770V750L107 880V904L294 770Z" />
<glyph unicode="&#xc1;" glyph-name="Aacute" horiz-adv-x="601" d="M475 194H127L53 0H30L291 683H311L571 0H548L475 194ZM468 212L301 651L134 212H468ZM484 880L297 750V770L484 904V880Z" />
<glyph unicode="&#xc2;" glyph-name="Acircumflex" horiz-adv-x="601" d="M475 194H127L53 0H30L291 683H311L571 0H548L475 194ZM468 212L301 651L134 212H468ZM301 865L177 784V804L301 885L424 804V784L301 865Z" />
<glyph unicode="&#xc3;" glyph-name="Atilde" horiz-adv-x="601" d="M475 194H127L53 0H30L291 683H311L571 0H548L475 194ZM468 212L301 651L134 212H468ZM262 834T274 828T301 808Q315 796 326 790T353 784Q370 784 382 796T397 831H415Q413 804 396 785T353
766Q335 766 322 773T293 793Q278 805 268 810T245 816Q209 816 203 768H185Q189 799 204 816T245 834Q262 834 274 828Z" />
<glyph unicode="&#xc4;" glyph-name="Adieresis" horiz-adv-x="601" d="M475 194H127L53 0H30L291 683H311L571 0H548L475 194ZM468 212L301 651L134 212H468ZM204 833T212 842T234 851Q246 851 255 842T264 821Q264 809 255 800T234 791Q221 791 213 800T204
821Q204 833 212 842ZM338 833T346 842T368 851Q380 851 388 842T397 821Q397 809 389 800T368 791Q355 791 347 800T338 821Q338 833 346 842Z" />
<glyph unicode="&#xc5;" glyph-name="Aring" horiz-adv-x="601" d="M475 194H127L53 0H30L291 683H311L571 0H548L475 194ZM468 212L301 651L134 212H468ZM338 959T364 935T390 869Q390 828 364 804T300 779Q262 779 236 803T210 869Q210 910 236 934T300 959Q338
959 364 935ZM269 941T250 922T230 869Q230 836 249 817T300 797Q331 797 350 816T370 869Q370 902 351 921T300 941Q269 941 250 922Z" />
<glyph unicode="&#xc6;" glyph-name="AE" horiz-adv-x="837" d="M463 686V361H757V343H463V18H787V0H443V194H152L33 0H10L443 704H787V686H463ZM443 212V667L163 212H443Z" />
<glyph unicode="&#xc7;" glyph-name="Ccedilla" horiz-adv-x="769" d="M511 711T591 654T709 496H689Q657 585 580 639T388 693Q302 693 232 652T122 533T81 351Q81 248 121 171T232 53T388 11Q503 11 580 64T689 207H709Q672 106 592 50T388 -7Q294 -7 219 38T102
165T60 351Q60 456 102 537T219 665T388 711Q511 711 591 654ZM454 -85T486 -106T519 -168Q519 -207 492 -230T413 -254H350V-234H410Q499 -234 499 -168Q499 -141 478 -124T410 -107H373V7H393V-89Q454 -85 486 -106Z" />
<glyph unicode="&#xc8;" glyph-name="Egrave" horiz-adv-x="479" d="M105 686V361H399V343H105V18H429V0H85V704H429V686H105ZM251 770V750L64 880V904L251 770Z" />
<glyph unicode="&#xc9;" glyph-name="Eacute" horiz-adv-x="479" d="M105 686V361H399V343H105V18H429V0H85V704H429V686H105ZM441 880L254 750V770L441 904V880Z" />
<glyph unicode="&#xca;" glyph-name="Ecircumflex" horiz-adv-x="479" d="M105 686V361H399V343H105V18H429V0H85V704H429V686H105ZM258 865L134 784V804L258 885L381 804V784L258 865Z" />
<glyph unicode="&#xcb;" glyph-name="Edieresis" horiz-adv-x="479" d="M105 686V361H399V343H105V18H429V0H85V704H429V686H105ZM161 833T169 842T191 851Q203 851 212 842T221 821Q221 809 212 800T191 791Q178 791 170 800T161 821Q161 833 169 842ZM295 833T303
842T325 851Q337 851 345 842T354 821Q354 809 346 800T325 791Q312 791 304 800T295 821Q295 833 303 842Z" />
<glyph unicode="&#xcc;" glyph-name="Igrave" horiz-adv-x="190" d="M105 704V0H85V704H105ZM89 770V750L-98 880V904L89 770Z" />
<glyph unicode="&#xcd;" glyph-name="Iacute" horiz-adv-x="190" d="M105 704V0H85V704H105ZM279 880L92 750V770L279 904V880Z" />
<glyph unicode="&#xce;" glyph-name="Icircumflex" horiz-adv-x="190" d="M105 704V0H85V704H105ZM96 865L-28 784V804L96 885L219 804V784L96 865Z" />
<glyph unicode="&#xcf;" glyph-name="Idieresis" horiz-adv-x="190" d="M105 704V0H85V704H105ZM-1 833T7 842T29 851Q41 851 50 842T59 821Q59 809 50 800T29 791Q16 791 8 800T-1 821Q-1 833 7 842ZM133 833T141 842T163 851Q175 851 183 842T192 821Q192 809
184 800T163 791Q150 791 142 800T133 821Q133 833 141 842Z" />
<glyph unicode="&#xd0;" glyph-name="Eth" horiz-adv-x="742" d="M492 704T587 612T682 352Q682 184 587 92T310 0H125V343H10V361H125V704H310Q492 704 587 612ZM484 18T573 106T662 352Q662 510 573 598T310 686H145V361H378V343H145V18H310Q484 18 573 106Z" />
<glyph unicode="&#xd1;" glyph-name="Ntilde" horiz-adv-x="647" d="M562 0H542L105 667V0H85V703H105L542 37V703H562V0ZM285 834T297 828T324 808Q338 796 349 790T376 784Q393 784 405 796T420 831H438Q436 804 419 785T376 766Q358 766 345 773T316 793Q301
805 291 810T268 816Q232 816 226 768H208Q212 799 227 816T268 834Q285 834 297 828Z" />
<glyph unicode="&#xd2;" glyph-name="Ograve" horiz-adv-x="792" d="M493 710T569 665T689 537T732 351Q732 247 689 165T570 38T396 -8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q493 710 569 665ZM308 692T236 651T123 532T81 351Q81 248
122 171T236 52T396 10Q484 10 556 51T669 170T711 351Q711 454 670 531T556 650T396 692Q308 692 236 651ZM390 770V750L203 880V904L390 770Z" />
<glyph unicode="&#xd3;" glyph-name="Oacute" horiz-adv-x="792" d="M493 710T569 665T689 537T732 351Q732 247 689 165T570 38T396 -8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q493 710 569 665ZM308 692T236 651T123 532T81 351Q81 248
122 171T236 52T396 10Q484 10 556 51T669 170T711 351Q711 454 670 531T556 650T396 692Q308 692 236 651ZM580 880L393 750V770L580 904V880Z" />
<glyph unicode="&#xd4;" glyph-name="Ocircumflex" horiz-adv-x="792" d="M493 710T569 665T689 537T732 351Q732 247 689 165T570 38T396 -8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q493 710 569 665ZM308 692T236 651T123 532T81 351Q81
248 122 171T236 52T396 10Q484 10 556 51T669 170T711 351Q711 454 670 531T556 650T396 692Q308 692 236 651ZM397 865L273 784V804L397 885L520 804V784L397 865Z" />
<glyph unicode="&#xd5;" glyph-name="Otilde" horiz-adv-x="792" d="M493 710T569 665T689 537T732 351Q732 247 689 165T570 38T396 -8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q493 710 569 665ZM308 692T236 651T123 532T81 351Q81 248
122 171T236 52T396 10Q484 10 556 51T669 170T711 351Q711 454 670 531T556 650T396 692Q308 692 236 651ZM358 834T370 828T397 808Q411 796 422 790T449 784Q466 784 478 796T493 831H511Q509 804 492 785T449 766Q431 766 418 773T389 793Q374 805 364 810T341
816Q305 816 299 768H281Q285 799 300 816T341 834Q358 834 370 828Z" />
<glyph unicode="&#xd6;" glyph-name="Odieresis" horiz-adv-x="792" d="M493 710T569 665T689 537T732 351Q732 247 689 165T570 38T396 -8Q299 -8 223 37T103 165T60 351Q60 455 103 537T222 664T396 710Q493 710 569 665ZM308 692T236 651T123 532T81 351Q81
248 122 171T236 52T396 10Q484 10 556 51T669 170T711 351Q711 454 670 531T556 650T396 692Q308 692 236 651ZM300 833T308 842T330 851Q342 851 351 842T360 821Q360 809 351 800T330 791Q317 791 309 800T300 821Q300 833 308 842ZM434 833T442 842T464 851Q476
851 484 842T493 821Q493 809 485 800T464 791Q451 791 443 800T434 821Q434 833 442 842Z" />
<glyph unicode="&#xd7;" glyph-name="multiply" horiz-adv-x="469" d="M415 185L234 366L54 186L41 199L221 379L40 560L54 574L235 393L415 573L428 560L248 380L429 199L415 185Z" />
<glyph unicode="&#xd8;" glyph-name="Oslash" horiz-adv-x="792" d="M684 558T708 493T732 351Q732 247 689 165T570 38T396 -8Q327 -8 268 16T163 84L86 0H61L150 97Q107 145 84 210T60 351Q60 455 103 537T222 664T396 710Q465 710 524 686T629 619L707 704H731L641
606Q684 558 708 493ZM81 278T102 217T163 111L616 604Q573 647 517 669T396 692Q308 692 236 651T123 532T81 351Q81 278 102 217ZM484 10T556 51T669 170T711 351Q711 425 690 486T628 591L176 98Q218 55 274 33T396 10Q484 10 556 51Z" />
<glyph unicode="&#xd9;" glyph-name="Ugrave" horiz-adv-x="629" d="M100 704V267Q100 133 158 72T317 11Q415 11 472 72T529 267V704H549V267Q549 127 488 60T317 -7Q208 -7 144 60T80 267V704H100ZM308 770V750L121 880V904L308 770Z" />
<glyph unicode="&#xda;" glyph-name="Uacute" horiz-adv-x="629" d="M100 704V267Q100 133 158 72T317 11Q415 11 472 72T529 267V704H549V267Q549 127 488 60T317 -7Q208 -7 144 60T80 267V704H100ZM498 880L311 750V770L498 904V880Z" />
<glyph unicode="&#xdb;" glyph-name="Ucircumflex" horiz-adv-x="629" d="M100 704V267Q100 133 158 72T317 11Q415 11 472 72T529 267V704H549V267Q549 127 488 60T317 -7Q208 -7 144 60T80 267V704H100ZM315 865L191 784V804L315 885L438 804V784L315 865Z" />
<glyph unicode="&#xdc;" glyph-name="Udieresis" horiz-adv-x="629" d="M100 704V267Q100 133 158 72T317 11Q415 11 472 72T529 267V704H549V267Q549 127 488 60T317 -7Q208 -7 144 60T80 267V704H100ZM218 833T226 842T248 851Q260 851 269 842T278 821Q278
809 269 800T248 791Q235 791 227 800T218 821Q218 833 226 842ZM352 833T360 842T382 851Q394 851 402 842T411 821Q411 809 403 800T382 791Q369 791 361 800T352 821Q352 833 360 842Z" />
<glyph unicode="&#xdd;" glyph-name="Yacute" horiz-adv-x="518" d="M488 704L270 295V0H250V295L30 704H54L259 317L464 704H488ZM443 880L256 750V770L443 904V880Z" />
<glyph unicode="&#xde;" glyph-name="Thorn" horiz-adv-x="528" d="M105 159V0H85V704H105V546H263Q379 546 433 495T488 352Q488 255 431 207T263 159H105ZM371 180T419 225T468 352Q468 438 420 482T263 526H105V180H263Q371 180 419 225Z" />
<glyph unicode="&#xdf;" glyph-name="germandbls" horiz-adv-x="588" d="M490 298T519 252T548 145Q548 100 526 65T464 9T376 -12Q297 -12 250 33T202 161H222Q222 92 260 49T376 6Q426 6 460 26T511 78T528 145Q528 197 504 238T418 322L254 433V450L299 472Q341
492 361 521T381 595Q381 656 345 694T249 732Q181 732 143 694T105 588V0H85V588Q85 663 130 706T249 750Q290 750 325 732T380 678T401 595Q401 502 307 456L276 441L423 343Q490 298 519 252Z" />
<glyph unicode="&#xe0;" glyph-name="agrave" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190
110 132T194 42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498ZM317 606V586L130 716V740L317 606Z" />
<glyph unicode="&#xe1;" glyph-name="aacute" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190
110 132T194 42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498ZM507 716L320 586V606L507 740V716Z" />
<glyph unicode="&#xe2;" glyph-name="acircumflex" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80
190 110 132T194 42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498ZM324 701L200 620V640L324 721L447 640V620L324 701Z" />
<glyph unicode="&#xe3;" glyph-name="atilde" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190
110 132T194 42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498ZM285 670T297 664T324 644Q338 632 349 626T376 620Q393 620 405 632T420 667H438Q436 640 419 621T376 602Q358 602 345 609T316 629Q301 641 291 646T268
652Q232 652 226 604H208Q212 635 227 652T268 670Q285 670 297 664Z" />
<glyph unicode="&#xe4;" glyph-name="adieresis" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80
190 110 132T194 42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498ZM227 669T235 678T257 687Q269 687 278 678T287 657Q287 645 278 636T257 627Q244 627 236 636T227 657Q227 669 235 678ZM361 669T369 678T391 687Q403
687 411 678T420 657Q420 645 412 636T391 627Q378 627 370 636T361 657Q361 669 369 678Z" />
<glyph unicode="&#xe5;" glyph-name="aring" horiz-adv-x="671" d="M416 547T480 497T567 366V540H586V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497ZM249 529T195 498T110 409T80 270Q80 190
110 132T194 42T323 11Q393 11 448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498ZM361 795T387 771T413 705Q413 664 387 640T323 615Q285 615 259 639T233 705Q233 746 259 770T323 795Q361 795 387 771ZM292 777T273 758T253 705Q253
672 272 653T323 633Q354 633 373 652T393 705Q393 738 374 757T323 777Q292 777 273 758Z" />
<glyph unicode="&#xe6;" glyph-name="ae" horiz-adv-x="1135" d="M918 11T980 57T1055 183H1075Q1061 98 996 46T827 -7Q738 -7 672 40T586 175V0H567V174Q545 93 481 43T323 -7Q246 -7 186 26T93 122T60 270Q60 355 93 417T186 513T323 547Q416 547 480 497T567
366V540H586V365Q606 452 672 499T827 547Q906 547 962 514T1046 427T1075 315Q1075 285 1071 261H587Q588 179 621 123T709 39T827 11Q918 11 980 57ZM393 11T448 43T535 134T567 270Q567 347 536 405T449 496T323 529Q249 529 195 498T110 409T80 270Q80 190
110 132T194 42T323 11Q393 11 448 43ZM763 529T709 502T622 418T587 279H1057Q1062 362 1031 418T945 501T827 529Q763 529 709 502Z" />
<glyph unicode="&#xe7;" glyph-name="ccedilla" horiz-adv-x="628" d="M423 547T488 495T568 357H548Q535 437 473 483T320 529Q255 529 201 501T113 414T80 270Q80 184 113 126T200 40T320 11Q411 11 473 57T548 183H568Q554 98 489 46T320 -7Q245 -7 186 26T94
122T60 270Q60 355 93 418T186 514T320 547Q423 547 488 495ZM384 -85T416 -106T449 -168Q449 -207 422 -230T343 -254H280V-234H340Q429 -234 429 -168Q429 -141 408 -124T340 -107H303V7H323V-89Q384 -85 416 -106Z" />
<glyph unicode="&#xe8;" glyph-name="egrave" horiz-adv-x="628" d="M411 11T473 57T548 183H568Q554 98 489 46T320 -7Q245 -7 186 26T94 122T60 270Q60 355 93 418T186 514T320 547Q399 547 455 514T539 427T568 315Q568 285 564 261H80Q81 178 114 122T202
39T320 11Q411 11 473 57ZM256 529T202 502T115 418T80 279H550Q555 362 524 418T438 501T320 529Q256 529 202 502ZM308 606V586L121 716V740L308 606Z" />
<glyph unicode="&#xe9;" glyph-name="eacute" horiz-adv-x="628" d="M411 11T473 57T548 183H568Q554 98 489 46T320 -7Q245 -7 186 26T94 122T60 270Q60 355 93 418T186 514T320 547Q399 547 455 514T539 427T568 315Q568 285 564 261H80Q81 178 114 122T202
39T320 11Q411 11 473 57ZM256 529T202 502T115 418T80 279H550Q555 362 524 418T438 501T320 529Q256 529 202 502ZM498 716L311 586V606L498 740V716Z" />
<glyph unicode="&#xea;" glyph-name="ecircumflex" horiz-adv-x="628" d="M411 11T473 57T548 183H568Q554 98 489 46T320 -7Q245 -7 186 26T94 122T60 270Q60 355 93 418T186 514T320 547Q399 547 455 514T539 427T568 315Q568 285 564 261H80Q81 178 114 122T202
39T320 11Q411 11 473 57ZM256 529T202 502T115 418T80 279H550Q555 362 524 418T438 501T320 529Q256 529 202 502ZM315 701L191 620V640L315 721L438 640V620L315 701Z" />
<glyph unicode="&#xeb;" glyph-name="edieresis" horiz-adv-x="628" d="M411 11T473 57T548 183H568Q554 98 489 46T320 -7Q245 -7 186 26T94 122T60 270Q60 355 93 418T186 514T320 547Q399 547 455 514T539 427T568 315Q568 285 564 261H80Q81 178 114 122T202
39T320 11Q411 11 473 57ZM256 529T202 502T115 418T80 279H550Q555 362 524 418T438 501T320 529Q256 529 202 502ZM218 669T226 678T248 687Q260 687 269 678T278 657Q278 645 269 636T248 627Q235 627 227 636T218 657Q218 669 226 678ZM352 669T360 678T382
687Q394 687 402 678T411 657Q411 645 403 636T382 627Q369 627 361 636T352 657Q352 669 360 678Z" />
<glyph unicode="&#xec;" glyph-name="igrave" horiz-adv-x="190" d="M105 540V0H85V540H105ZM89 606V586L-98 716V740L89 606Z" />
<glyph unicode="&#xed;" glyph-name="iacute" horiz-adv-x="190" d="M105 540V0H85V540H105ZM279 716L92 586V606L279 740V716Z" />
<glyph unicode="&#xee;" glyph-name="icircumflex" horiz-adv-x="190" d="M105 540V0H85V540H105ZM352 701L228 620V640L352 721L475 640V620L352 701Z" />
<glyph unicode="&#xef;" glyph-name="idieresis" horiz-adv-x="190" d="M105 540V0H85V540H105ZM255 669T263 678T285 687Q297 687 306 678T315 657Q315 645 306 636T285 627Q272 627 264 636T255 657Q255 669 263 678ZM389 669T397 678T419 687Q431 687 439 678T448
657Q448 645 440 636T419 627Q406 627 398 636T389 657Q389 669 397 678Z" />
<glyph unicode="&#xf0;" glyph-name="eth" horiz-adv-x="645" d="M507 593T546 505T585 307Q585 153 514 73T323 -7Q242 -7 183 30T92 133T60 286Q60 365 92 425T184 519T323 552Q411 552 478 502T571 351Q558 447 521 520T406 668L252 612V630L392 681Q354 715
322 740H344Q390 704 408 687L557 741V723L422 674Q507 593 546 505ZM392 11T447 42T533 131T565 270Q565 352 533 412T445 503T323 534Q253 534 198 505T112 420T80 286Q80 201 111 139T197 44T323 11Q392 11 447 42Z" />
<glyph unicode="&#xf1;" glyph-name="ntilde" horiz-adv-x="614" d="M412 552T473 493T534 315V0H515V317Q515 423 462 478T317 534Q221 534 163 472T105 287V0H85V540H105V388Q124 470 182 511T317 552Q412 552 473 493ZM271 670T283 664T310 644Q324 632 335
626T362 620Q379 620 391 632T406 667H424Q422 640 405 621T362 602Q344 602 331 609T302 629Q287 641 277 646T254 652Q218 652 212 604H194Q198 635 213 652T254 670Q271 670 283 664Z" />
<glyph unicode="&#xf2;" glyph-name="ograve" horiz-adv-x="647" d="M399 547T459 514T553 418T587 270Q587 185 553 123T459 27T324 -7Q249 -7 189 26T95 122T60 270Q60 355 94 417T189 513T324 547Q399 547 459 514ZM259 529T204 501T114 414T80 270Q80 185
114 127T203 40T324 11Q389 11 444 39T533 126T567 270Q567 356 533 414T444 500T324 529Q259 529 204 501ZM317 606V586L130 716V740L317 606Z" />
<glyph unicode="&#xf3;" glyph-name="oacute" horiz-adv-x="647" d="M399 547T459 514T553 418T587 270Q587 185 553 123T459 27T324 -7Q249 -7 189 26T95 122T60 270Q60 355 94 417T189 513T324 547Q399 547 459 514ZM259 529T204 501T114 414T80 270Q80 185
114 127T203 40T324 11Q389 11 444 39T533 126T567 270Q567 356 533 414T444 500T324 529Q259 529 204 501ZM507 716L320 586V606L507 740V716Z" />
<glyph unicode="&#xf4;" glyph-name="ocircumflex" horiz-adv-x="647" d="M399 547T459 514T553 418T587 270Q587 185 553 123T459 27T324 -7Q249 -7 189 26T95 122T60 270Q60 355 94 417T189 513T324 547Q399 547 459 514ZM259 529T204 501T114 414T80 270Q80
185 114 127T203 40T324 11Q389 11 444 39T533 126T567 270Q567 356 533 414T444 500T324 529Q259 529 204 501ZM324 701L200 620V640L324 721L447 640V620L324 701Z" />
<glyph unicode="&#xf5;" glyph-name="otilde" horiz-adv-x="647" d="M399 547T459 514T553 418T587 270Q587 185 553 123T459 27T324 -7Q249 -7 189 26T95 122T60 270Q60 355 94 417T189 513T324 547Q399 547 459 514ZM259 529T204 501T114 414T80 270Q80 185
114 127T203 40T324 11Q389 11 444 39T533 126T567 270Q567 356 533 414T444 500T324 529Q259 529 204 501ZM285 670T297 664T324 644Q338 632 349 626T376 620Q393 620 405 632T420 667H438Q436 640 419 621T376 602Q358 602 345 609T316 629Q301 641 291 646T268
652Q232 652 226 604H208Q212 635 227 652T268 670Q285 670 297 664Z" />
<glyph unicode="&#xf6;" glyph-name="odieresis" horiz-adv-x="647" d="M399 547T459 514T553 418T587 270Q587 185 553 123T459 27T324 -7Q249 -7 189 26T95 122T60 270Q60 355 94 417T189 513T324 547Q399 547 459 514ZM259 529T204 501T114 414T80 270Q80 185
114 127T203 40T324 11Q389 11 444 39T533 126T567 270Q567 356 533 414T444 500T324 529Q259 529 204 501ZM227 669T235 678T257 687Q269 687 278 678T287 657Q287 645 278 636T257 627Q244 627 236 636T227 657Q227 669 235 678ZM361 669T369 678T391 687Q403
687 411 678T420 657Q420 645 412 636T391 627Q378 627 370 636T361 657Q361 669 369 678Z" />
<glyph unicode="&#xf7;" glyph-name="divide" horiz-adv-x="589" d="M320 618V569H270V618H320ZM549 389V371H40V389H549ZM320 194V145H270V194H320Z" />
<glyph unicode="&#xf8;" glyph-name="oslash" horiz-adv-x="647" d="M550 433T568 383T587 270Q587 185 553 123T459 27T324 -7Q217 -7 144 58L91 0H69L132 70Q97 106 79 157T60 270Q60 355 94 417T189 513T324 547Q377 547 423 530T505 480L559 540H581L516 469Q550
433 568 383ZM80 152T145 84L492 466Q459 497 416 513T324 529Q259 529 204 501T114 414T80 270Q80 152 145 84ZM389 11T444 39T533 126T567 270Q567 385 504 455L157 72Q190 42 233 27T324 11Q389 11 444 39Z" />
<glyph unicode="&#xf9;" glyph-name="ugrave" horiz-adv-x="599" d="M514 540V0H494V152Q475 70 417 29T282 -12Q187 -12 126 47T65 225V540H84V223Q84 117 137 62T282 6Q378 6 436 68T494 253V540H514ZM283 606V586L96 716V740L283 606Z" />
<glyph unicode="&#xfa;" glyph-name="uacute" horiz-adv-x="599" d="M514 540V0H494V152Q475 70 417 29T282 -12Q187 -12 126 47T65 225V540H84V223Q84 117 137 62T282 6Q378 6 436 68T494 253V540H514ZM473 716L286 586V606L473 740V716Z" />
<glyph unicode="&#xfb;" glyph-name="ucircumflex" horiz-adv-x="599" d="M514 540V0H494V152Q475 70 417 29T282 -12Q187 -12 126 47T65 225V540H84V223Q84 117 137 62T282 6Q378 6 436 68T494 253V540H514ZM290 701L166 620V640L290 721L413 640V620L290 701Z" />
<glyph unicode="&#xfc;" glyph-name="udieresis" horiz-adv-x="599" d="M514 540V0H494V152Q475 70 417 29T282 -12Q187 -12 126 47T65 225V540H84V223Q84 117 137 62T282 6Q378 6 436 68T494 253V540H514ZM193 669T201 678T223 687Q235 687 244 678T253 657Q253
645 244 636T223 627Q210 627 202 636T193 657Q193 669 201 678ZM327 669T335 678T357 687Q369 687 377 678T386 657Q386 645 378 636T357 627Q344 627 336 636T327 657Q327 669 335 678Z" />
<glyph unicode="&#xfd;" glyph-name="yacute" horiz-adv-x="503" d="M42 540L260 29L461 540H483L175 -254H150L249 -1L20 540H42ZM435 716L248 586V606L435 740V716Z" />
<glyph unicode="&#xfe;" glyph-name="thorn" horiz-adv-x="671" d="M425 547T485 514T578 418T611 270Q611 185 578 123T485 27T348 -7Q255 -7 191 43T104 174V-254H85V740H104V366Q126 447 190 497T348 547Q425 547 485 514ZM278 529T223 497T136 406T104 270Q104
193 135 135T222 44T348 11Q422 11 476 42T561 131T591 270Q591 350 561 408T477 498T348 529Q278 529 223 497Z" />
<glyph unicode="&#xff;" glyph-name="ydieresis" horiz-adv-x="503" d="M42 540L260 29L461 540H483L175 -254H150L249 -1L20 540H42ZM155 669T163 678T185 687Q197 687 206 678T215 657Q215 645 206 636T185 627Q172 627 164 636T155 657Q155 669 163 678ZM289
669T297 678T319 687Q331 687 339 678T348 657Q348 645 340 636T319 627Q306 627 298 636T289 657Q289 669 297 678Z" />
<glyph unicode="&#x2013;" glyph-name="endash" horiz-adv-x="581" d="M541 389V371H40V389H541Z" />
<glyph unicode="&#x2014;" glyph-name="emdash" horiz-adv-x="764" d="M724 389V371H40V389H724Z" />
<glyph unicode="&#x2018;" glyph-name="quoteleft" horiz-adv-x="122" d="M30 632T30 662Q30 706 47 723T97 740V720Q72 720 62 709T52 675V646H90V593H34Q30 632 30 662Z" />
<glyph unicode="&#x2019;" glyph-name="quoteright" horiz-adv-x="122" d="M92 701T92 671Q92 627 75 610T25 593V613Q50 613 60 624T70 658V687H32V740H88Q92 701 92 671Z" />
<glyph unicode="&#x201a;" glyph-name="quotesinglbase" horiz-adv-x="138" d="M90 7T90 -2Q90 -86 25 -86V-68Q68 -68 68 -14V-7H37V31H88Q90 7 90 -2Z" />
<glyph unicode="&#x201c;" glyph-name="quotedblleft" horiz-adv-x="208" d="M30 632T30 662Q30 706 47 723T97 740V720Q72 720 62 709T52 675V646H90V593H34Q30 632 30 662ZM116 632T116 662Q116 706 133 723T183 740V720Q158 720 148 709T138 675V646H176V593H120Q116
632 116 662Z" />
<glyph unicode="&#x201d;" glyph-name="quotedblright" horiz-adv-x="208" d="M92 701T92 671Q92 627 75 610T25 593V613Q50 613 60 624T70 658V687H32V740H88Q92 701 92 671ZM178 701T178 671Q178 627 161 610T111 593V613Q136 613 146 624T156 658V687H118V740H174Q178
701 178 671Z" />
<glyph unicode="&#x201e;" glyph-name="quotedblbase" horiz-adv-x="208" d="M92 14T92 -16Q92 -60 75 -77T25 -94V-74Q50 -74 60 -63T70 -29V0H32V53H88Q92 14 92 -16ZM178 14T178 -16Q178 -60 161 -77T111 -94V-74Q136 -74 146 -63T156 -29V0H118V53H174Q178
14 178 -16Z" />
<glyph unicode="&#x2022;" glyph-name="bullet" horiz-adv-x="256" d="M30 374T58 403T128 432Q169 432 197 403T226 333Q226 292 198 264T128 236Q87 236 59 264T30 333Q30 374 58 403Z" />
<glyph unicode="&#x2039;" glyph-name="guilsinglleft" horiz-adv-x="229" d="M152 120L45 295L152 470H179L70 295L179 120H152Z" />
<glyph unicode="&#x203a;" glyph-name="guilsinglright" horiz-adv-x="229" d="M160 295L50 470H78L184 295L78 120H50L160 295Z" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 52 KiB

View file

@ -1,320 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<defs >
<font id="Poppins" horiz-adv-x="855" ><font-face
font-family="Poppins Light"
units-per-em="1000"
panose-1="0 0 4 0 0 0 0 0 0 0"
ascent="1050"
descent="-350"
alphabetic="0" />
<glyph unicode=" " glyph-name="space" horiz-adv-x="260" />
<glyph unicode="!" glyph-name="exclam" horiz-adv-x="277" d="M179 717L168 196H112L101 717H179ZM188 90V0H95V90H188Z" />
<glyph unicode="&quot;" glyph-name="quotedbl" horiz-adv-x="270" d="M108 796L100 603H51L41 796H108ZM228 796L220 603H171L161 796H228Z" />
<glyph unicode="#" glyph-name="numbersign" horiz-adv-x="791" d="M597 454L560 281H704V217H546L500 0H433L479 217H256L210 0H144L190 217H33V281H204L241 454H87V518H255L302 740H368L321 518H544L591 740H658L611 518H758V454H597ZM530 454H307L270 281H493L530
454Z" />
<glyph unicode="$" glyph-name="dollar" horiz-adv-x="568" d="M371 365T413 346T485 286T516 179Q516 131 493 89T422 20T302 -7H297V-87H270V-6Q178 3 120 55T55 184H129Q134 139 170 99T270 51V331Q197 353 155 371T83 428T53 533Q53 586 79 626T153 690T268
713H270V800H297V712Q386 705 438 657T501 542H426Q418 582 386 615T297 656V387Q371 365 413 346ZM127 477T163 448T270 395V658H268Q203 658 165 626T127 536Q127 477 163 448ZM368 49T405 85T442 178Q442 238 405 269T297 322V49H300Q368 49 405 85Z" />
<glyph unicode="%" glyph-name="percent" horiz-adv-x="621" d="M237 714T276 675T316 572Q316 508 277 469T176 430Q114 430 75 469T35 572Q35 636 74 675T176 714Q237 714 276 675ZM551 706L140 0H70L480 706H551ZM86 529T110 503T175 476Q215 476 240 502T265
572Q265 614 240 641T175 668Q135 668 111 641T86 572Q86 529 110 503ZM507 276T546 237T586 134Q586 70 547 31T446 -9Q385 -9 346 30T306 134Q306 198 345 237T446 276Q507 276 546 237ZM357 91T381 64T446 37Q485 37 510 64T535 134Q535 176 510 203T446 230Q406
230 382 203T357 134Q357 91 381 64Z" />
<glyph unicode="&amp;" glyph-name="ampersand" horiz-adv-x="679" d="M598 0L424 191Q425 186 425 177Q425 163 422 145Q414 78 365 36T244 -7Q185 -7 138 18T63 91T36 200Q36 262 60 307T125 375T215 398Q234 398 254 393Q235 415 234 416Q202 452 186 483T170
559Q170 599 190 634T249 690T343 712Q429 712 473 668T523 557Q528 533 526 507H459Q460 513 460 522Q460 532 459 536Q459 591 429 622T344 653Q294 653 266 624T237 553Q237 518 250 494T290 438L672 0H598ZM305 54T343 92T382 191Q382 233 360 268T303 322T229
342Q172 342 136 306T99 202Q99 134 141 94T244 54Q305 54 343 92Z" />
<glyph unicode="&apos;" glyph-name="quotesingle" horiz-adv-x="156" d="M114 796L103 603H54L41 796H114Z" />
<glyph unicode="(" glyph-name="parenleft" horiz-adv-x="375" d="M49 596T121 746T311 989H379V981Q247 874 182 728T117 403Q117 224 182 78T379 -176V-183H311Q193 -91 121 59T49 403Q49 596 121 746Z" />
<glyph unicode=")" glyph-name="parenright" horiz-adv-x="376" d="M327 209T255 59T65 -183H-4V-176Q129 -69 194 77T259 403Q259 582 194 728T-4 981V989H65Q183 897 255 747T327 403Q327 209 255 59Z" />
<glyph unicode="*" glyph-name="asterisk" horiz-adv-x="462" d="M378 667L414 603L265 546L414 490L378 426L254 518L273 360H200L216 518L89 423L51 489L198 546L52 604L89 669L215 576L199 732H272L254 576L378 667Z" />
<glyph unicode="+" glyph-name="plus" horiz-adv-x="580" d="M545 338H324V115H255V338H35V402H255V625H324V402H545V338Z" />
<glyph unicode="," glyph-name="comma" horiz-adv-x="159" d="M125 22T125 -12Q125 -60 102 -88T33 -117V-84Q57 -84 68 -71T79 -29V-21H34V56H115Q125 22 125 -12Z" />
<glyph unicode="-" glyph-name="hyphen" horiz-adv-x="477" d="M625 400V341H235V400H625Z" />
<glyph unicode="." glyph-name="period" horiz-adv-x="154" d="M123 90V0H30V90H123Z" />
<glyph unicode="/" glyph-name="slash" horiz-adv-x="369" d="M357 940L82 -167H14L288 940H357Z" />
<glyph unicode="0" glyph-name="zero" horiz-adv-x="607" d="M61 185T114 91T304 -4Q439 -4 493 90T547 356Q547 526 493 620T304 714Q168 714 115 620T61 356Q61 185 114 91ZM477 263T464 200T412 99T304 62Q233 62 195 99T144 199T130 356Q130 448 143 511T195
611T304 648Q374 648 412 611T463 511T477 356Q477 263 464 200Z" />
<glyph unicode="1" glyph-name="one" horiz-adv-x="269" d="M14 640V705H193V0H120V640H14Z" />
<glyph unicode="2" glyph-name="two" horiz-adv-x="567" d="M222 170T327 288T433 509Q433 574 399 614T289 655Q211 655 171 607T125 475H56Q63 590 127 652T294 715Q388 715 446 664T504 515Q504 432 449 346T312 186T145 61H524V3H45V53Q222 170 327 288Z" />
<glyph unicode="3" glyph-name="three" horiz-adv-x="585" d="M366 720T413 697T484 632T509 539Q509 473 470 427T351 373V367Q431 347 476 305T522 187Q522 101 466 47T301 -7Q194 -7 128 48T49 210H118Q129 139 175 97T299 54Q374 54 413 92T453 191Q453 263
397 300T237 338H215V397H237Q440 397 440 532Q440 591 402 626T297 661Q231 661 185 624T130 513H61Q69 611 131 665T299 720Q366 720 413 697Z" />
<glyph unicode="4" glyph-name="four" horiz-adv-x="607" d="M28 170V228L400 708H475V232H585V170H475V0H404V170H28ZM409 621L103 232H409V621Z" />
<glyph unicode="5" glyph-name="five" horiz-adv-x="609" d="M523 649H149V388Q176 424 226 446T334 468Q447 468 502 403T558 240Q558 133 498 64T320 -5Q217 -5 152 51T76 203H145Q158 133 203 95T316 57Q402 57 445 107T489 241Q489 321 445 366T319 411Q262
411 219 387T152 321H84V711H523V649Z" />
<glyph unicode="6" glyph-name="six" horiz-adv-x="619" d="M461 587T423 624T318 662Q226 662 178 592T129 369Q129 340 130 325Q144 391 201 429T336 468Q443 468 503 405T563 228Q563 163 538 110T460 25T328 -7Q177 -7 122 87T66 344Q66 529 126 624T322 720Q419
720 473 665T537 519H470Q461 587 423 624ZM138 194T158 151T220 81T326 54Q403 54 449 100T496 229Q496 318 450 365T322 412Q276 412 234 393T165 337T138 244Q138 194 158 151Z" />
<glyph unicode="7" glyph-name="seven" horiz-adv-x="505" d="M472 653L201 0H127L401 646H13V707H472V653Z" />
<glyph unicode="8" glyph-name="eight" horiz-adv-x="620" d="M72 590T100 632T182 698T310 722Q385 722 438 698T520 632T548 537Q548 477 515 434T412 372Q485 355 526 308T567 189Q567 91 498 35T310 -22Q193 -22 123 34T53 189Q53 260 94 307T208 372Q139
390 106 433T72 537Q72 590 100 632ZM142 463T187 428T310 392Q388 392 433 427T478 528Q478 593 433 628T310 664Q234 664 188 629T142 528Q142 463 187 428ZM122 119T173 78T310 37Q396 37 447 78T498 192Q498 267 445 304T310 342Q228 342 175 305T122 192Q122
119 173 78Z" />
<glyph unicode="9" glyph-name="nine" horiz-adv-x="610" d="M155 125T193 88T298 50Q392 50 439 120T487 346Q487 373 486 387Q472 322 415 283T279 244Q173 244 113 308T53 485Q53 550 78 603T155 687T287 719Q438 719 494 626T550 371Q550 187 492 90T298 -8Q200
-8 144 47T79 194H145Q155 125 193 88ZM477 547T430 602T289 658Q212 658 166 612T119 484Q119 395 165 348T293 300Q339 300 381 318T450 375T477 468Q477 547 430 602Z" />
<glyph unicode=":" glyph-name="colon" horiz-adv-x="168" d="M131 528V438H39V528H131ZM131 90V0H39V90H131Z" />
<glyph unicode=";" glyph-name="semicolon" horiz-adv-x="213" d="M166 527V437H73V527H166ZM165 22T165 -12Q165 -60 142 -88T73 -117V-84Q97 -84 108 -71T119 -29V-21H74V56H155Q165 22 165 -12Z" />
<glyph unicode="&lt;" glyph-name="less" horiz-adv-x="410" d="M287 139L35 370L287 603H375L123 370L375 139H287Z" />
<glyph unicode="=" glyph-name="equal" horiz-adv-x="580" d="M545 504V440H35V504H545ZM545 301V237H35V301H545Z" />
<glyph unicode="&gt;" glyph-name="greater" horiz-adv-x="410" d="M35 603H123L375 370L123 139H35L287 370L35 603Z" />
<glyph unicode="?" glyph-name="question" horiz-adv-x="496" d="M346 727T403 674T461 527Q461 418 395 366T212 312V186H145V361H178Q274 361 333 396T392 527Q392 593 353 630T248 668Q179 668 137 630T95 529H28Q27 588 55 633T134 702T250 727Q346 727 403
674ZM225 90V0H133V90H225Z" />
<glyph unicode="@" glyph-name="at" horiz-adv-x="1106" d="M747 780T851 724T1009 568T1063 333Q1063 236 1032 158T948 34T832 -11Q780 -11 750 14T719 95Q719 111 722 130Q647 -11 501 -11Q438 -11 389 20T313 106T286 234Q286 325 324 399T428 515T575 557Q649
557 701 527T779 445T806 328Q806 301 802 278L778 134Q776 116 776 109Q776 77 791 64T836 51Q878 51 913 84T970 181T992 331Q992 448 946 535T810 672T590 721Q444 721 337 657T174 481T118 232Q118 23 233 -83T555 -192L542 -251Q309 -247 179 -124T48 232Q48
384 115 509T308 707T599 780Q747 780 851 724ZM736 396T692 445T565 494Q504 494 456 462T381 372T354 242Q354 158 399 105T521 51Q580 51 629 83T707 176T736 315Q736 396 692 445Z" />
<glyph unicode="A" glyph-name="A" horiz-adv-x="644" d="M483 172H162L98 0H23L283 689H362L622 0H546L483 172ZM462 228L323 608L182 228H462Z" />
<glyph unicode="B" glyph-name="B" horiz-adv-x="591" d="M465 352T506 304T547 190Q547 105 489 53T321 0H76V705H318Q424 705 477 658T531 530Q531 461 494 420T399 365Q465 352 506 304ZM147 390H309Q383 390 421 424T460 519Q460 578 422 612T305 646H147V390ZM388
60T432 96T476 198Q476 262 431 299T307 337H147V60H309Q388 60 432 96Z" />
<glyph unicode="C" glyph-name="C" horiz-adv-x="765" d="M513 712T596 651T713 481H639Q611 557 546 602T385 647Q312 647 253 611T159 508T125 352Q125 263 159 197T252 94T385 58Q481 58 546 103T639 224H713Q679 116 596 55T386 -7Q289 -7 213 39T95 166T53
352Q53 457 95 539T213 666T386 712Q513 712 596 651Z" />
<glyph unicode="D" glyph-name="D" horiz-adv-x="712" d="M660 244T617 165T491 43T289 0H76V705H289Q467 705 563 612T660 353Q660 244 617 165ZM433 63T511 139T589 353Q589 490 511 566T284 643H147V63H284Q433 63 511 139Z" />
<glyph unicode="E" glyph-name="E" horiz-adv-x="498" d="M147 648V382H422V327H147V58H452V0H76V705H452V648H147Z" />
<glyph unicode="F" glyph-name="F" horiz-adv-x="496" d="M459 705V648H147V379H417V324H147V0H76V705H459Z" />
<glyph unicode="G" glyph-name="G" horiz-adv-x="782" d="M514 711T597 651T711 488H638Q611 559 547 603T394 647Q318 647 257 611T160 508T125 352Q125 261 160 194T258 91T401 55Q472 55 531 86T626 178T669 319H376V372H730V310Q723 220 680 148T563 35T395
-7Q295 -7 217 38T96 166T53 352Q53 456 96 538T216 665T394 711Q514 711 597 651Z" />
<glyph unicode="H" glyph-name="H" horiz-adv-x="676" d="M599 705V0H529V334H147V0H76V705H147V392H529V705H599Z" />
<glyph unicode="I" glyph-name="I" horiz-adv-x="223" d="M147 705V0H76V705H147Z" />
<glyph unicode="J" glyph-name="J" horiz-adv-x="467" d="M396 705V170Q396 90 349 42T218 -7Q127 -7 76 47T25 198H95Q95 134 124 96T215 57Q274 57 299 89T325 170V705H396Z" />
<glyph unicode="K" glyph-name="K" horiz-adv-x="559" d="M209 354L542 0H455L147 331V0H76V705H147V374L455 705H542L209 354Z" />
<glyph unicode="L" glyph-name="L" horiz-adv-x="423" d="M147 56H403V0H76V705H147V56Z" />
<glyph unicode="M" glyph-name="M" horiz-adv-x="836" d="M759 690V0H689V578L450 0H387L147 576V0H76V690H159L419 87L677 690H759Z" />
<glyph unicode="N" glyph-name="N" horiz-adv-x="680" d="M604 0H533L147 589V0H76V704H147L533 114V704H604V0Z" />
<glyph unicode="O" glyph-name="O" horiz-adv-x="791" d="M494 713T572 667T694 539T739 353Q739 249 695 167T572 38T396 -8Q298 -8 220 38T98 166T53 353Q53 457 97 539T220 667T396 713Q494 713 572 667ZM319 648T258 612T161 509T125 353Q125 264 160 197T257
94T396 58Q473 58 534 94T631 197T667 353Q667 442 632 509T535 612T396 648Q319 648 258 612Z" />
<glyph unicode="P" glyph-name="P" horiz-adv-x="558" d="M147 294V0H76V705H287Q406 705 464 650T523 499Q523 401 463 348T287 294H147ZM372 354T412 391T452 499Q452 571 412 608T281 646H147V354H281Q372 354 412 391Z" />
<glyph unicode="Q" glyph-name="Q" horiz-adv-x="791" d="M660 -141L502 7Q453 -8 396 -8Q298 -8 220 38T98 166T53 353Q53 457 97 539T220 667T396 713Q494 713 572 667T694 539T739 353Q739 245 691 161T561 32L752 -141H660ZM125 264T160 197T257 94T396 58Q473
58 534 94T631 197T667 353Q667 442 632 509T535 612T396 648Q319 648 258 612T161 509T125 353Q125 264 160 197Z" />
<glyph unicode="R" glyph-name="R" horiz-adv-x="569" d="M454 0L261 294H147V0H76V705H292Q411 705 469 649T528 498Q528 410 479 358T335 296L534 0H454ZM147 347H287Q457 347 457 494Q457 644 287 644H147V347Z" />
<glyph unicode="S" glyph-name="S" horiz-adv-x="558" d="M506 131T484 89T414 20T297 -7Q229 -7 176 18T92 86T55 184H129Q133 153 152 122T207 71T294 50Q361 50 396 86T432 177Q432 224 409 253T351 299T256 334Q190 355 150 374T81 432T53 533Q53 613 109
663T263 713Q363 713 421 664T491 542H416Q407 587 369 622T263 657Q201 657 164 625T127 536Q127 491 150 463T206 420T298 386Q365 365 406 345T477 285T506 179Q506 131 484 89Z" />
<glyph unicode="T" glyph-name="T" horiz-adv-x="507" d="M489 705V648H288V0H218V648H18V705H489Z" />
<glyph unicode="U" glyph-name="U" horiz-adv-x="652" d="M142 705V278Q142 165 191 112T327 59Q413 59 461 112T510 278V705H580V278Q580 135 511 64T326 -7Q210 -7 141 64T72 278V705H142Z" />
<glyph unicode="V" glyph-name="V" horiz-adv-x="655" d="M100 705L327 82L556 705H633L371 0H284L23 705H100Z" />
<glyph unicode="W" glyph-name="W" horiz-adv-x="953" d="M919 705L737 0H653L477 604L300 0H216L34 705H112L259 87L437 705H516L694 86L842 705H919Z" />
<glyph unicode="X" glyph-name="X" horiz-adv-x="566" d="M461 0L277 297L106 0H25L238 359L23 705H106L287 414L458 705H539L325 352L544 0H461Z" />
<glyph unicode="Y" glyph-name="Y" horiz-adv-x="571" d="M549 705L322 275V0H251V275L23 705H104L286 350L468 705H549Z" />
<glyph unicode="Z" glyph-name="Z" horiz-adv-x="523" d="M117 59H484V0H39V56L398 646H49V705H477V648L117 59Z" />
<glyph unicode="[" glyph-name="bracketleft" horiz-adv-x="298" d="M254 940V882H155V-108H254V-166H88V940H254Z" />
<glyph unicode="\" glyph-name="backslash" horiz-adv-x="575" d="M398 -167L73 940H143L467 -167H398Z" />
<glyph unicode="]" glyph-name="bracketright" horiz-adv-x="297" d="M44 -166V-108H143V882H44V940H210V-166H44Z" />
<glyph unicode="^" glyph-name="asciicircum" horiz-adv-x="604" d="M106 171H36L270 690H334L567 171H497L303 605L106 171Z" />
<glyph unicode="_" glyph-name="underscore" horiz-adv-x="591" d="M551 -33V-101H42V-33H551Z" />
<glyph unicode="`" glyph-name="grave" horiz-adv-x="281" d="M238 642V590L33 711V770L238 642Z" />
<glyph unicode="a" glyph-name="a" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176 114T324 55Q382
55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433Z" />
<glyph unicode="b" glyph-name="b" horiz-adv-x="673" d="M443 553T500 520T590 423T623 273Q623 188 591 125T500 27T368 -7Q284 -7 226 36T146 150V0H76V740H146V396Q168 467 226 510T368 553Q443 553 500 520ZM291 491T245 464T172 388T146 273Q146 208 172
159T244 82T349 55Q442 55 497 113T552 273Q552 374 497 432T349 491Q291 491 245 464Z" />
<glyph unicode="c" glyph-name="c" horiz-adv-x="623" d="M421 553T489 498T573 352H500Q489 417 439 454T316 492Q263 492 219 468T148 395T121 273Q121 201 147 152T218 78T316 54Q389 54 439 91T500 194H573Q558 103 490 48T317 -7Q239 -7 179 26T84 123T50
273Q50 359 84 422T178 519T317 553Q421 553 489 498Z" />
<glyph unicode="d" glyph-name="d" horiz-adv-x="673" d="M389 553T447 510T527 396V740H597V0H527V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176 114T324 55Q382
55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433Z" />
<glyph unicode="e" glyph-name="e" horiz-adv-x="625" d="M388 51T438 88T498 188H572Q557 101 489 47T317 -7Q239 -7 179 26T84 123T50 273Q50 359 84 422T178 519T317 553Q397 553 455 520T544 430T575 308Q575 280 571 257H119Q120 188 147 142T219 73T314
51Q388 51 438 88ZM264 496T220 474T149 407T119 293H505Q509 360 483 406T413 474T317 496Q264 496 220 474Z" />
<glyph unicode="f" glyph-name="f" horiz-adv-x="335" d="M230 692T200 664T169 568V546H306V484H169V0H99V484H18V546H99V573Q99 665 149 711T306 753V691Q230 692 200 664Z" />
<glyph unicode="g" glyph-name="g" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V-13Q597 -89 565 -146T476 -235T346 -267Q239 -267 171 -216T81 -76H151Q169 -136 217 -171T342 -206Q394 -206 436 -184T503 -117T528 -13V150Q506 79 448 36T306 -7Q232
-7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176 114T324 55Q382 55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433Z" />
<glyph unicode="h" glyph-name="h" horiz-adv-x="635" d="M444 556T503 496T563 318V0H494V313Q494 402 449 449T326 497Q244 497 196 445T147 290V0H76V740H147V412Q168 482 223 519T349 556Q444 556 503 496Z" />
<glyph unicode="i" glyph-name="i" horiz-adv-x="223" d="M60 730T74 743T112 756Q135 756 149 743T163 708Q163 687 149 674T112 661Q89 661 75 674T60 708Q60 730 74 743ZM147 546V0H76V546H147Z" />
<glyph unicode="j" glyph-name="j" horiz-adv-x="230" d="M96 661T82 674T67 708Q67 730 81 743T119 756Q142 756 156 743T170 708Q170 687 156 674T119 661Q96 661 82 674ZM-27 -196H11Q49 -196 66 -179T84 -120V546H154V-124Q154 -258 18 -258H-27V-196Z" />
<glyph unicode="k" glyph-name="k" horiz-adv-x="465" d="M368 0L147 250V0H76V740H147V306L364 546H451L200 276L458 0H368Z" />
<glyph unicode="l" glyph-name="l" horiz-adv-x="223" d="M147 740V0H76V740H147Z" />
<glyph unicode="m" glyph-name="m" horiz-adv-x="1021" d="M833 556T891 496T950 318V0H880V313Q880 401 838 448T721 495Q642 495 595 443T548 290V0H479V313Q479 401 437 448T320 495Q241 495 194 443T147 290V0H76V546H147V416Q167 485 219 520T341 556Q413
556 465 518T537 406Q559 479 614 517T739 556Q833 556 891 496Z" />
<glyph unicode="n" glyph-name="n" horiz-adv-x="635" d="M444 556T503 496T563 318V0H494V313Q494 402 449 449T326 497Q244 497 196 445T147 290V0H76V546H147V412Q168 482 223 519T349 556Q444 556 503 496Z" />
<glyph unicode="o" glyph-name="o" horiz-adv-x="644" d="M401 553T462 520T559 423T594 273Q594 187 559 124T463 27T323 -7Q245 -7 183 27T86 124T50 273Q50 359 85 422T183 519T323 553Q401 553 462 520ZM269 492T223 468T149 395T121 273Q121 201 149 152T223
79T323 55Q376 55 421 79T495 152T523 273Q523 345 495 394T422 468T323 492Q269 492 223 468Z" />
<glyph unicode="p" glyph-name="p" horiz-adv-x="673" d="M443 553T500 520T590 423T623 273Q623 188 591 125T500 27T368 -7Q284 -7 226 36T146 150V-258H76V546H146V396Q168 467 226 510T368 553Q443 553 500 520ZM291 491T245 464T172 388T146 273Q146 208
172 159T244 82T349 55Q442 55 497 113T552 273Q552 374 497 432T349 491Q291 491 245 464Z" />
<glyph unicode="q" glyph-name="q" horiz-adv-x="673" d="M391 552T447 512T528 403V546H597V-258H528V146Q504 76 447 35T310 -7Q235 -7 176 27T84 125T50 272Q50 356 83 419T176 517T310 552Q391 552 447 512ZM233 491T177 433T121 272Q121 170 177 113T324
55Q381 55 427 81T501 157T528 273Q528 339 501 388T428 464T324 491Q233 491 177 433Z" />
<glyph unicode="r" glyph-name="r" horiz-adv-x="370" d="M171 492T224 524T353 556V480H330Q251 480 199 438T147 299V0H76V546H147V433Q171 492 224 524Z" />
<glyph unicode="s" glyph-name="s" horiz-adv-x="507" d="M330 553T384 506T450 379H383Q377 432 340 466T239 501Q185 501 155 475T124 404Q124 371 143 351T192 321T270 299Q330 285 366 271T429 225T455 141Q455 76 407 35T278 -7Q184 -7 124 39T53 167H121Q127
113 168 79T279 45Q331 45 360 72T389 142Q389 177 369 198T320 229T239 252Q180 266 145 280T84 323T59 404Q59 471 108 512T241 553Q330 553 384 506Z" />
<glyph unicode="t" glyph-name="t" horiz-adv-x="363" d="M324 62V0H259Q180 0 141 37T102 164V485H19V546H102V684H173V546H323V485H173V163Q173 105 194 84T268 62H324Z" />
<glyph unicode="u" glyph-name="u" horiz-adv-x="624" d="M547 546V0H477V135Q456 64 401 27T275 -10Q179 -10 120 50T60 228V546H130V234Q130 144 175 97T298 49Q380 49 428 101T477 256V546H547Z" />
<glyph unicode="v" glyph-name="v" horiz-adv-x="554" d="M278 79L461 546H537L317 0H237L17 546H93L278 79Z" />
<glyph unicode="w" glyph-name="w" horiz-adv-x="828" d="M814 546L653 0H579L415 464L249 0H176L15 546H86L216 73L380 546H452L617 73L746 546H814Z" />
<glyph unicode="x" glyph-name="x" horiz-adv-x="479" d="M384 0L234 223L95 0H18L196 281L18 546H98L244 329L381 546H458L281 273L464 0H384Z" />
<glyph unicode="y" glyph-name="y" horiz-adv-x="547" d="M91 546L281 91L456 546H534L213 -258H133L243 6L13 546H91Z" />
<glyph unicode="z" glyph-name="z" horiz-adv-x="435" d="M104 60H407V0H28V57L318 487H36V546H397V489L104 60Z" />
<glyph unicode="{" glyph-name="braceleft" horiz-adv-x="348" d="M160 359T189 317T218 217Q218 182 207 131Q200 94 197 70T194 14Q194 -67 231 -125T317 -210V-217H250Q227 -204 200 -175T150 -97T128 19Q128 65 142 131Q153 186 153 218Q153 264 132 300T44
354V390Q110 407 131 443T153 526Q153 558 142 613Q128 679 128 725Q128 791 150 840T199 919T250 961H317V954Q269 927 232 869T194 730Q194 698 197 674T207 614Q218 563 218 528Q218 469 189 428T101 373V371Q160 359 189 317Z" />
<glyph unicode="|" glyph-name="bar" horiz-adv-x="208" d="M139 -101H69V791H139V-101Z" />
<glyph unicode="}" glyph-name="braceright" horiz-adv-x="350" d="M196 386T167 426T138 523Q138 558 149 609Q156 644 159 668T162 725Q162 779 144 825T97 905T39 954V961H106Q129 948 156 919T206 841T228 725Q228 679 214 613Q203 558 203 526Q203 480 225
443T312 390V354Q247 339 225 302T203 218Q203 186 214 131Q228 65 228 19Q228 -47 206 -96T157 -175T106 -217H39V-210Q69 -194 97 -161T143 -82T162 19Q162 51 159 75T149 136Q138 187 138 222Q138 278 167 318T255 371V373Q196 386 167 426Z" />
<glyph unicode="~" glyph-name="asciitilde" horiz-adv-x="487" d="M187 387T207 378T256 351Q281 335 297 328T331 320Q356 320 373 338T395 387H452Q441 323 409 289T326 255Q301 255 281 264T232 291Q207 307 191 314T156 322Q130 322 114 304T91 255H35Q46
319 78 353T161 387Q187 387 207 378Z" />
<glyph unicode="&#xa0;" glyph-name="uni00A0" horiz-adv-x="260" />
<glyph unicode="&#xa1;" glyph-name="exclamdown" horiz-adv-x="284" d="M179 -161L168 360H112L101 -161H179ZM188 466V556H95V466H188Z" />
<glyph unicode="&#xa2;" glyph-name="cent" horiz-adv-x="586" d="M366 73T413 110T470 211H536Q525 125 465 72T313 15V-91H275V16Q173 24 112 90T50 266Q50 376 111 441T275 515V615H313V516Q404 512 464 460T536 321H470Q459 385 413 422T300 459Q250 459 209
438T143 373T118 266Q118 172 170 123T300 73Q366 73 413 110Z" />
<glyph unicode="&#xa3;" glyph-name="sterling" horiz-adv-x="575" d="M245 184T226 141T166 56H532V-2H99L89 39Q135 88 157 129T179 229Q179 263 163 315H45V364H146Q129 410 120 449T110 530Q110 587 135 631T207 701T315 726Q411 726 469 669T536 524H470Q463
587 422 627T317 668Q257 668 218 631T178 524Q178 487 187 455T216 364H403V315H232Q245 271 245 232Q245 184 226 141Z" />
<glyph unicode="&#xa4;" glyph-name="currency" horiz-adv-x="527" d="M434 312T397 265L456 207L426 177L367 236Q324 202 264 202Q201 202 158 236L100 177L69 207L128 266Q94 313 94 378Q94 444 130 491L69 550L100 580L160 520Q204 552 264 552Q322 552 366
520L426 580L456 550L396 491Q434 445 434 378Q434 312 397 265ZM310 247T343 282T376 377Q376 436 343 471T264 507Q215 507 184 472T152 377Q152 316 183 282T264 247Q310 247 343 282Z" />
<glyph unicode="&#xa5;" glyph-name="yen" horiz-adv-x="605" d="M392 373H540V323H365L340 275V240H540V189H340V0H270V189H70V240H270V275L244 323H70V373H218L41 705H123L305 341L487 705H567L392 373Z" />
<glyph unicode="&#xa6;" glyph-name="brokenbar" horiz-adv-x="251" d="M90 791H161V421H90V791ZM161 -101H90V271H161V-101Z" />
<glyph unicode="&#xa7;" glyph-name="section" horiz-adv-x="573" d="M496 74T496 -24Q496 -88 446 -128T307 -169Q213 -169 154 -122T85 8H154Q161 -47 198 -82T308 -117Q361 -117 393 -95T425 -27Q425 17 388 45T260 101Q158 130 104 170T50 286Q50 345 93 390T216
447Q77 500 77 598Q77 662 128 701T270 741Q363 741 420 694T489 564H419Q415 615 378 652T269 689Q214 689 181 667T148 601Q148 557 185 528T314 471Q415 442 469 402T523 286Q523 228 481 184T360 125Q496 74 496 -24ZM125 230T168 196T289 162Q340 162 376
179T430 224T449 286Q449 342 406 376T285 410Q210 410 168 374T125 286Q125 230 168 196Z" />
<glyph unicode="&#xa8;" glyph-name="dieresis" horiz-adv-x="344" d="M50 688T62 700T94 713Q112 713 125 701T138 669Q138 651 125 638T94 625Q75 625 63 638T50 669Q50 688 62 700ZM207 688T219 700T250 713Q269 713 281 701T294 669Q294 651 282 638T250 625Q232
625 220 638T207 669Q207 688 219 700Z" />
<glyph unicode="&#xa9;" glyph-name="copyright" horiz-adv-x="795" d="M297 707T219 662T97 537T53 354Q53 251 96 171T218 45T398 0Q499 0 577 45T699 170T743 354Q743 457 700 537T578 662T398 707Q297 707 219 662ZM492 677T563 636T672 521T710 354Q710 260
672 187T563 72T398 30Q303 30 232 71T123 186T85 354Q85 447 123 520T232 635T398 677Q492 677 563 636ZM475 587T532 545T607 426H545Q529 479 489 507T399 536Q357 536 321 515T264 453T242 353Q242 295 263 254T321 192T399 171Q449 171 489 199T545 281H607Q589
205 532 163T401 120Q341 120 292 147T213 227T184 354Q184 427 213 479T291 559T401 587Q475 587 532 545Z" />
<glyph unicode="&#xaa;" glyph-name="ordfeminine" horiz-adv-x="445" d="M251 710T287 685T338 615V704H394V354H338V443Q323 398 287 373T198 348Q125 348 80 397T35 529Q35 611 80 660T198 710Q251 710 287 685ZM160 664T126 627T92 528Q92 467 126 431T212
394Q268 394 303 430T338 529Q338 591 303 627T212 664Q160 664 126 627Z" />
<glyph unicode="&#xab;" glyph-name="guillemotleft" horiz-adv-x="435" d="M145 120L45 295L145 470H215L114 295L215 120H145ZM317 120L218 295L317 470H388L286 295L388 120H317Z" />
<glyph unicode="&#xac;" glyph-name="logicalnot" horiz-adv-x="730" d="M680 430V230H611V374H50V430H680Z" />
<glyph unicode="&#xad;" glyph-name="uni00AD" horiz-adv-x="477" d="M625 400V341H235V400H625Z" />
<glyph unicode="&#xae;" glyph-name="registered" horiz-adv-x="795" d="M500 707T578 663T699 539T743 354Q743 251 700 171T578 45T398 -1Q297 -1 219 44T97 170T53 354Q53 459 96 539T218 663T398 707Q500 707 578 663ZM493 36T563 75T672 187T710 354Q710
451 672 524T564 638T398 678Q303 678 232 638T123 525T85 354Q85 259 123 187T232 76T398 36Q493 36 563 75ZM571 411T538 378T440 340L571 143H517L389 339H313V143H265V591H417Q494 591 532 557T571 466Q571 411 538 378ZM521 374T521 461Q521 503 496 525T413
547H313V374H413Q521 374 521 461Z" />
<glyph unicode="&#xaf;" glyph-name="overscore" horiz-adv-x="405" d="M372 700V650H33V700H372Z" />
<glyph unicode="&#xb0;" glyph-name="degree" horiz-adv-x="391" d="M271 697T317 650T363 523Q363 443 318 395T197 347Q121 347 75 395T28 523Q28 602 74 649T196 697Q271 697 317 650ZM146 655T116 620T86 522Q86 460 116 425T197 390Q246 390 276 425T306
522Q306 585 276 620T196 655Q146 655 116 620Z" />
<glyph unicode="&#xb1;" glyph-name="plusminus" horiz-adv-x="580" d="M545 443V379H324V160H255V379H35V443H255V634H324V443H545ZM35 156H545V92H35V156Z" />
<glyph unicode="&#xb2;" glyph-name="twosuperior" horiz-adv-x="331" d="M294 397V352H42V390L161 492Q194 521 211 547T229 606Q229 637 213 652T169 668Q137 668 119 649T96 591H40Q44 642 78 676T174 710Q228 710 258 683T288 610Q288 568 263 535T186 464L102
397H294Z" />
<glyph unicode="&#xb3;" glyph-name="threesuperior" horiz-adv-x="327" d="M222 710T256 683T290 611Q290 580 273 559T221 530V527Q256 518 274 497T293 442Q293 402 260 375T167 348Q104 348 72 374T33 454H91Q95 418 114 404T164 389Q198 389 216 405T235
450Q235 481 214 497T158 514H105V541H158Q192 541 211 558T231 605Q231 633 213 650T164 667Q102 667 90 609H36Q42 653 74 681T169 710Q222 710 256 683Z" />
<glyph unicode="&#xb4;" glyph-name="acute" horiz-adv-x="270" d="M238 711L33 590V642L238 770V711Z" />
<glyph unicode="&#xb5;" glyph-name="uni00B5" horiz-adv-x="652" d="M570 546V0H499V130Q479 62 427 26T308 -10Q257 -10 216 12T152 85V-258H83V546H152V264Q152 160 200 106T329 52Q407 52 453 104T499 256V546H570Z" />
<glyph unicode="&#xb6;" glyph-name="paragraph" horiz-adv-x="561" d="M479 0H420V635H324V0H265V289H257Q140 289 81 344T21 489Q21 579 81 634T257 690H479V0Z" />
<glyph unicode="&#xb7;" glyph-name="middot" horiz-adv-x="162" d="M127 390V300H34V390H127Z" />
<glyph unicode="&#xb8;" glyph-name="cedilla" horiz-adv-x="273" d="M171 -71T205 -93T240 -163Q240 -208 208 -233T123 -258H33V-215H115Q188 -215 188 -162Q188 -139 171 -126T115 -112H66V7H113V-74Q171 -71 205 -93Z" />
<glyph unicode="&#xb9;" glyph-name="onesuperior" horiz-adv-x="187" d="M35 659V704H148V352H92V659H35Z" />
<glyph unicode="&#xba;" glyph-name="ordmasculine" horiz-adv-x="427" d="M266 709T307 686T370 622T393 527Q393 474 371 433T307 368T214 344Q162 344 122 367T58 432T35 527Q35 580 58 621T121 686T214 709Q266 709 307 686ZM161 663T127 627T92 527Q92 463
126 427T214 391Q267 391 301 427T336 527Q336 591 302 627T214 663Q161 663 127 627Z" />
<glyph unicode="&#xbb;" glyph-name="guillemotright" horiz-adv-x="435" d="M153 295L50 470H122L220 295L122 120H50L153 295ZM325 295L223 470H294L393 295L294 120H223L325 295Z" />
<glyph unicode="&#xbc;" glyph-name="onequarter" horiz-adv-x="604" d="M497 705L140 0H70L428 705H497ZM56 659V704H169V352H113V659H56ZM568 72H518V0H462V72H282V106L450 354H518V109H568V72ZM464 294L339 110H464V294Z" />
<glyph unicode="&#xbd;" glyph-name="onehalf" horiz-adv-x="677" d="M487 706L127 0H58L418 706H487ZM116 659H59V704H172V352H116V659ZM627 44V-1H375V37L494 139Q527 168 544 194T562 253Q562 284 546 299T502 315Q470 315 452 296T429 238H373Q377 289 411
323T507 357Q561 357 591 330T621 257Q621 215 596 182T519 111L435 44H627Z" />
<glyph unicode="&#xbe;" glyph-name="threequarters" horiz-adv-x="655" d="M248 710T282 683T316 611Q316 580 299 559T247 530V527Q282 518 300 497T319 442Q319 402 286 375T193 348Q130 348 98 374T59 454H117Q121 418 140 404T190 389Q224 389 242 405T261
450Q261 481 240 497T184 514H131V541H184Q218 541 237 558T257 605Q257 633 239 650T190 667Q128 667 116 609H62Q68 653 100 681T195 710Q248 710 282 683ZM568 706L215 0H146L499 706H568ZM621 72H571V0H515V72H335V106L498 354H571V109H621V72ZM518 299L392
109H518V299Z" />
<glyph unicode="&#xbf;" glyph-name="questiondown" horiz-adv-x="481" d="M159 -181T102 -128T44 19Q44 128 110 180T293 234V360H360V185H327Q231 185 172 150T113 19Q113 -47 152 -84T257 -122Q326 -122 368 -84T410 17H477Q478 -42 450 -87T371 -156T255 -181Q159
-181 102 -128ZM280 456V546H372V456H280Z" />
<glyph unicode="&#xc0;" glyph-name="Agrave" horiz-adv-x="644" d="M483 172H162L98 0H23L283 689H362L622 0H546L483 172ZM462 228L323 608L182 228H462ZM350 801V749L145 870V929L350 801Z" />
<glyph unicode="&#xc1;" glyph-name="Aacute" horiz-adv-x="644" d="M483 172H162L98 0H23L283 689H362L622 0H546L483 172ZM462 228L323 608L182 228H462ZM493 870L288 749V801L493 929V870Z" />
<glyph unicode="&#xc2;" glyph-name="Acircumflex" horiz-adv-x="644" d="M483 172H162L98 0H23L283 689H362L622 0H546L483 172ZM462 228L323 608L182 228H462ZM323 855L189 772V823L323 905L457 823V772L323 855Z" />
<glyph unicode="&#xc3;" glyph-name="Atilde" horiz-adv-x="644" d="M483 172H162L98 0H23L283 689H362L622 0H546L483 172ZM462 228L323 608L182 228H462ZM282 859T296 852T327 833Q342 822 354 816T380 810Q397 810 409 822T424 857H465Q460 813 437 789T378
764Q359 764 344 771T311 791Q294 803 284 808T261 813Q225 813 219 765H178Q185 811 206 835T263 859Q282 859 296 852Z" />
<glyph unicode="&#xc4;" glyph-name="Adieresis" horiz-adv-x="644" d="M483 172H162L98 0H23L283 689H362L622 0H546L483 172ZM462 228L323 608L182 228H462ZM200 847T212 859T244 872Q262 872 275 860T288 828Q288 810 275 797T244 784Q225 784 213 797T200
828Q200 847 212 859ZM357 847T369 859T400 872Q419 872 431 860T444 828Q444 810 432 797T400 784Q382 784 370 797T357 828Q357 847 369 859Z" />
<glyph unicode="&#xc5;" glyph-name="Aring" horiz-adv-x="644" d="M483 172H162L98 0H23L283 689H362L622 0H546L483 172ZM462 228L323 608L182 228H462ZM365 970T394 943T423 869Q423 824 394 797T322 769Q279 769 251 796T222 869Q222 915 250 942T322 970Q365
970 394 943ZM294 935T276 917T258 869Q258 839 276 821T322 803Q350 803 368 821T386 869Q386 899 368 917T322 935Q294 935 276 917Z" />
<glyph unicode="&#xc6;" glyph-name="AE" horiz-adv-x="874" d="M525 648V383H798V327H525V58H828V0H455V170H184L85 0H8L417 705H828V648H525ZM455 225V635L216 225H455Z" />
<glyph unicode="&#xc7;" glyph-name="Ccedilla" horiz-adv-x="765" d="M513 712T596 651T713 481H639Q611 557 546 602T385 647Q312 647 253 611T159 508T125 352Q125 263 159 197T252 94T385 58Q481 58 546 103T639 224H713Q679 116 596 55T386 -7Q289 -7 213
39T95 166T53 352Q53 457 95 539T213 666T386 712Q513 712 596 651ZM465 -71T499 -93T534 -163Q534 -208 502 -233T417 -258H327V-215H409Q482 -215 482 -162Q482 -139 465 -126T409 -112H360V7H407V-74Q465 -71 499 -93Z" />
<glyph unicode="&#xc8;" glyph-name="Egrave" horiz-adv-x="498" d="M147 648V382H422V327H147V58H452V0H76V705H452V648H147ZM293 801V749L88 870V929L293 801Z" />
<glyph unicode="&#xc9;" glyph-name="Eacute" horiz-adv-x="498" d="M147 648V382H422V327H147V58H452V0H76V705H452V648H147ZM435 870L230 749V801L435 929V870Z" />
<glyph unicode="&#xca;" glyph-name="Ecircumflex" horiz-adv-x="498" d="M147 648V382H422V327H147V58H452V0H76V705H452V648H147ZM265 855L131 772V823L265 905L399 823V772L265 855Z" />
<glyph unicode="&#xcb;" glyph-name="Edieresis" horiz-adv-x="498" d="M147 648V382H422V327H147V58H452V0H76V705H452V648H147ZM143 847T155 859T187 872Q205 872 218 860T231 828Q231 810 218 797T187 784Q168 784 156 797T143 828Q143 847 155 859ZM300 847T312
859T343 872Q362 872 374 860T387 828Q387 810 375 797T343 784Q325 784 313 797T300 828Q300 847 312 859Z" />
<glyph unicode="&#xcc;" glyph-name="Igrave" horiz-adv-x="223" d="M147 705V0H76V705H147ZM140 801V749L-65 870V929L140 801Z" />
<glyph unicode="&#xcd;" glyph-name="Iacute" horiz-adv-x="223" d="M147 705V0H76V705H147ZM283 870L78 749V801L283 929V870Z" />
<glyph unicode="&#xce;" glyph-name="Icircumflex" horiz-adv-x="223" d="M147 705V0H76V705H147ZM112 855L-22 772V823L112 905L246 823V772L112 855Z" />
<glyph unicode="&#xcf;" glyph-name="Idieresis" horiz-adv-x="223" d="M147 705V0H76V705H147ZM-10 847T2 859T34 872Q52 872 65 860T78 828Q78 810 65 797T34 784Q15 784 3 797T-10 828Q-10 847 2 859ZM147 847T159 859T190 872Q209 872 221 860T234 828Q234
810 222 797T190 784Q172 784 160 797T147 828Q147 847 159 859Z" />
<glyph unicode="&#xd0;" glyph-name="Eth" horiz-adv-x="742" d="M497 705T593 612T690 353Q690 244 647 165T521 43T319 0H106V324H8V382H106V705H319Q497 705 593 612ZM463 63T541 139T619 353Q619 490 541 566T314 643H177V382H384V324H177V63H314Q463 63 541 139Z" />
<glyph unicode="&#xd1;" glyph-name="Ntilde" horiz-adv-x="680" d="M604 0H533L147 589V0H76V704H147L533 114V704H604V0ZM300 859T314 852T345 833Q360 822 372 816T398 810Q415 810 427 822T442 857H483Q478 813 455 789T396 764Q377 764 362 771T329 791Q312
803 302 808T279 813Q243 813 237 765H196Q203 811 224 835T281 859Q300 859 314 852Z" />
<glyph unicode="&#xd2;" glyph-name="Ograve" horiz-adv-x="791" d="M494 713T572 667T694 539T739 353Q739 249 695 167T572 38T396 -8Q298 -8 220 38T98 166T53 353Q53 457 97 539T220 667T396 713Q494 713 572 667ZM319 648T258 612T161 509T125 353Q125 264
160 197T257 94T396 58Q473 58 534 94T631 197T667 353Q667 442 632 509T535 612T396 648Q319 648 258 612ZM424 801V749L219 870V929L424 801Z" />
<glyph unicode="&#xd3;" glyph-name="Oacute" horiz-adv-x="791" d="M494 713T572 667T694 539T739 353Q739 249 695 167T572 38T396 -8Q298 -8 220 38T98 166T53 353Q53 457 97 539T220 667T396 713Q494 713 572 667ZM319 648T258 612T161 509T125 353Q125 264
160 197T257 94T396 58Q473 58 534 94T631 197T667 353Q667 442 632 509T535 612T396 648Q319 648 258 612ZM567 870L362 749V801L567 929V870Z" />
<glyph unicode="&#xd4;" glyph-name="Ocircumflex" horiz-adv-x="791" d="M494 713T572 667T694 539T739 353Q739 249 695 167T572 38T396 -8Q298 -8 220 38T98 166T53 353Q53 457 97 539T220 667T396 713Q494 713 572 667ZM319 648T258 612T161 509T125 353Q125
264 160 197T257 94T396 58Q473 58 534 94T631 197T667 353Q667 442 632 509T535 612T396 648Q319 648 258 612ZM397 855L263 772V823L397 905L531 823V772L397 855Z" />
<glyph unicode="&#xd5;" glyph-name="Otilde" horiz-adv-x="791" d="M494 713T572 667T694 539T739 353Q739 249 695 167T572 38T396 -8Q298 -8 220 38T98 166T53 353Q53 457 97 539T220 667T396 713Q494 713 572 667ZM319 648T258 612T161 509T125 353Q125 264
160 197T257 94T396 58Q473 58 534 94T631 197T667 353Q667 442 632 509T535 612T396 648Q319 648 258 612ZM356 859T370 852T401 833Q416 822 428 816T454 810Q471 810 483 822T498 857H539Q534 813 511 789T452 764Q433 764 418 771T385 791Q368 803 358 808T335
813Q299 813 293 765H252Q259 811 280 835T337 859Q356 859 370 852Z" />
<glyph unicode="&#xd6;" glyph-name="Odieresis" horiz-adv-x="791" d="M494 713T572 667T694 539T739 353Q739 249 695 167T572 38T396 -8Q298 -8 220 38T98 166T53 353Q53 457 97 539T220 667T396 713Q494 713 572 667ZM319 648T258 612T161 509T125 353Q125
264 160 197T257 94T396 58Q473 58 534 94T631 197T667 353Q667 442 632 509T535 612T396 648Q319 648 258 612ZM274 847T286 859T318 872Q336 872 349 860T362 828Q362 810 349 797T318 784Q299 784 287 797T274 828Q274 847 286 859ZM431 847T443 859T474 872Q493
872 505 860T518 828Q518 810 506 797T474 784Q456 784 444 797T431 828Q431 847 443 859Z" />
<glyph unicode="&#xd7;" glyph-name="multiply" horiz-adv-x="497" d="M415 158L247 326L81 159L36 205L202 371L36 537L83 584L249 418L413 583L459 538L294 373L462 205L415 158Z" />
<glyph unicode="&#xd8;" glyph-name="Oslash" horiz-adv-x="791" d="M649 604Q692 556 715 492T739 353Q739 249 695 167T572 38T396 -8Q329 -8 271 14T168 76L98 0H50L143 102Q100 150 77 214T53 353Q53 457 97 539T220 667T396 713Q462 713 520 691T624 629L694
706H741L649 604ZM125 232T189 152L578 579Q543 613 496 630T396 648Q319 648 258 612T161 509T125 353Q125 232 189 152ZM667 475T602 554L213 127Q287 58 396 58Q473 58 534 94T631 197T667 353Q667 475 602 554Z" />
<glyph unicode="&#xd9;" glyph-name="Ugrave" horiz-adv-x="652" d="M142 705V278Q142 165 191 112T327 59Q413 59 461 112T510 278V705H580V278Q580 135 511 64T326 -7Q210 -7 141 64T72 278V705H142ZM354 801V749L149 870V929L354 801Z" />
<glyph unicode="&#xda;" glyph-name="Uacute" horiz-adv-x="652" d="M142 705V278Q142 165 191 112T327 59Q413 59 461 112T510 278V705H580V278Q580 135 511 64T326 -7Q210 -7 141 64T72 278V705H142ZM496 870L291 749V801L496 929V870Z" />
<glyph unicode="&#xdb;" glyph-name="Ucircumflex" horiz-adv-x="652" d="M142 705V278Q142 165 191 112T327 59Q413 59 461 112T510 278V705H580V278Q580 135 511 64T326 -7Q210 -7 141 64T72 278V705H142ZM326 855L192 772V823L326 905L460 823V772L326 855Z" />
<glyph unicode="&#xdc;" glyph-name="Udieresis" horiz-adv-x="652" d="M142 705V278Q142 165 191 112T327 59Q413 59 461 112T510 278V705H580V278Q580 135 511 64T326 -7Q210 -7 141 64T72 278V705H142ZM204 847T216 859T248 872Q266 872 279 860T292 828Q292
810 279 797T248 784Q229 784 217 797T204 828Q204 847 216 859ZM361 847T373 859T404 872Q423 872 435 860T448 828Q448 810 436 797T404 784Q386 784 374 797T361 828Q361 847 373 859Z" />
<glyph unicode="&#xdd;" glyph-name="Yacute" horiz-adv-x="571" d="M549 705L322 275V0H251V275L23 705H104L286 350L468 705H549ZM457 870L252 749V801L457 929V870Z" />
<glyph unicode="&#xde;" glyph-name="Thorn" horiz-adv-x="558" d="M147 151V0H76V705H146V557H287Q406 557 464 503T523 353Q523 256 463 204T287 151H147ZM372 208T412 245T452 353Q452 425 412 462T281 500H147V208H281Q372 208 412 245Z" />
<glyph unicode="&#xdf;" glyph-name="germandbls" horiz-adv-x="634" d="M540 318T571 271T602 158Q602 110 579 72T514 11T418 -11Q330 -11 280 38T228 176H288Q290 120 321 86T412 51Q471 51 502 84T533 163Q533 206 512 240T437 310L278 411V445L314 464Q350
482 366 507T383 569Q383 621 352 653T270 686Q212 686 179 653T146 560L147 0H76V548Q76 646 129 698T277 750Q356 750 405 706T455 590Q455 542 431 508T353 449L341 444L468 363Q540 318 571 271Z" />
<glyph unicode="&#xe0;" glyph-name="agrave" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176 114T324
55Q382 55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433ZM352 642V590L147 711V770L352 642Z" />
<glyph unicode="&#xe1;" glyph-name="aacute" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176 114T324
55Q382 55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433ZM495 711L290 590V642L495 770V711Z" />
<glyph unicode="&#xe2;" glyph-name="acircumflex" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172
176 114T324 55Q382 55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433ZM325 696L191 613V664L325 746L459 664V613L325 696Z" />
<glyph unicode="&#xe3;" glyph-name="atilde" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176 114T324
55Q382 55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433ZM284 700T298 693T329 674Q344 663 356 657T382 651Q399 651 411 663T426 698H467Q462 654 439 630T380 605Q361 605 346 612T313 632Q296 644 286 649T263 654Q227 654 221
606H180Q187 652 208 676T265 700Q284 700 298 693Z" />
<glyph unicode="&#xe4;" glyph-name="adieresis" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176
114T324 55Q382 55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433ZM202 688T214 700T246 713Q264 713 277 701T290 669Q290 651 277 638T246 625Q227 625 215 638T202 669Q202 688 214 700ZM359 688T371 700T402 713Q421 713 433 701T446
669Q446 651 434 638T402 625Q384 625 372 638T359 669Q359 688 371 700Z" />
<glyph unicode="&#xe5;" glyph-name="aring" horiz-adv-x="673" d="M389 553T447 510T528 396V546H597V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510ZM232 491T177 433T121 273Q121 172 176 114T324
55Q382 55 428 82T501 158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433ZM367 811T396 784T425 710Q425 665 396 638T324 610Q281 610 253 637T224 710Q224 756 252 783T324 811Q367 811 396 784ZM296 776T278 758T260 710Q260 680 278 662T324 644Q352
644 370 662T388 710Q388 740 370 758T324 776Q296 776 278 758Z" />
<glyph unicode="&#xe6;" glyph-name="ae" horiz-adv-x="1115" d="M875 50T926 87T988 188H1062Q1047 101 983 47T819 -7Q738 -7 678 34T597 151V0H528V150Q506 79 448 36T306 -7Q232 -7 174 27T83 124T50 273Q50 359 82 422T173 519T306 553Q389 553 447 510T528
396V546H597V396Q617 471 677 512T819 553Q896 553 951 520T1036 430T1065 308Q1065 280 1061 257H599Q601 188 629 142T702 73T800 50Q875 50 926 87ZM748 496T703 475T630 407T599 293H995Q999 360 973 406T901 474T801 496Q748 496 703 475ZM382 55T428 82T501
158T528 273Q528 338 502 387T429 464T324 491Q232 491 177 433T121 273Q121 172 176 114T324 55Q382 55 428 82Z" />
<glyph unicode="&#xe7;" glyph-name="ccedilla" horiz-adv-x="623" d="M421 553T489 498T573 352H500Q489 417 439 454T316 492Q263 492 219 468T148 395T121 273Q121 201 147 152T218 78T316 54Q389 54 439 91T500 194H573Q558 103 490 48T317 -7Q239 -7 179
26T84 123T50 273Q50 359 84 422T178 519T317 553Q421 553 489 498ZM384 -71T418 -93T453 -163Q453 -208 421 -233T336 -258H246V-215H328Q401 -215 401 -162Q401 -139 384 -126T328 -112H279V7H326V-74Q384 -71 418 -93Z" />
<glyph unicode="&#xe8;" glyph-name="egrave" horiz-adv-x="625" d="M388 51T438 88T498 188H572Q557 101 489 47T317 -7Q239 -7 179 26T84 123T50 273Q50 359 84 422T178 519T317 553Q397 553 455 520T544 430T575 308Q575 280 571 257H119Q120 188 147 142T219
73T314 51Q388 51 438 88ZM264 496T220 474T149 407T119 293H505Q509 360 483 406T413 474T317 496Q264 496 220 474ZM341 642V590L136 711V770L341 642Z" />
<glyph unicode="&#xe9;" glyph-name="eacute" horiz-adv-x="625" d="M388 51T438 88T498 188H572Q557 101 489 47T317 -7Q239 -7 179 26T84 123T50 273Q50 359 84 422T178 519T317 553Q397 553 455 520T544 430T575 308Q575 280 571 257H119Q120 188 147 142T219
73T314 51Q388 51 438 88ZM264 496T220 474T149 407T119 293H505Q509 360 483 406T413 474T317 496Q264 496 220 474ZM484 711L279 590V642L484 770V711Z" />
<glyph unicode="&#xea;" glyph-name="ecircumflex" horiz-adv-x="625" d="M388 51T438 88T498 188H572Q557 101 489 47T317 -7Q239 -7 179 26T84 123T50 273Q50 359 84 422T178 519T317 553Q397 553 455 520T544 430T575 308Q575 280 571 257H119Q120 188 147
142T219 73T314 51Q388 51 438 88ZM264 496T220 474T149 407T119 293H505Q509 360 483 406T413 474T317 496Q264 496 220 474ZM314 696L180 613V664L314 746L448 664V613L314 696Z" />
<glyph unicode="&#xeb;" glyph-name="edieresis" horiz-adv-x="625" d="M388 51T438 88T498 188H572Q557 101 489 47T317 -7Q239 -7 179 26T84 123T50 273Q50 359 84 422T178 519T317 553Q397 553 455 520T544 430T575 308Q575 280 571 257H119Q120 188 147 142T219
73T314 51Q388 51 438 88ZM264 496T220 474T149 407T119 293H505Q509 360 483 406T413 474T317 496Q264 496 220 474ZM191 688T203 700T235 713Q253 713 266 701T279 669Q279 651 266 638T235 625Q216 625 204 638T191 669Q191 688 203 700ZM348 688T360 700T391
713Q410 713 422 701T435 669Q435 651 423 638T391 625Q373 625 361 638T348 669Q348 688 360 700Z" />
<glyph unicode="&#xec;" glyph-name="igrave" horiz-adv-x="236" d="M153 546V0H83V546H153ZM402 642V590L197 711V770L402 642Z" />
<glyph unicode="&#xed;" glyph-name="iacute" horiz-adv-x="236" d="M153 546V0H83V546H153ZM289 711L84 590V642L289 770V711Z" />
<glyph unicode="&#xee;" glyph-name="icircumflex" horiz-adv-x="236" d="M153 546V0H83V546H153ZM375 696L241 613V664L375 746L509 664V613L375 696Z" />
<glyph unicode="&#xef;" glyph-name="idieresis" horiz-adv-x="236" d="M153 546V0H83V546H153ZM252 688T264 700T296 713Q314 713 327 701T340 669Q340 651 327 638T296 625Q277 625 265 638T252 669Q252 688 264 700ZM409 688T421 700T452 713Q471 713 483 701T496
669Q496 651 484 638T452 625Q434 625 422 638T409 669Q409 688 421 700Z" />
<glyph unicode="&#xf0;" glyph-name="eth" horiz-adv-x="645" d="M595 514T595 310Q595 207 561 136T465 29T324 -7Q243 -7 181 29T85 132T50 286Q50 367 83 428T178 522T318 556Q389 556 444 518T523 403Q509 478 480 538T397 655L255 606V638L373 679Q350 702
303 740H368Q404 710 420 695L550 740V708L444 671Q595 514 595 310ZM380 55T425 80T497 155T523 273Q523 343 496 393T424 469T323 495Q266 495 220 471T148 399T122 285Q122 213 148 161T220 82T324 55Q380 55 425 80Z" />
<glyph unicode="&#xf1;" glyph-name="ntilde" horiz-adv-x="635" d="M444 556T503 496T563 318V0H494V313Q494 402 449 449T326 497Q244 497 196 445T147 290V0H76V546H147V412Q168 482 223 519T349 556Q444 556 503 496ZM280 700T294 693T325 674Q340 663 352
657T378 651Q395 651 407 663T422 698H463Q458 654 435 630T376 605Q357 605 342 612T309 632Q292 644 282 649T259 654Q223 654 217 606H176Q183 652 204 676T261 700Q280 700 294 693Z" />
<glyph unicode="&#xf2;" glyph-name="ograve" horiz-adv-x="644" d="M401 553T462 520T559 423T594 273Q594 187 559 124T463 27T323 -7Q245 -7 183 27T86 124T50 273Q50 359 85 422T183 519T323 553Q401 553 462 520ZM269 492T223 468T149 395T121 273Q121 201
149 152T223 79T323 55Q376 55 421 79T495 152T523 273Q523 345 495 394T422 468T323 492Q269 492 223 468ZM350 642V590L145 711V770L350 642Z" />
<glyph unicode="&#xf3;" glyph-name="oacute" horiz-adv-x="644" d="M401 553T462 520T559 423T594 273Q594 187 559 124T463 27T323 -7Q245 -7 183 27T86 124T50 273Q50 359 85 422T183 519T323 553Q401 553 462 520ZM269 492T223 468T149 395T121 273Q121 201
149 152T223 79T323 55Q376 55 421 79T495 152T523 273Q523 345 495 394T422 468T323 492Q269 492 223 468ZM493 711L288 590V642L493 770V711Z" />
<glyph unicode="&#xf4;" glyph-name="ocircumflex" horiz-adv-x="644" d="M401 553T462 520T559 423T594 273Q594 187 559 124T463 27T323 -7Q245 -7 183 27T86 124T50 273Q50 359 85 422T183 519T323 553Q401 553 462 520ZM269 492T223 468T149 395T121 273Q121
201 149 152T223 79T323 55Q376 55 421 79T495 152T523 273Q523 345 495 394T422 468T323 492Q269 492 223 468ZM323 696L189 613V664L323 746L457 664V613L323 696Z" />
<glyph unicode="&#xf5;" glyph-name="otilde" horiz-adv-x="644" d="M401 553T462 520T559 423T594 273Q594 187 559 124T463 27T323 -7Q245 -7 183 27T86 124T50 273Q50 359 85 422T183 519T323 553Q401 553 462 520ZM269 492T223 468T149 395T121 273Q121 201
149 152T223 79T323 55Q376 55 421 79T495 152T523 273Q523 345 495 394T422 468T323 492Q269 492 223 468ZM282 700T296 693T327 674Q342 663 354 657T380 651Q397 651 409 663T424 698H465Q460 654 437 630T378 605Q359 605 344 612T311 632Q294 644 284 649T261
654Q225 654 219 606H178Q185 652 206 676T263 700Q282 700 296 693Z" />
<glyph unicode="&#xf6;" glyph-name="odieresis" horiz-adv-x="644" d="M401 553T462 520T559 423T594 273Q594 187 559 124T463 27T323 -7Q245 -7 183 27T86 124T50 273Q50 359 85 422T183 519T323 553Q401 553 462 520ZM269 492T223 468T149 395T121 273Q121
201 149 152T223 79T323 55Q376 55 421 79T495 152T523 273Q523 345 495 394T422 468T323 492Q269 492 223 468ZM201 688T213 700T245 713Q263 713 276 701T289 669Q289 651 276 638T245 625Q226 625 214 638T201 669Q201 688 213 700ZM358 688T370 700T401 713Q420
713 432 701T445 669Q445 651 433 638T401 625Q383 625 371 638T358 669Q358 688 370 700Z" />
<glyph unicode="&#xf7;" glyph-name="divide" horiz-adv-x="580" d="M336 634V544H244V634H336ZM545 402V338H35V402H545ZM336 199V109H244V199H336Z" />
<glyph unicode="&#xf8;" glyph-name="oslash" horiz-adv-x="641" d="M592 394T592 273Q592 187 557 124T461 27T321 -7Q216 -7 142 53L94 0H52L120 74Q86 111 67 161T48 273Q48 359 83 422T181 519T321 553Q428 553 500 492L549 546H591L523 471Q592 394 592 273ZM119
183T165 125L455 443Q428 467 394 479T321 492Q267 492 221 468T147 395T119 273Q119 183 165 125ZM374 55T419 79T493 152T521 273Q521 364 476 420L187 102Q241 55 321 55Q374 55 419 79Z" />
<glyph unicode="&#xf9;" glyph-name="ugrave" horiz-adv-x="624" d="M547 546V0H477V135Q456 64 401 27T275 -10Q179 -10 120 50T60 228V546H130V234Q130 144 175 97T298 49Q380 49 428 101T477 256V546H547ZM332 642V590L127 711V770L332 642Z" />
<glyph unicode="&#xfa;" glyph-name="uacute" horiz-adv-x="624" d="M547 546V0H477V135Q456 64 401 27T275 -10Q179 -10 120 50T60 228V546H130V234Q130 144 175 97T298 49Q380 49 428 101T477 256V546H547ZM474 711L269 590V642L474 770V711Z" />
<glyph unicode="&#xfb;" glyph-name="ucircumflex" horiz-adv-x="624" d="M547 546V0H477V135Q456 64 401 27T275 -10Q179 -10 120 50T60 228V546H130V234Q130 144 175 97T298 49Q380 49 428 101T477 256V546H547ZM304 696L170 613V664L304 746L438 664V613L304 696Z" />
<glyph unicode="&#xfc;" glyph-name="udieresis" horiz-adv-x="624" d="M547 546V0H477V135Q456 64 401 27T275 -10Q179 -10 120 50T60 228V546H130V234Q130 144 175 97T298 49Q380 49 428 101T477 256V546H547ZM182 688T194 700T226 713Q244 713 257 701T270
669Q270 651 257 638T226 625Q207 625 195 638T182 669Q182 688 194 700ZM339 688T351 700T382 713Q401 713 413 701T426 669Q426 651 414 638T382 625Q364 625 352 638T339 669Q339 688 351 700Z" />
<glyph unicode="&#xfd;" glyph-name="yacute" horiz-adv-x="547" d="M91 546L281 91L456 546H534L213 -258H133L243 6L13 546H91ZM444 711L239 590V642L444 770V711Z" />
<glyph unicode="&#xfe;" glyph-name="thorn" horiz-adv-x="673" d="M439 552T498 518T590 420T623 272Q623 188 590 125T498 28T364 -7Q283 -7 227 34T146 146V-258H76V740H146V403Q169 472 226 512T364 552Q439 552 498 518ZM292 491T246 465T173 389T146 273Q146
207 172 158T245 82T350 55Q441 55 496 112T552 272Q552 374 497 432T350 491Q292 491 246 465Z" />
<glyph unicode="&#xff;" glyph-name="ydieresis" horiz-adv-x="547" d="M91 546L281 91L456 546H534L213 -258H133L243 6L13 546H91ZM152 688T164 700T196 713Q214 713 227 701T240 669Q240 651 227 638T196 625Q177 625 165 638T152 669Q152 688 164 700ZM309
688T321 700T352 713Q371 713 383 701T396 669Q396 651 384 638T352 625Q334 625 322 638T309 669Q309 688 321 700Z" />
<glyph unicode="&#x2013;" glyph-name="endash" horiz-adv-x="607" d="M563 400V341H44V400H563Z" />
<glyph unicode="&#x2014;" glyph-name="emdash" horiz-adv-x="807" d="M763 400V341H44V400H763Z" />
<glyph unicode="&#x2018;" glyph-name="quoteleft" horiz-adv-x="162" d="M35 635T35 672Q35 774 127 774V740Q101 740 90 727T79 687V663H126V586H45Q35 635 35 672Z" />
<glyph unicode="&#x2019;" glyph-name="quoteright" horiz-adv-x="162" d="M127 725T127 689Q127 586 36 586V621Q61 621 72 634T83 673V698H37V774H118Q127 725 127 689Z" />
<glyph unicode="&#x201a;" glyph-name="quotesinglbase" horiz-adv-x="173" d="M125 22T125 -12Q125 -60 102 -88T33 -117V-84Q57 -84 68 -71T79 -29V-21H34V56H115Q125 22 125 -12Z" />
<glyph unicode="&#x201c;" glyph-name="quotedblleft" horiz-adv-x="283" d="M35 635T35 672Q35 774 127 774V740Q101 740 90 727T79 687V663H126V586H45Q35 635 35 672ZM156 630T156 672Q156 774 247 774V740Q221 740 211 727T200 687V663H246V586H165Q156 630 156 672Z" />
<glyph unicode="&#x201d;" glyph-name="quotedblright" horiz-adv-x="283" d="M127 725T127 689Q127 586 36 586V621Q61 621 72 634T83 673V698H37V774H118Q127 725 127 689ZM248 727T248 689Q248 586 156 586V621Q182 621 193 634T204 673V698H157V774H238Q248
727 248 689Z" />
<glyph unicode="&#x201e;" glyph-name="quotedblbase" horiz-adv-x="283" d="M127 27T127 -9Q127 -112 36 -112V-77Q61 -77 72 -64T83 -25V0H37V76H118Q127 27 127 -9ZM248 29T248 -9Q248 -112 156 -112V-77Q182 -77 193 -64T204 -25V0H157V76H238Q248 29 248 -9Z" />
<glyph unicode="&#x2022;" glyph-name="bullet" horiz-adv-x="326" d="M48 382T81 416T163 450Q212 450 245 416T279 333Q279 285 246 252T163 219Q115 219 82 252T48 333Q48 382 81 416Z" />
<glyph unicode="&#x2039;" glyph-name="guilsinglleft" horiz-adv-x="265" d="M145 120L45 295L145 470H215L114 295L215 120H145Z" />
<glyph unicode="&#x203a;" glyph-name="guilsinglright" horiz-adv-x="265" d="M153 295L50 470H122L220 295L122 120H50L153 295Z" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 51 KiB

Some files were not shown because too many files have changed in this diff Show more