mirror of
https://github.com/appwrite/appwrite
synced 2026-05-06 06:48:22 +00:00
Merge branch 'master' into fix-4594-function-response-500
This commit is contained in:
commit
6d85dd6d85
5097 changed files with 91468 additions and 56513 deletions
7
.env
7
.env
|
|
@ -76,11 +76,14 @@ _APP_MAINTENANCE_RETENTION_CACHE=2592000
|
|||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
|
||||
_APP_USAGE_TIMESERIES_INTERVAL=2
|
||||
_APP_USAGE_DATABASE_INTERVAL=15
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=5
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=3
|
||||
DOCKERHUB_PULL_USERNAME=
|
||||
DOCKERHUB_PULL_PASSWORD=
|
||||
DOCKERHUB_PULL_EMAIL=
|
||||
35
.gitattributes
vendored
35
.gitattributes
vendored
|
|
@ -1,28 +1,7 @@
|
|||
app/config/* linguist-detectable=false
|
||||
app/config/*/* linguist-detectable=false
|
||||
app/config/*/*/* linguist-detectable=false
|
||||
app/config/*/*/*/* linguist-detectable=false
|
||||
app/views/* linguist-detectable=false
|
||||
app/views/*/* linguist-detectable=false
|
||||
app/views/*/*/* linguist-detectable=false
|
||||
app/views/*/*/*/* linguist-detectable=false
|
||||
app/controllers/* linguist-detectable=false
|
||||
app/controllers/*/* linguist-detectable=false
|
||||
app/controllers/*/*/* linguist-detectable=false
|
||||
app/controllers/*/*/*/* linguist-detectable=false
|
||||
app/controllers/*/*/*/*/* linguist-detectable=false
|
||||
src/* linguist-detectable=false
|
||||
src/*/* linguist-detectable=false
|
||||
src/*/*/* linguist-detectable=false
|
||||
src/*/*/*/* linguist-detectable=false
|
||||
src/*/*/*/*/* linguist-detectable=false
|
||||
tests/* linguist-detectable=false
|
||||
tests/*/* linguist-detectable=false
|
||||
tests/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/*/*/* linguist-detectable=false
|
||||
public/scripts/* linguist-detectable=false
|
||||
public/scripts/*/*/* linguist-detectable=false
|
||||
public/scripts/*/*/*/* linguist-detectable=false
|
||||
public/dist/scripts/* linguist-detectable=false
|
||||
app/config/** linguist-detectable=false
|
||||
app/views/** linguist-detectable=false
|
||||
app/controllers/** linguist-detectable=false
|
||||
src/** linguist-detectable=false
|
||||
tests/** linguist-detectable=false
|
||||
public/scripts/** linguist-detectable=false
|
||||
public/dist/scripts/** linguist-detectable=false
|
||||
|
|
|
|||
88
.github/ISSUE_TEMPLATE/bug.yaml
vendored
88
.github/ISSUE_TEMPLATE/bug.yaml
vendored
|
|
@ -1,88 +0,0 @@
|
|||
name: "🐛 Bug Report"
|
||||
description: "Submit a bug report to help us improve"
|
||||
title: "🐛 Bug Report: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out our bug report form 🙏
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👟 Reproduction steps"
|
||||
description: "How do you trigger this bug? Please walk us through it step by step."
|
||||
placeholder: "When I ..."
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👍 Expected behavior"
|
||||
description: "What did you think would happen?"
|
||||
placeholder: "It should ..."
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👎 Actual Behavior"
|
||||
description: "What did actually happen? Add screenshots, if applicable."
|
||||
placeholder: "It actually ..."
|
||||
- type: dropdown
|
||||
id: appwrite-version
|
||||
attributes:
|
||||
label: "🎲 Appwrite version"
|
||||
description: "What version of Appwrite are you running?"
|
||||
options:
|
||||
- Version 1.0.x
|
||||
- Version 0.15.x
|
||||
- Version 0.14.x
|
||||
- Version 0.13.x
|
||||
- Version 0.12.x
|
||||
- Version 0.11.x
|
||||
- Version 0.10.x
|
||||
- Version 0.9.x
|
||||
- Version 0.8.x
|
||||
- Version 0.7.x
|
||||
- Version 0.6.x
|
||||
- Different version (specify in environment)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: "💻 Operating system"
|
||||
description: "What OS is your server / device running on?"
|
||||
options:
|
||||
- Linux
|
||||
- MacOS
|
||||
- Windows
|
||||
- Something else
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: "🧱 Your Environment"
|
||||
description: "Is your environment customized in any way?"
|
||||
placeholder: "I use Cloudflare for ..."
|
||||
- type: checkboxes
|
||||
id: no-duplicate-issues
|
||||
attributes:
|
||||
label: "👀 Have you spent some time to check if this issue has been raised before?"
|
||||
description: "Have you Googled for a similar issue or checked our older issues for a similar bug?"
|
||||
options:
|
||||
- label: "I checked and didn't find similar issue"
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: read-code-of-conduct
|
||||
attributes:
|
||||
label: "🏢 Have you read the Code of Conduct?"
|
||||
options:
|
||||
- label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)"
|
||||
required: true
|
||||
32
.github/ISSUE_TEMPLATE/documentation.yaml
vendored
32
.github/ISSUE_TEMPLATE/documentation.yaml
vendored
|
|
@ -1,32 +0,0 @@
|
|||
name: "📚 Documentation"
|
||||
description: "Report an issue related to documentation"
|
||||
title: "📚 Documentation: "
|
||||
labels: [documentation]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to make our documentation better 🙏
|
||||
- type: textarea
|
||||
id: issue-description
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "💭 Description"
|
||||
description: "A clear and concise description of what the issue is."
|
||||
placeholder: "Documentation should not ..."
|
||||
- type: checkboxes
|
||||
id: no-duplicate-issues
|
||||
attributes:
|
||||
label: "👀 Have you spent some time to check if this issue has been raised before?"
|
||||
description: "Have you Googled for a similar issue or checked our older issues for a similar bug?"
|
||||
options:
|
||||
- label: "I checked and didn't find similar issue"
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: read-code-of-conduct
|
||||
attributes:
|
||||
label: "🏢 Have you read the Code of Conduct?"
|
||||
options:
|
||||
- label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)"
|
||||
required: true
|
||||
40
.github/ISSUE_TEMPLATE/feature.yaml
vendored
40
.github/ISSUE_TEMPLATE/feature.yaml
vendored
|
|
@ -1,40 +0,0 @@
|
|||
name: 🚀 Feature
|
||||
description: "Submit a proposal for a new feature"
|
||||
title: "🚀 Feature: "
|
||||
labels: [feature]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out our feature request form 🙏
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "🔖 Feature description"
|
||||
description: "A clear and concise description of what the feature is."
|
||||
placeholder: "You should add ..."
|
||||
- type: textarea
|
||||
id: pitch
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "🎤 Pitch"
|
||||
description: "Please explain why this feature should be implemented and how it would be used. Add examples, if applicable."
|
||||
placeholder: "In my use-case, ..."
|
||||
- type: checkboxes
|
||||
id: no-duplicate-issues
|
||||
attributes:
|
||||
label: "👀 Have you spent some time to check if this issue has been raised before?"
|
||||
description: "Have you Googled for a similar issue or checked our older issues for a similar bug?"
|
||||
options:
|
||||
- label: "I checked and didn't find similar issue"
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: read-code-of-conduct
|
||||
attributes:
|
||||
label: "🏢 Have you read the Code of Conduct?"
|
||||
options:
|
||||
- label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)"
|
||||
required: true
|
||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -11,21 +11,17 @@ Happy contributing!
|
|||
|
||||
## What does this PR do?
|
||||
|
||||
(Provide a description of what this PR does.)
|
||||
(Provide a description of what this PR does and why it's needed.)
|
||||
|
||||
## Test Plan
|
||||
|
||||
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)
|
||||
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)
|
||||
|
||||
## Related PRs and Issues
|
||||
|
||||
(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)
|
||||
- (Related PR or issue)
|
||||
|
||||
### Have you added your change to the [Changelog](https://github.com/appwrite/appwrite/blob/master/CHANGES.md)?
|
||||
## Checklist
|
||||
|
||||
(The CHANGES.md file tracks all the changes that make it to the `main` branch. Add your change to this file in the following format)
|
||||
- One line description of your PR [#pr_number](Link to your PR)
|
||||
|
||||
### Have you read the [Contributing Guidelines on issues](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md)?
|
||||
|
||||
(Write your answer here.)
|
||||
- [ ] Have you read the [Contributing Guidelines on issues](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md)?
|
||||
- [ ] If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?
|
||||
|
|
|
|||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
@ -38,14 +38,14 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
|
@ -59,4 +59,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
|
|||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
|
|
@ -8,11 +8,13 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# 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.
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,6 +5,8 @@
|
|||
/tests/resources/functions/**/code.tar.gz
|
||||
/app/sdks/*
|
||||
/.idea/
|
||||
!/.idea/workspace.xml
|
||||
!/.idea/php.xml
|
||||
.DS_Store
|
||||
.php_cs.cache
|
||||
debug/
|
||||
|
|
|
|||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 2.1.1
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
tasks:
|
||||
- name: Run Appwrite Docker Stack
|
||||
init: |
|
||||
docker compose pull
|
||||
git submodule update --init
|
||||
docker compose build
|
||||
docker compose pull
|
||||
docker pull composer
|
||||
command: |
|
||||
docker run --rm --interactive --tty \
|
||||
|
|
|
|||
53
CHANGES.md
53
CHANGES.md
|
|
@ -1,8 +1,59 @@
|
|||
# Version 1.2.1
|
||||
## Features
|
||||
|
||||
## Changes
|
||||
|
||||
## Bugs
|
||||
- Fix a few null safety warnings [#4654](https://github.com/appwrite/appwrite/pull/4654)
|
||||
- Fix timestamp format in Realtime response [#4515](https://github.com/appwrite/appwrite/pull/4515)
|
||||
- Add flutter-web as a platform type [#4992](https://github.com/appwrite/appwrite/pull/4992)
|
||||
- Fix not storing function's response on response codes 5xx [#4610](https://github.com/appwrite/appwrite/pull/4610)
|
||||
|
||||
# Version 1.2.0
|
||||
## Features
|
||||
- Added GraphQL API [#974](https://github.com/appwrite/appwrite/pull/974)
|
||||
- Added GraphQL Explorer [#974](https://github.com/appwrite/appwrite/pull/974)
|
||||
- Added ability to set max sessions per user per project [#4831](https://github.com/appwrite/appwrite/pull/4831)
|
||||
|
||||
## Changes
|
||||
- Get default region from environment on project create [#4780](https://github.com/appwrite/appwrite/pull/4780)
|
||||
- Fix french translation [#4782](https://github.com/appwrite/appwrite/pull/4782)
|
||||
- Fix max mimetype size [#4814](https://github.com/appwrite/appwrite/pull/4814)
|
||||
|
||||
## Bugs
|
||||
- Fix invited account verified status [#4776](https://github.com/appwrite/appwrite/pull/4776)
|
||||
|
||||
# Version 1.1.2
|
||||
## Changes
|
||||
- Released `appwrite/console` [2.0.2](https://github.com/appwrite/console/releases/tag/2.0.2)
|
||||
- Make `region` parameter optional with default for project create [#4763](https://github.com/appwrite/appwrite/pull/4763)
|
||||
|
||||
## Bugs
|
||||
- Fix default oauth paths [#4725](https://github.com/appwrite/appwrite/pull/4725)
|
||||
- Fix session expiration, and expired session deletion [#4739](https://github.com/appwrite/appwrite/pull/4739)
|
||||
- Fix processing status on sync executions [#4737](https://github.com/appwrite/appwrite/pull/4737)
|
||||
- Fix Locale API returning Unknown continent [#4761](https://github.com/appwrite/appwrite/pull/4761)
|
||||
|
||||
# Version 1.1.1
|
||||
## Bugs
|
||||
- Fix Deletes worker using incorrect device for file deletion [#4662](https://github.com/appwrite/appwrite/pull/4662)
|
||||
- Fix Migration for Stats adding the region attribute [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
- Fix Migration stopping scheduled functions [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
- Fix Migration enabling OAuth providers with data by default [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
- Fix Error pages when OAuth providers are disabled [#4704](https://github.com/appwrite/appwrite/pull/4704)
|
||||
|
||||
# Version 1.1.0
|
||||
## Features
|
||||
- Added support for the new Appwrite Console [#4655](https://github.com/appwrite/appwrite/pull/4655)
|
||||
- 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)
|
||||
- Fix not storing function's response on response codes 5xx [#4610](https://github.com/appwrite/appwrite/pull/4610)
|
||||
- Fix missing realtime event for create function deployment [#4574](https://github.com/appwrite/appwrite/pull/4574)
|
||||
- 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)
|
||||
- Fix missing file permissions due to cache [#4661](https://github.com/appwrite/appwrite/pull/4661)
|
||||
- Fix usage stats for async function executions [#4674](https://github.com/appwrite/appwrite/pull/4674)
|
||||
|
||||
# Version 1.0.3
|
||||
## Bugs
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity, expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at team@appwrite.io. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
146
CONTRIBUTING.md
146
CONTRIBUTING.md
|
|
@ -1,18 +1,18 @@
|
|||
# Contributing
|
||||
|
||||
We would ❤️ for you to contribute to Appwrite and help make it better! We want contributing to Appwrite to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, new docs as well as updates and tweaks, blog posts, workshops, and more.
|
||||
We would ❤️ you to contribute to Appwrite and help make it better! We want contributing to Appwrite to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, and new docs, as well as updates and tweaks, blog posts, workshops, and more.
|
||||
|
||||
## How to Start?
|
||||
|
||||
If you are worried or don’t know where to start, check out our next section explaining what kind of help we could use and where can you get involved. You can reach out with questions to [Eldad Fux (@eldadfux)](https://twitter.com/eldadfux) or [@appwrite](https://twitter.com/appwrite) on Twitter, and anyone from the [Appwrite team on Discord](https://discord.gg/GSeTUeA). You can also submit an issue, and a maintainer can guide you!
|
||||
If you are worried or don’t know where to start, check out the next section that explains what kind of help we could use and where you can get involved. You can send your questions to [@appwrite](https://twitter.com/appwrite) on Twitter or to anyone from the [Appwrite team on Discord](https://appwrite.io/discord). You can also submit an issue, and a maintainer can guide you!
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Appwrite open and inclusive. Please read and follow our [Code of Conduct](/CODE_OF_CONDUCT.md).
|
||||
Help us keep Appwrite open and inclusive. Please read and follow our [Code of Conduct](https://github.com/appwrite/.github/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Submit a Pull Request 🚀
|
||||
|
||||
Branch naming convention is as follows
|
||||
Branch naming convention is as following
|
||||
|
||||
`TYPE-ISSUE_ID-DESCRIPTION`
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ doc-548-submit-a-pull-request-section-to-contribution-guide
|
|||
|
||||
When `TYPE` can be:
|
||||
|
||||
- **feat** - is a new feature
|
||||
- **feat** - a new feature
|
||||
- **doc** - documentation only changes
|
||||
- **cicd** - changes related to CI/CD system
|
||||
- **fix** - a bug fix
|
||||
|
|
@ -40,33 +40,33 @@ For the initial start, fork the project and use git clone command to do
|
|||
$ git pull
|
||||
```
|
||||
|
||||
2. Create new branch from `master` like: `doc-548-submit-a-pull-request-section-to-contribution-guide`<br/>
|
||||
2. Create a new branch from `master` like: `doc-548-submit-a-pull-request-section-to-contribution-guide`.<br/>
|
||||
|
||||
```
|
||||
$ git checkout -b [name_of_your_new_branch]
|
||||
```
|
||||
|
||||
3. Work - commit - repeat ( be sure to be in your branch )
|
||||
3. Work - commit - repeat (make sure you're on the correct branch!)
|
||||
|
||||
4. Before you push your changes, make sure your code follows the `PSR12` coding standards, which is the standard Appwrite follows currently. You can easily do this by running the formatter.
|
||||
4. Before you push your changes, make sure your code follows the `PSR12` coding standards, which is the standard that Appwrite currently follows. You can easily do this by running the formatter.
|
||||
|
||||
```bash
|
||||
composer format <your file path>
|
||||
```
|
||||
|
||||
Now, go a step further by running the linter by the following command to manually fix the issues the formatter wasn't able to fix.
|
||||
Now, go a step further by running the linter using the following command to manually fix the issues the formatter wasn't able to.
|
||||
|
||||
```bash
|
||||
composer lint <your file path>
|
||||
```
|
||||
|
||||
This will give you a list of errors for you to rectify, if there is an instance you need more information on the errors being displayed you can pass in additional command line arguments. More list of available arguments can be found [here](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage). A very useful command line argument is `--report=diff`. This will give you the expected changes by the linter for easy fixing of formatting issues.
|
||||
This will give you a list of errors to rectify. If you need more information on the errors, you can pass in additional command line arguments to get more verbose information. More lists of available arguments can be found [here](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage). A very useful command line argument is `--report=diff`. This will give you the expected changes by the linter for easy fixing of formatting issues.
|
||||
|
||||
```bash
|
||||
composer lint --report=diff <your file path>
|
||||
```
|
||||
|
||||
5. Push changes to GitHub
|
||||
5. Push changes to GitHub.
|
||||
|
||||
```
|
||||
$ git push origin [name_of_your_new_branch]
|
||||
|
|
@ -76,23 +76,23 @@ $ git push origin [name_of_your_new_branch]
|
|||
If you go to your repository on GitHub, you'll see a `Compare & pull request` button. Click on that button.
|
||||
7. Start a Pull Request
|
||||
Now submit the pull request and click on `Create pull request`.
|
||||
8. Get a code review approval/reject
|
||||
9. After approval, merge your PR
|
||||
8. Get a code review approval/reject.
|
||||
9. After approval, merge your PR.
|
||||
10. GitHub will automatically delete the branch after the merge is done. (they can still be restored).
|
||||
|
||||
## Setup From Source
|
||||
|
||||
To set up a working **development environment**, just fork the project git repository and install the backend and frontend dependencies using the proper package manager and create run the docker-compose stack.
|
||||
|
||||
> If you just want to install Appwrite for day-to-day usage and not as a code maintainer use this [installation guide](https://github.com/appwrite/appwrite#installation).
|
||||
|
||||
Please note that these instructions are for setting a functional dev environment. If you want to set up an Appwrite instance to integrate into your app, you should probably try and install Appwrite by using the instructions in the [getting started guide](https://appwrite.io/docs/getting-started-for-web) or in the main [README](README.md) file.
|
||||
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/getting-started-for-web), or the main [README](README.md) file.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git
|
||||
|
||||
cd appwrite
|
||||
|
||||
git submodule update --init
|
||||
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
|
@ -117,7 +117,7 @@ After finishing the installation process, you can start writing and editing code
|
|||
|
||||
#### Advanced Topics
|
||||
|
||||
We love to create issues that are good for beginners and label them as `good first issue` or `hacktoberfest`, but some more advanced topics might require extra knowledge. Below is a list of links you can use to learn more about some of the more advanced topics that will help you master the Appwrite codebase.
|
||||
We love to create issues that are good for beginners and label them as `good first issue` or `hacktoberfest`, but some more advanced topics might require extra knowledge. Below is a list of links you can use to learn about the more advanced topics that will help you master the Appwrite codebase.
|
||||
|
||||
##### Tools and Libs
|
||||
|
||||
|
|
@ -150,7 +150,7 @@ Learn more at our [Technology Stack](#technology-stack) section.
|
|||
|
||||
## Architecture
|
||||
|
||||
Appwrite's current structure is a combination of both [Monolithic](https://en.wikipedia.org/wiki/Monolithic_application) and [Microservice](https://en.wikipedia.org/wiki/Microservices) architectures, but our final goal, as we grow, is to be using only microservices.
|
||||
Appwrite's current structure is a combination of both [Monolithic](https://en.wikipedia.org/wiki/Monolithic_application) and [Microservice](https://en.wikipedia.org/wiki/Microservices) architectures.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -188,20 +188,23 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
|
|||
├── src # Supporting libraries (each lib has one role, common libs are released as individual projects)
|
||||
│ └── Appwrite
|
||||
│ ├── Auth
|
||||
│ ├── Database
|
||||
│ ├── Detector
|
||||
│ ├── Docker
|
||||
| ├── DSN
|
||||
│ ├── Event
|
||||
│ ├── Extend
|
||||
│ ├── GraphQL
|
||||
│ ├── Messaging
|
||||
│ ├── Migration
|
||||
│ ├── Network
|
||||
│ ├── OpenSSL
|
||||
│ ├── Realtime
|
||||
│ ├── Promises
|
||||
│ ├── Resque
|
||||
│ ├── Specification
|
||||
│ ├── Task
|
||||
│ ├── Template
|
||||
│ ├── URL
|
||||
│ ├── Usage
|
||||
│ └── Utopia
|
||||
└── tests # End to end & unit tests
|
||||
├── e2e
|
||||
|
|
@ -213,13 +216,13 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
|
|||
|
||||
Appwrite's main API container is designed as a monolithic app. This is a decision we made to allow us to develop the project faster while still being a very small team.
|
||||
|
||||
Although the Appwrite API is a monolithic app, it has a very clear separation of concern as each internal service or worker is separated by its container, which will allow us as we grow to start breaking services for better maintenance and scalability.
|
||||
Although the Appwrite API is a monolithic app, it has a very clear separation of concern as each internal service or worker is separated by its container, which allows us to start breaking services for better maintenance and scalability as we grow.
|
||||
|
||||
### The Microservice Part
|
||||
|
||||
Each container in Appwrite is a microservice on its own. Each service is an independent process that can scale without regard to any of the other services.
|
||||
|
||||
Currently, all of the Appwrite microservices are intended to communicate using the TCP protocol over a private network. You should be aware to not expose any of the services to the public-facing network, besides the public port 80 and 443, who, by default, are used to expose the Appwrite HTTP API.
|
||||
Currently, all the Appwrite microservices are intended to communicate using the TCP protocol over a private network. With the exception of the public-facing port 80 and 443, which by default are used to expose the Appwrite HTTP API, you should **avoid exposing any other services' ports**.
|
||||
|
||||
## Ports
|
||||
|
||||
|
|
@ -227,43 +230,43 @@ Appwrite dev version uses ports 80 and 443 as an entry point to the Appwrite API
|
|||
|
||||
## Technology Stack
|
||||
|
||||
To start helping us to improve the Appwrite server by submitting code, prior knowledge of Appwrite's technology stack can help you with getting started.
|
||||
To start helping us to improve the Appwrite server by submitting code, prior knowledge of Appwrite's technology stack can help you get started.
|
||||
|
||||
Appwrite stack is combined from a variety of open-source technologies and tools. Appwrite backend API is written primarily with PHP version 7 and above on top of the [Utopia PHP framework](https://github.com/utopia-php/framework). The Appwrite frontend is built with tools like gulp, less, and [litespeed.js](https://github.com/litespeed-js). We use Docker as the container technology to package the Appwrite server for easy integration on-cloud, on-premise, or on-localhosts.
|
||||
Appwrite stack is a combination of a variety of open-source technologies and tools. Appwrite backend API is written primarily with PHP version 7 and above, on top of the [Utopia PHP framework](https://github.com/utopia-php/framework). The Appwrite frontend is built with tools like gulp, less, and [litespeed.js](https://github.com/litespeed-js). We use Docker as the container technology to package the Appwrite server for easy on-cloud, on-premise, or on-localhost integration.
|
||||
|
||||
### Other Technologies
|
||||
|
||||
- Redis - for managing cache and in-memory data (currently, we do not use Redis for persistent data)
|
||||
- MariaDB - for database storage and queries
|
||||
- InfluxDB - for managing stats and time-series based data
|
||||
- Statsd - for sending data over UDP protocol (using Telegraf)
|
||||
- ClamAV - for validating and scanning storage files
|
||||
- Redis - for managing cache and in-memory data (currently, we do not use Redis for persistent data).
|
||||
- MariaDB - for database storage and queries.
|
||||
- InfluxDB - for managing stats and time-series based data.
|
||||
- Statsd - for sending data over UDP protocol (using Telegraf).
|
||||
- ClamAV - for validating and scanning storage files.
|
||||
- Imagemagick - for manipulating and managing image media files.
|
||||
- Webp - for better compression of images on supporting clients
|
||||
- SMTP - for sending email messages and alerts
|
||||
- Resque - for managing data queues and scheduled tasks over a Redis server
|
||||
- Webp - for better compression of images on supporting clients.
|
||||
- SMTP - for sending email messages and alerts.
|
||||
- Resque - for managing data queues and scheduled tasks over a Redis server.
|
||||
|
||||
## Package Managers
|
||||
|
||||
Appwrite uses a package manager for managing code dependencies for both backend and frontend development. We try our best to avoid creating any unnecessary, and any new dependency to the project is subjected to a lead developer review and approval.
|
||||
Appwrite uses a package manager for managing code dependencies for both backend and frontend development. We try our best to avoid creating any unnecessary dependencies. New dependency to the project is subjected to a lead developer's review and approval.
|
||||
|
||||
Many of Appwrite's internal modules are also used as dependencies to allow other Appwrite's projects to reuse them and as a way to contribute them back to the community.
|
||||
Many of Appwrite's internal modules are also used as dependencies to allow other Appwrite projects to reuse them and as a way to contribute back to the community.
|
||||
|
||||
Appwrite uses [PHP's Composer](https://getcomposer.org/) for managing dependencies on the server-side and [JS NPM](https://www.npmjs.com/) for managing dependencies on the frontend side.
|
||||
|
||||
## Coding Standards
|
||||
|
||||
Appwrite is following the [PHP-FIG standards](https://www.php-fig.org/). Currently, we are using both PSR-0 and PSR-12 for coding standards and autoloading standards.
|
||||
Appwrite follows the [PHP-FIG standards](https://www.php-fig.org/). Currently, we use both PSR-0 and PSR-12 for coding standards and autoloading standards.
|
||||
|
||||
We use prettier for our JS coding standards and auto-formatting our code.
|
||||
We use prettier for our JS coding standards and auto-formatting for our code.
|
||||
|
||||
## Scalability, Speed, and Performance
|
||||
|
||||
Appwrite is built to scale. Please keep in mind that the Appwrite stack can run in different environments and different scales.
|
||||
|
||||
We wish Appwrite will be as easy to set up and in a single, localhost, and easy to grow to a large environment with dozens and even hundreds of instances.
|
||||
We intend Appwrite to be as easy to set up as possible in a single localhost, and to grow easily into a large environment with dozens and even hundreds of instances.
|
||||
|
||||
When contributing code, please take into account the following considerations:
|
||||
When contributing code, please take into account the following:
|
||||
|
||||
- Response Time
|
||||
- Throughput
|
||||
|
|
@ -274,25 +277,25 @@ When contributing code, please take into account the following considerations:
|
|||
- Background Jobs
|
||||
- Task Execution Time
|
||||
|
||||
## Security & Privacy
|
||||
## Security and Privacy
|
||||
|
||||
Security and privacy are extremely important to Appwrite, developers, and users alike. Make sure to follow the best industry standards and practices.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Please avoid introducing new dependencies to Appwrite without consulting the team. New dependencies can be very helpful but also introduce new security and privacy issues, complexity, and impact total docker image size.
|
||||
Please avoid introducing new dependencies to Appwrite without consulting the team. New dependencies can be very helpful, but they also introduce new security and privacy risks, add complexity, and impact the total docker image size.
|
||||
|
||||
Adding a new dependency should have vital value on the product with minimum possible risk.
|
||||
Adding a new dependency should have vital value for the product with minimum possible risk.
|
||||
|
||||
## Introducing New Features
|
||||
|
||||
We would 💖 you to contribute to Appwrite, but we would also like to make sure Appwrite is as great as possible and loyal to its vision and mission statement 🙏.
|
||||
We would 💖 you to contribute to Appwrite, but we also want to ensure Appwrite is loyal to its vision and mission statement 🙏.
|
||||
|
||||
For us to find the right balance, please open an issue explaining your ideas before introducing a new pull request.
|
||||
|
||||
This will allow the Appwrite community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision.
|
||||
This will allow the Appwrite community to sufficiently discuss the new feature value and how it fits within the product roadmap and vision.
|
||||
|
||||
This is also important for the Appwrite lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc).
|
||||
This is also important for the Appwrite lead developers to be able to provide technical input and potentially a different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc).
|
||||
|
||||
## Build
|
||||
|
||||
|
|
@ -316,30 +319,35 @@ The Runtimes for all supported cloud functions (multicore builds) can be found a
|
|||
|
||||
## Generate SDK
|
||||
|
||||
For generating a new console SDK follow the next steps:
|
||||
The following steps are used to generate a new console SDK:
|
||||
|
||||
1. Update the console spec file located at `app/config/specs/swagger2-<version-number>.console.json` using Appwrite Tasks. Run the `php app/cli.php specs <version-number> normal` command in a running `appwrite/appwrite` container.
|
||||
2. Generate a new SDK using the command `php app/cli.php sdks`
|
||||
3. Change your working dir using `cd app/sdks/console-web`
|
||||
4. Build the new SDK `npm run build`
|
||||
5. Copy `iife/sdk.js` to `appwrite.js`
|
||||
6. Go back to the root of the project `run npm run build`
|
||||
2. Generate a new SDK using the command `php app/cli.php sdks`.
|
||||
3. Change your working dir using `cd app/sdks/console-web`.
|
||||
4. Build the new SDK `npm run build`.
|
||||
5. Copy `iife/sdk.js` to `appwrite.js`.
|
||||
6. Go back to the root of the project `run npm run build`.
|
||||
|
||||
## Checklist for Releasing SDKs
|
||||
|
||||
Things to remember when releasing SDKs
|
||||
Things to remember when releasing SDKs:
|
||||
|
||||
- Update the Changelogs in **docs/sdks** (right now only Dart and Flutter are using these)
|
||||
- Update **GETTING_STARTED.md** in **docs/sdks** for each SDKs if any changes in the related APIs in there
|
||||
- Update SDK versions as required on **app/config/platforms.php**
|
||||
- Generate SDKs using the command `php app/cli.php sdks` and follow the instructions
|
||||
- Release new tags on GitHub repository for each SDKs
|
||||
- Update the Changelogs in **docs/sdks** (right now only Dart and Flutter are using these).
|
||||
- Update **GETTING_STARTED.md** in **docs/sdks** for each SDKs if any changes in the related APIs are in there.
|
||||
- Update SDK versions as required on **app/config/platforms.php**.
|
||||
- Generate SDKs using the command `php app/cli.php sdks` and follow the instructions.
|
||||
- Release new tags on GitHub repository for each SDK.
|
||||
|
||||
## Debug
|
||||
|
||||
Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension or if you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection.
|
||||
Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code's [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension.
|
||||
|
||||
First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php** and then change the IP address to your development machine's IP. Without the proper IP address debugger won't connect. And you also need to set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
|
||||
If you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection:
|
||||
|
||||
1. Create an init file.
|
||||
2. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php**.
|
||||
3. Change the IP address to your development machine's IP. Without the proper IP address, the debugger won't connect.
|
||||
4. Set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
|
||||
|
||||
### VS Code Launch Configuration
|
||||
|
||||
|
|
@ -357,7 +365,7 @@ First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** fil
|
|||
|
||||
### PHPStorm Setup
|
||||
|
||||
In settings, go to **Languages & Frameworks** > **PHP** > **Debug**, there under **Xdebug** set the debug port to **9005** and enable **can accept external connections** checkbox.
|
||||
In settings, go to **Languages & Frameworks** > **PHP** > **Debug**, under **Xdebug** set the debug port to **9005** and enable the **can accept external connections** checkbox.
|
||||
|
||||
## Tests
|
||||
|
||||
|
|
@ -433,34 +441,34 @@ composer lint <your file path>
|
|||
From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials:
|
||||
|
||||
- [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
|
||||
- [Appwrite Environment Variables](./docs/tutorials/environment-variables.md)
|
||||
- [Running in Production](./docs/tutorials/running-in-production.md)
|
||||
- [Appwrite Environment Variables](./docs/tutorials/add-environment-variable.md)
|
||||
- [Running in Production](https://appwrite.io/docs/production)
|
||||
- [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
|
||||
|
||||
## Other Ways to Help
|
||||
|
||||
Pull requests are great, but there are many other areas where you can help Appwrite.
|
||||
Pull requests are great, but there are many other ways you can help Appwrite.
|
||||
|
||||
### Blogging & Speaking
|
||||
|
||||
Blogging, speaking about, or creating tutorials about one of Appwrite’s many features. Mention [@appwrite](https://twitter.com/appwrite) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io) so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub.
|
||||
Blogging, speaking about, or creating tutorials about one of Appwrite’s many features are great ways to get the word out about Appwrite. Mention [@appwrite](https://twitter.com/appwrite) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io) so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub.
|
||||
|
||||
### Presenting at Meetups
|
||||
|
||||
Presenting at meetups and conferences about your Appwrite projects. Your unique challenges and successes in building things with Appwrite can provide great speaking material. We’d love to review your talk abstract/CFP, so get in touch with us if you’d like some help!
|
||||
We encourage our contributors to present at meetups and conferences about your Appwrite projects. Your unique challenges and successes in building things with Appwrite can provide great speaking material. We’d love to review your talk abstract/CFP, so get in touch with us if you’d like some help!
|
||||
|
||||
### Sending Feedbacks & Reporting Bugs
|
||||
### Sending Feedbacks and Reporting Bugs
|
||||
|
||||
Sending feedback is a great way for us to understand your different use cases of Appwrite better. If you had any issues, bugs, or want to share about your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA).
|
||||
Sending feedback is a great way for us to understand your different use cases of Appwrite better. If you had any issues, bugs, or want to share your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA).
|
||||
|
||||
### Submitting New Ideas
|
||||
|
||||
If you think Appwrite could use a new feature, please open an issue on our GitHub repository, stating as much information as you can think about your new idea and it's implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature.
|
||||
If you think Appwrite could use a new feature, please open an issue on our GitHub repository, stating as much information as you have about your new idea and its implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature.
|
||||
|
||||
### Improving Documentation
|
||||
|
||||
Submitting documentation updates, enhancements, designs, or bug fixes. Spelling or grammar fixes will be very much appreciated.
|
||||
Submitting documentation updates, enhancements, designs, or bug fixes, as well as spelling or grammar fixes is much appreciated.
|
||||
|
||||
### Helping Someone
|
||||
|
||||
Searching for Appwrite on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Appwrite's repo!
|
||||
Consider searching for Appwrite on Discord, GitHub, or StackOverflow to help someone who needs help. You can also help by teaching others how to contribute to Appwrite's repo!
|
||||
|
|
|
|||
25
Dockerfile
25
Dockerfile
|
|
@ -14,12 +14,15 @@ 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
|
||||
|
||||
ARG VITE_GA_PROJECT
|
||||
ARG VITE_CONSOLE_MODE
|
||||
|
||||
ENV VITE_GA_PROJECT=$VITE_GA_PROJECT
|
||||
ENV VITE_CONSOLE_MODE=$VITE_CONSOLE_MODE
|
||||
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
|
@ -35,7 +38,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 \
|
||||
|
|
@ -246,13 +249,13 @@ ENV _APP_SERVER=swoole \
|
|||
_APP_SETUP=self-hosted \
|
||||
_APP_VERSION=$VERSION \
|
||||
_APP_USAGE_STATS=enabled \
|
||||
_APP_USAGE_TIMESERIES_INTERVAL=30 \
|
||||
_APP_USAGE_DATABASE_INTERVAL=900 \
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=30 \
|
||||
# 14 Days = 1209600 s
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600 \
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT=1209600 \
|
||||
# 1 Day = 86400 s
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400 \
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000 \
|
||||
_APP_MAINTENANCE_INTERVAL=86400 \
|
||||
_APP_LOGGING_PROVIDER= \
|
||||
_APP_LOGGING_CONFIG=
|
||||
|
|
@ -297,7 +300,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 +314,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 +377,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" ]
|
||||
20
README-CN.md
20
README-CN.md
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://twitter.com/appwrite)
|
||||
|
||||
<!-- [](https://hub.docker.com/r/appwrite/appwrite) -->
|
||||
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
[**我们发布了 Appwrite Console 2.0 版本,点击这里了解更多!**](https://medium.com/appwrite-io/announcing-console-2-0-2e0e96891cb0?source=friends_link&sk=7a82b4069778e3adc165dc026e960fe1)
|
||||
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
|
||||
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存,图像处理,云函数计算,[等多种服务](https://appwrite.io/docs).
|
||||
|
|
@ -62,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.2.0
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
|
@ -74,17 +76,17 @@ 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.2.0
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
||||
```powershell
|
||||
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
|
||||
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.2.0
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
|
@ -167,7 +169,7 @@ Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应
|
|||
|
||||
## 订阅我们
|
||||
|
||||
加入我们在世界各地不断发展的社区!请参阅我们的官方 [博客](https://medium.com/appwrite-io)。在 [Twitter](https://twitter.com/appwrite)、[Facebook 页面](https://www.facebook.com/appwrite.io)、[Facebook 群组](https://www.facebook)、[开发者社区](https://dev.to/appwrite) 等平台订阅我们或加入我们的 [Discord 社区](https://discord.gg/GSeTUeA) 以获得更多帮助,想法和讨论。
|
||||
加入我们在世界各地不断发展的社区!请参阅我们的官方 [博客](https://medium.com/appwrite-io)。在 [Twitter](https://twitter.com/appwrite)、[Facebook 页面](https://www.facebook.com/appwrite.io)、[Facebook 群组](https://www.facebook.com/appwrite.io/groups/)、[开发者社区](https://dev.to/appwrite) 等平台订阅我们或加入我们的 [Discord 社区](https://discord.gg/GSeTUeA) 以获得更多帮助,想法和讨论。
|
||||
|
||||
## 版权说明
|
||||
|
||||
|
|
|
|||
38
README.md
38
README.md
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
[](https://twitter.com/appwrite)
|
||||
|
||||
<!-- [](https://hub.docker.com/r/appwrite/appwrite) -->
|
||||
|
|
@ -25,11 +25,11 @@
|
|||
|
||||
English | [简体中文](README-CN.md)
|
||||
|
||||
[**Appwrite 1.0 has been released! Learn what's new!**](https://appwrite.io/1.0)
|
||||
[**Announcing Appwrite 1.2 with GraphQL support! Learn what's new!**](https://appwrite.io/graphql)
|
||||
|
||||
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<nobr> microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
|
||||
|
||||
Using Appwrite, you can easily integrate your app with user authentication & multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, and [more services](https://appwrite.io/docs).
|
||||
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, and [more services](https://appwrite.io/docs).
|
||||
|
||||
<p align="center">
|
||||
<br />
|
||||
|
|
@ -64,7 +64,7 @@ Table of Contents:
|
|||
|
||||
## Installation
|
||||
|
||||
Appwrite backend server is designed to run in a container environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool like Kubernetes, Docker Swarm, or Rancher.
|
||||
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as Kubernetes, Docker Swarm, or Rancher.
|
||||
|
||||
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
|
||||
|
||||
|
|
@ -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.2.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.2.0
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
|
@ -97,10 +97,10 @@ 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.2.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.
|
||||
Once the Docker installation is complete, 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 completing the installation.
|
||||
|
||||
For advanced production and custom installation, check out our Docker [environment variables](https://appwrite.io/docs/environment-variables) docs. You can also use our public [docker-compose.yml](https://appwrite.io/install/compose) and [.env](https://appwrite.io/install/env) files to manually set up an environment.
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ If you are upgrading your Appwrite server from an older version, you should use
|
|||
|
||||
## One-Click Setups
|
||||
|
||||
In addition to running Appwrite locally, you can also launch Appwrite using a pre-configured setup. This allows you to get up and running with Appwrite quickly without installing Docker on your local machine.
|
||||
In addition to running Appwrite locally, you can also launch Appwrite using a pre-configured setup. This allows you to get up and running quickly with Appwrite without installing Docker on your local machine.
|
||||
|
||||
Choose from one of the providers below:
|
||||
|
||||
|
|
@ -147,18 +147,18 @@ Getting started with Appwrite is as easy as creating a new project, choosing you
|
|||
- [**Account**](https://appwrite.io/docs/client/account) - Manage current user authentication and account. Track and manage the user sessions, devices, sign-in methods, and security logs.
|
||||
- [**Users**](https://appwrite.io/docs/server/users) - Manage and list all project users when building backend integrations with Server SDKs.
|
||||
- [**Teams**](https://appwrite.io/docs/client/teams) - Manage and group users in teams. Manage memberships, invites, and user roles within a team.
|
||||
- [**Databases**](https://appwrite.io/docs/client/databases) - Manage databases, collections and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
|
||||
- [**Storage**](https://appwrite.io/docs/client/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to fit your app perfectly. All files are scanned by ClamAV and stored in a secure and encrypted way.
|
||||
- [**Functions**](https://appwrite.io/docs/server/functions) - Customize your Appwrite server by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event, manually or using a CRON schedule.
|
||||
- [**Realtime**](https://appwrite.io/docs/realtime) - Listen to real-time events for any of your Appwrite services including users, storage, functions, databases and more.
|
||||
- [**Locale**](https://appwrite.io/docs/client/locale) - Track your user's location, and manage your app locale-based data.
|
||||
- [**Avatars**](https://appwrite.io/docs/client/avatars) - Manage your users' avatars, countries' flags, browser icons, credit card symbols, and generate QR codes.
|
||||
- [**Databases**](https://appwrite.io/docs/client/databases) - Manage databases, collections, and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
|
||||
- [**Storage**](https://appwrite.io/docs/client/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to perfectly fit your app. All files are scanned by ClamAV and stored in a secure and encrypted way.
|
||||
- [**Functions**](https://appwrite.io/docs/server/functions) - Customize your Appwrite server by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event either manually or using a CRON schedule.
|
||||
- [**Realtime**](https://appwrite.io/docs/realtime) - Listen to real-time events for any of your Appwrite services including users, storage, functions, databases, and more.
|
||||
- [**Locale**](https://appwrite.io/docs/client/locale) - Track your user's location and manage your app locale-based data.
|
||||
- [**Avatars**](https://appwrite.io/docs/client/avatars) - Manage your users' avatars, countries' flags, browser icons, and credit card symbols. Generate QR codes from links or plaintext strings.
|
||||
|
||||
For the complete API documentation, visit [https://appwrite.io/docs](https://appwrite.io/docs). For more tutorials, news and announcements check out our [blog](https://medium.com/appwrite-io) and [Discord Server](https://discord.gg/GSeTUeA).
|
||||
|
||||
### SDKs
|
||||
|
||||
Below is a list of currently supported platforms and languages. If you wish to help us add support to your platform of choice, you can go over to our [SDK Generator](https://github.com/appwrite/sdk-generator) project and view our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md).
|
||||
Below is a list of currently supported platforms and languages. If you would like to help us add support to your platform of choice, you can go over to our [SDK Generator](https://github.com/appwrite/sdk-generator) project and view our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md).
|
||||
|
||||
#### Client
|
||||
|
||||
|
|
@ -190,13 +190,13 @@ Looking for more SDKs? - Help us by contributing a pull request to our [SDK Gene
|
|||
|
||||

|
||||
|
||||
Appwrite uses a microservices architecture that was designed for easy scaling and delegation of responsibilities. In addition, Appwrite supports multiple APIs (REST, WebSocket, and GraphQL-soon) to allow you to interact with your resources by leveraging your existing knowledge and protocols of choice.
|
||||
Appwrite uses a microservices architecture that was designed for easy scaling and delegation of responsibilities. In addition, Appwrite supports multiple APIs, such as REST, WebSocket, and GraphQL to allow you to interact with your resources by leveraging your existing knowledge and protocols of choice.
|
||||
|
||||
The Appwrite API layer was designed to be extremely fast by leveraging in-memory caching and delegating any heavy-lifting tasks to the Appwrite background workers. The background workers also allow you to precisely control your compute capacity and costs using a message queue to handle the load. You can learn more about our architecture in the [contribution guide](CONTRIBUTING.md#architecture-1).
|
||||
|
||||
## Contributing
|
||||
|
||||
All code contributions - including those of people having commit access - must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code.
|
||||
All code contributions, including those of people having commit access, must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code.
|
||||
|
||||
We truly ❤️ pull requests! If you wish to help, you can learn more about how you can contribute to this project in the [contribution guide](CONTRIBUTING.md).
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ For security issues, kindly email us at [security@appwrite.io](mailto:security@a
|
|||
|
||||
## Follow Us
|
||||
|
||||
Join our growing community around the world! See our official [Blog](https://medium.com/appwrite-io). Follow us on [Twitter](https://twitter.com/appwrite), [Facebook Page](https://www.facebook.com/appwrite.io), [Facebook Group](https://www.facebook.com/groups/appwrite.developers/), [Dev Community](https://dev.to/appwrite) or join our live [Discord server](https://discord.gg/GSeTUeA) for more help, ideas, and discussions.
|
||||
Join our growing community around the world! Check out our official [Blog](https://medium.com/appwrite-io). Follow us on [Twitter](https://twitter.com/appwrite), [Facebook Page](https://www.facebook.com/appwrite.io), [Facebook Group](https://www.facebook.com/groups/appwrite.developers/), [Dev Community](https://dev.to/appwrite) or join our live [Discord server](https://discord.gg/GSeTUeA) for more help, ideas, and discussions.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| <= 0.10 | :x: |
|
||||
| 0.11.x | :white_check_mark: |
|
||||
| 0.12.x | :white_check_mark: |
|
||||
| <= 0.14 | :x: |
|
||||
| 1.15.x | :white_check_mark: |
|
||||
| 1.0.x | :white_check_mark: |
|
||||
| 1.1.x | :white_check_mark: |
|
||||
| 1.2.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
|
|
|||
BIN
app/assets/dbip/dbip-country-lite-2023-01.mmdb
Normal file
BIN
app/assets/dbip/dbip-country-lite-2023-01.mmdb
Normal file
Binary file not shown.
|
|
@ -523,6 +523,17 @@ $collections = [
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('region'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
|
|
@ -737,6 +748,13 @@ $collections = [
|
|||
'lengths' => [128],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_team'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['teamId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -1614,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,
|
||||
|
|
@ -2209,14 +2216,14 @@ $collections = [
|
|||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [768],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -2230,21 +2237,21 @@ $collections = [
|
|||
'$id' => ID::custom('_key_runtime'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['runtime'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [768],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_deployment'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['deployment'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_schedule'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['schedule'],
|
||||
'lengths' => [128],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -2403,14 +2410,14 @@ $collections = [
|
|||
'$id' => ID::custom('_key_resource'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_resource_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceType'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -2424,7 +2431,7 @@ $collections = [
|
|||
'$id' => ID::custom('_key_entrypoint'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['entrypoint'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [768],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -2438,7 +2445,7 @@ $collections = [
|
|||
'$id' => ID::custom('_key_buildId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['buildId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -2939,14 +2946,14 @@ $collections = [
|
|||
'$id' => ID::custom('_fulltext_name'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [1024],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -2960,7 +2967,7 @@ $collections = [
|
|||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [128],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -3010,6 +3017,17 @@ $collections = [
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('region'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 255,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('value'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
|
|
@ -3238,7 +3256,7 @@ $collections = [
|
|||
'$id' => ID::custom('mimeType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 127, // https://tools.ietf.org/html/rfc4288#section-4.2
|
||||
'size' => 255, // https://tools.ietf.org/html/rfc4288#section-4.2
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
|
|
@ -3383,7 +3401,7 @@ $collections = [
|
|||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
@ -3397,21 +3415,21 @@ $collections = [
|
|||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [768],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_signature'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['signature'],
|
||||
'lengths' => [2048],
|
||||
'lengths' => [768],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_mimeType'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['mimeType'],
|
||||
'lengths' => [127],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ return [
|
|||
],
|
||||
Exception::GENERAL_ROUTE_NOT_FOUND => [
|
||||
'name' => Exception::GENERAL_ROUTE_NOT_FOUND,
|
||||
'description' => 'The requested route was not found. Please refer to the docs and try again.',
|
||||
'description' => 'The requested route was not found. Please refer to the API docs and try again.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::GENERAL_CURSOR_NOT_FOUND => [
|
||||
|
|
@ -486,7 +486,7 @@ return [
|
|||
],
|
||||
Exception::PROJECT_PROVIDER_UNSUPPORTED => [
|
||||
'name' => Exception::PROJECT_PROVIDER_UNSUPPORTED,
|
||||
'description' => 'The chosen OAuth provider is unsupported. Please check <a href="/docs/client/account?sdk=web-default#accountCreateOAuth2Session"> the docs</a> for the complete list of supported OAuth providers.',
|
||||
'description' => 'The chosen OAuth provider is unsupported. Please check the <a href="/docs/client/account?sdk=web-default#accountCreateOAuth2Session">Create OAuth2 Session docs</a> for the complete list of supported OAuth providers.',
|
||||
'code' => 501,
|
||||
],
|
||||
Exception::PROJECT_INVALID_SUCCESS_URL => [
|
||||
|
|
@ -549,4 +549,14 @@ return [
|
|||
'description' => 'Domain verification for the requested domain has failed.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::GRAPHQL_NO_QUERY => [
|
||||
'name' => Exception::GRAPHQL_NO_QUERY,
|
||||
'description' => 'Param "query" is not optional.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GRAPHQL_TOO_MANY_QUERIES => [
|
||||
'name' => Exception::GRAPHQL_TOO_MANY_QUERIES,
|
||||
'description' => 'Too many queries.',
|
||||
'code' => 400,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"emails.magicSession.signature": "L'équipe {{project}}",
|
||||
"emails.recovery.subject": "Réinitialisation du mot de passe",
|
||||
"emails.recovery.hello": "Bonjour {{name}}",
|
||||
"emails.recovery.body": "Suivez ce lien pour réinitialiser votre mot de passe pour {{projet}}.",
|
||||
"emails.recovery.body": "Suivez ce lien pour réinitialiser votre mot de passe pour {{project}}.",
|
||||
"emails.recovery.footer": "Si vous n'avez pas demandé à réinitialiser votre mot de passe, vous pouvez ignorer ce message.",
|
||||
"emails.recovery.thanks": "Merci",
|
||||
"emails.recovery.signature": "L'équipe {{project}}",
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
"emails.invitation.signature": "L'équipe {{project}}",
|
||||
"emails.certificate.subject": "Échec du certificat pour %s",
|
||||
"emails.certificate.hello": "Bonjour",
|
||||
"emails.certificate.body": "Le certificate pour votre domaine '{{domain}}' n'a pas pu être généré. Ceci est la tentative {{tentative}} et l'échec a été causé par : {{erreur}}",
|
||||
"emails.certificate.body": "Le certificate pour votre domaine '{{domain}}' n'a pas pu être généré. Ceci est la tentative {{attempt}} et l'échec a été causé par : {{error}}",
|
||||
"emails.certificate.footer": "Votre certificat précédent sera valide pendant 30 jours à compter de la première défaillance. Nous vous recommandons fortement d'enquêter sur ce cas, sinon votre domaine se retrouvera sans communication SSL valide.",
|
||||
"emails.certificate.thanks": "Merci",
|
||||
"emails.certificate.signature": "L'équipe {{project}}",
|
||||
|
|
|
|||
|
|
@ -6,25 +6,25 @@
|
|||
"emails.verification.subject": "अकाउंट वेरिफिकेशन ",
|
||||
"emails.verification.hello": "नमस्ते {{name}}",
|
||||
"emails.verification.body": "इस लिंक के माध्यम से अपने ईमेल को सत्यापित कीजिये।",
|
||||
"emails.verification.footer": "यदि आपने इस पते को सत्यापित नहीं करना चाहते है, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.verification.footer": "यदि आप इस पते को सत्यापित नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.verification.thanks": "धन्यवाद",
|
||||
"emails.verification.signature": "{{project}} टीम",
|
||||
"emails.magicSession.subject": "लॉग इन",
|
||||
"emails.magicSession.hello": "नमस्ते,",
|
||||
"emails.magicSession.body": "इस लिंक के माध्यम से लॉग-इन करें।",
|
||||
"emails.magicSession.footer": "यदि आप इस ईमेल द्वारा लॉगिन नहीं करना चाहते है, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.magicSession.footer": "यदि आप इस ईमेल द्वारा लॉगिन नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.magicSession.thanks": "धन्यवाद",
|
||||
"emails.magicSession.signature": "{{project}} टीम",
|
||||
"emails.recovery.subject": "पासवर्ड रीसेट",
|
||||
"emails.recovery.hello": "नमस्ते {{name}}",
|
||||
"emails.recovery.body": "इस लिंक के माध्यम से अपना {{project}} पासवर्ड रीसेट करें।",
|
||||
"emails.recovery.footer": "यदि आप अपना पासवर्ड रिसेट नहीं करना चाहते है, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.recovery.footer": "यदि आप अपना पासवर्ड रीसेट नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.recovery.thanks": "धन्यवाद",
|
||||
"emails.recovery.signature": "{{project}} टीम",
|
||||
"emails.invitation.subject": "%s टीम का यहाँ %s पर आमंत्रण",
|
||||
"emails.invitation.hello": "नमस्ते",
|
||||
"emails.invitation.body": "यह मेल आपको इसलिए भेजा गया था क्योंकि {{owner}} आपको {{team}} टीम का सदस्य बनाना चाहते थे, जो {{project}} से जुड़ा हुआ है।",
|
||||
"emails.invitation.footer": "यदि आप इसमे रूचि नहीं रखते, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.invitation.body": "यह मेल आपको इसलिए भेजा गया है क्योंकि {{owner}} आपको {{team}} टीम का सदस्य बनाना चाहते है, जो {{project}} से जुड़ा हुआ है।",
|
||||
"emails.invitation.footer": "यदि आप इसमें रूचि नहीं रखते, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।",
|
||||
"emails.invitation.thanks": "धन्यवाद",
|
||||
"emails.invitation.signature": "{{project}} टीम",
|
||||
"locale.country.unknown": "अज्ञात",
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"countries.ae": "संयुक्त अरब अमीरात",
|
||||
"countries.ar": "अर्जेंटीना",
|
||||
"countries.am": "आर्मीनिया",
|
||||
"countries.ag": "अंटीगुआ और बारबूडा",
|
||||
"countries.ag": "एंटीगुआ और बारबूडा",
|
||||
"countries.au": "ऑस्ट्रेलिया",
|
||||
"countries.at": "ऑस्ट्रिया",
|
||||
"countries.az": "अज़रबैजान",
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
"countries.bd": "बांग्लादेश",
|
||||
"countries.bg": "बुल्गारिया",
|
||||
"countries.bh": "बहरीन",
|
||||
"countries.bs": "बहामास",
|
||||
"countries.bs": "द बाहामास",
|
||||
"countries.ba": "बॉस्निया और हर्ज़ेगोविना",
|
||||
"countries.by": "बेलारूस",
|
||||
"countries.bz": "बेलीज़",
|
||||
|
|
|
|||
|
|
@ -5,57 +5,63 @@
|
|||
"emails.sender": "%s 小組",
|
||||
"emails.verification.subject": "帳戶驗證",
|
||||
"emails.verification.hello": "嗨 {{name}}",
|
||||
"emails.verification.body": "按照此鏈接驗證您的電子郵件地址。",
|
||||
"emails.verification.body": "按照此連結驗證您的電子郵件地址。",
|
||||
"emails.verification.footer": "如果您沒有要求驗證此地址,則可以忽略此消息。",
|
||||
"emails.verification.thanks": "謝謝",
|
||||
"emails.verification.signature": "{{project}} 團隊",
|
||||
"emails.magicSession.subject": "登錄",
|
||||
"emails.magicSession.hello": "嗨",
|
||||
"emails.magicSession.body": "點此鏈接登錄。",
|
||||
"emails.magicSession.footer": "如果您沒有要求使用此電子郵件登錄,則可以忽略此消息。",
|
||||
"emails.magicSession.subject": "登入",
|
||||
"emails.magicSession.hello": "嗨,",
|
||||
"emails.magicSession.body": "點此連結登入。",
|
||||
"emails.magicSession.footer": "如果您沒有要求使用此電子郵件登入,則可以忽略此消息。",
|
||||
"emails.magicSession.thanks": "謝謝",
|
||||
"emails.magicSession.signature": "{{project}} 團隊",
|
||||
"emails.recovery.subject": "重設密碼",
|
||||
"emails.recovery.hello": "你好 {{name}}",
|
||||
"emails.recovery.body": "按照此鏈接重置您的 {{project}} 密碼.",
|
||||
"emails.recovery.hello": "您好 {{name}}",
|
||||
"emails.recovery.body": "按照此連結重置您的 {{project}} 密碼。",
|
||||
"emails.recovery.footer": "如果您沒有要求重置密碼,則可以忽略此消息。",
|
||||
"emails.recovery.thanks": "謝謝",
|
||||
"emails.recovery.signature": "{{project}} 團隊",
|
||||
"emails.invitation.subject": "邀請 %s 團隊在 %s",
|
||||
"emails.invitation.hello": "你好",
|
||||
"emails.invitation.body": "這封郵件發送給您是因為 {{owner}} 想邀請您成為 {{team}} 團隊在 {{project}}.",
|
||||
"emails.invitation.hello": "您好",
|
||||
"emails.invitation.body": "發送這封郵件給您是因為 {{owner}} 想邀請您成為 {{team}} 團隊在 {{project}}。",
|
||||
"emails.invitation.footer": "如果您不感興趣,可以忽略此消息。",
|
||||
"emails.invitation.thanks": "謝謝",
|
||||
"emails.invitation.signature": "{{project}} 團隊",
|
||||
"emails.certificate.subject": "%s 的憑證更新失敗",
|
||||
"emails.certificate.hello": "您好",
|
||||
"emails.certificate.body": "無法產生您的域名 '{{domain}}' 的憑證。這是第 {{attempt}} 次嘗試,失敗原因是:{{error}}",
|
||||
"emails.certificate.footer": "您的上一個憑證將會在第一次失敗的 30 天後失效。我們強烈建議您調查這件事情,否則您的域名將會無法進行有效的 SSL 通訊。",
|
||||
"emails.certificate.thanks": "謝謝",
|
||||
"emails.certificate.signature": "{{project}} 團隊",
|
||||
"locale.country.unknown": "未知",
|
||||
"countries.af": "阿富汗",
|
||||
"countries.ao": "安哥拉",
|
||||
"countries.al": "阿爾巴尼亞",
|
||||
"countries.ad": "安道爾",
|
||||
"countries.ae": "阿拉伯聯合酋長國",
|
||||
"countries.ae": "阿拉伯聯合大公國",
|
||||
"countries.ar": "阿根廷",
|
||||
"countries.am": "亞美尼亞",
|
||||
"countries.ag": "安提瓜和巴布達",
|
||||
"countries.au": "澳大利亞",
|
||||
"countries.ag": "安地卡及巴布達",
|
||||
"countries.au": "澳洲",
|
||||
"countries.at": "奧地利",
|
||||
"countries.az": "阿塞拜疆",
|
||||
"countries.bi": "布隆迪",
|
||||
"countries.az": "亞塞拜然",
|
||||
"countries.bi": "蒲隆地",
|
||||
"countries.be": "比利時",
|
||||
"countries.bj": "貝甯",
|
||||
"countries.bf": "布基納法索",
|
||||
"countries.bd": "孟加拉國",
|
||||
"countries.bj": "貝南",
|
||||
"countries.bf": "布吉納法索",
|
||||
"countries.bd": "孟加拉",
|
||||
"countries.bg": "保加利亞",
|
||||
"countries.bh": "巴林",
|
||||
"countries.bs": "巴哈馬",
|
||||
"countries.ba": "波黑",
|
||||
"countries.by": "白羅斯",
|
||||
"countries.bz": "伯利茲",
|
||||
"countries.ba": "波士尼亞與赫塞哥維納",
|
||||
"countries.by": "白俄羅斯",
|
||||
"countries.bz": "貝里斯",
|
||||
"countries.bo": "玻利維亞",
|
||||
"countries.br": "巴西",
|
||||
"countries.bb": "巴巴多斯",
|
||||
"countries.bn": "文萊",
|
||||
"countries.bb": "巴貝多",
|
||||
"countries.bn": "汶萊",
|
||||
"countries.bt": "不丹",
|
||||
"countries.bw": "博茨瓦納",
|
||||
"countries.bw": "波札那",
|
||||
"countries.cf": "中非共和國",
|
||||
"countries.ca": "加拿大",
|
||||
"countries.ch": "瑞士",
|
||||
|
|
@ -66,42 +72,42 @@
|
|||
"countries.cd": "剛果民主共和國",
|
||||
"countries.cg": "剛果共和國",
|
||||
"countries.co": "哥倫比亞",
|
||||
"countries.km": "科摩",
|
||||
"countries.cv": "佛得角",
|
||||
"countries.cr": "哥斯達黎加",
|
||||
"countries.km": "葛摩",
|
||||
"countries.cv": "維德角",
|
||||
"countries.cr": "哥斯大黎加",
|
||||
"countries.cu": "古巴",
|
||||
"countries.cy": "塞浦路斯",
|
||||
"countries.cy": "賽普勒斯",
|
||||
"countries.cz": "捷克",
|
||||
"countries.de": "德國",
|
||||
"countries.dj": "吉布提",
|
||||
"countries.dj": "吉布地",
|
||||
"countries.dm": "多米尼克",
|
||||
"countries.dk": "丹麥",
|
||||
"countries.do": "多明尼加共和國",
|
||||
"countries.dz": "阿爾及利亞",
|
||||
"countries.ec": "厄瓜多爾",
|
||||
"countries.ec": "厄瓜多",
|
||||
"countries.eg": "埃及",
|
||||
"countries.er": "厄立特裏亞",
|
||||
"countries.er": "厄利垂亞",
|
||||
"countries.es": "西班牙",
|
||||
"countries.ee": "愛沙尼亞",
|
||||
"countries.et": "埃塞俄比亞",
|
||||
"countries.et": "衣索比亞",
|
||||
"countries.fi": "芬蘭",
|
||||
"countries.fj": "斐濟",
|
||||
"countries.fr": "法國",
|
||||
"countries.fm": "密克羅尼西亞",
|
||||
"countries.ga": "加蓬",
|
||||
"countries.ga": "加彭",
|
||||
"countries.gb": "英國",
|
||||
"countries.ge": "格魯吉亞",
|
||||
"countries.gh": "加納",
|
||||
"countries.ge": "喬治亞",
|
||||
"countries.gh": "迦納",
|
||||
"countries.gn": "幾內亞",
|
||||
"countries.gm": "岡比亞",
|
||||
"countries.gw": "幾內亞比紹",
|
||||
"countries.gm": "甘比亞",
|
||||
"countries.gw": "幾內亞比索",
|
||||
"countries.gq": "赤道幾內亞",
|
||||
"countries.gr": "希臘",
|
||||
"countries.gd": "格林納達",
|
||||
"countries.gt": "危地馬拉",
|
||||
"countries.gy": "圭亞那",
|
||||
"countries.hn": "洪都拉斯",
|
||||
"countries.hr": "克羅地亞",
|
||||
"countries.gd": "格瑞那達",
|
||||
"countries.gt": "瓜地馬拉",
|
||||
"countries.gy": "蓋亞那",
|
||||
"countries.hn": "宏都拉斯",
|
||||
"countries.hr": "克羅埃西亞",
|
||||
"countries.ht": "海地",
|
||||
"countries.hu": "匈牙利",
|
||||
"countries.id": "印度尼西亞",
|
||||
|
|
@ -111,26 +117,26 @@
|
|||
"countries.iq": "伊拉克",
|
||||
"countries.is": "冰島",
|
||||
"countries.il": "以色列",
|
||||
"countries.it": "意大利",
|
||||
"countries.it": "義大利",
|
||||
"countries.jm": "牙買加",
|
||||
"countries.jo": "約旦",
|
||||
"countries.jp": "日本",
|
||||
"countries.kz": "哈薩克斯坦",
|
||||
"countries.ke": "肯尼亞",
|
||||
"countries.kg": "吉爾吉斯斯坦",
|
||||
"countries.kz": "哈薩克",
|
||||
"countries.ke": "肯亞",
|
||||
"countries.kg": "吉爾吉斯",
|
||||
"countries.kh": "柬埔寨",
|
||||
"countries.ki": "基裏巴斯",
|
||||
"countries.kn": "聖基茨和尼維斯",
|
||||
"countries.ki": "吉里巴斯",
|
||||
"countries.kn": "聖克里斯多福及尼維斯",
|
||||
"countries.kr": "韓國",
|
||||
"countries.kw": "科威特",
|
||||
"countries.la": "老撾",
|
||||
"countries.la": "寮國",
|
||||
"countries.lb": "黎巴嫩",
|
||||
"countries.lr": "利比裏亞",
|
||||
"countries.lr": "賴比瑞亞",
|
||||
"countries.ly": "利比亞",
|
||||
"countries.lc": "聖盧西亞",
|
||||
"countries.li": "列支敦士登",
|
||||
"countries.lk": "斯裏蘭卡",
|
||||
"countries.ls": "萊索托",
|
||||
"countries.lc": "聖露西亞",
|
||||
"countries.li": "列支敦斯登",
|
||||
"countries.lk": "斯里蘭卡",
|
||||
"countries.ls": "賴索托",
|
||||
"countries.lt": "立陶宛",
|
||||
"countries.lu": "盧森堡",
|
||||
"countries.lv": "拉脫維亞",
|
||||
|
|
@ -138,90 +144,90 @@
|
|||
"countries.mc": "摩納哥",
|
||||
"countries.md": "摩爾多瓦",
|
||||
"countries.mg": "馬達加斯加",
|
||||
"countries.mv": "馬爾代夫",
|
||||
"countries.mv": "馬爾地夫",
|
||||
"countries.mx": "墨西哥",
|
||||
"countries.mh": "馬紹爾群島",
|
||||
"countries.mk": "馬其頓共和國",
|
||||
"countries.ml": "馬裏",
|
||||
"countries.mt": "馬耳他",
|
||||
"countries.ml": "馬利",
|
||||
"countries.mt": "馬爾他",
|
||||
"countries.mm": "緬甸",
|
||||
"countries.me": "黑山",
|
||||
"countries.me": "蒙特內哥羅",
|
||||
"countries.mn": "蒙古",
|
||||
"countries.mz": "莫桑比克",
|
||||
"countries.mr": "毛裏塔尼亞",
|
||||
"countries.mu": "毛裏求斯",
|
||||
"countries.mw": "馬拉維",
|
||||
"countries.mz": "莫三比克",
|
||||
"countries.mr": "茅利塔尼亞",
|
||||
"countries.mu": "模里西斯",
|
||||
"countries.mw": "馬拉威",
|
||||
"countries.my": "馬來西亞",
|
||||
"countries.na": "納米比亞",
|
||||
"countries.ne": "尼日爾",
|
||||
"countries.ng": "尼日利亞",
|
||||
"countries.ne": "尼日",
|
||||
"countries.ng": "奈及利亞",
|
||||
"countries.ni": "尼加拉瓜",
|
||||
"countries.nl": "荷蘭",
|
||||
"countries.no": "挪威",
|
||||
"countries.np": "尼泊爾",
|
||||
"countries.nr": "瑙魯",
|
||||
"countries.nz": "新西蘭",
|
||||
"countries.nr": "諾魯",
|
||||
"countries.nz": "紐西蘭",
|
||||
"countries.om": "阿曼",
|
||||
"countries.pk": "巴基斯坦",
|
||||
"countries.pa": "巴拿馬",
|
||||
"countries.pe": "秘魯",
|
||||
"countries.ph": "菲律賓",
|
||||
"countries.pw": "帕勞",
|
||||
"countries.pg": "巴布亞新幾內亞",
|
||||
"countries.pw": "帛琉",
|
||||
"countries.pg": "巴布亞紐幾內亞",
|
||||
"countries.pl": "波蘭",
|
||||
"countries.kp": "北朝鮮",
|
||||
"countries.kp": "北韓",
|
||||
"countries.pt": "葡萄牙",
|
||||
"countries.py": "巴拉圭",
|
||||
"countries.qa": "卡塔爾",
|
||||
"countries.qa": "卡達",
|
||||
"countries.ro": "羅馬尼亞",
|
||||
"countries.ru": "俄羅斯",
|
||||
"countries.rw": "盧旺達",
|
||||
"countries.sa": "沙特阿拉伯",
|
||||
"countries.rw": "盧安達",
|
||||
"countries.sa": "沙烏地阿拉伯",
|
||||
"countries.sd": "蘇丹",
|
||||
"countries.sn": "塞內加爾",
|
||||
"countries.sg": "新加坡",
|
||||
"countries.sb": "所羅門群島",
|
||||
"countries.sl": "塞拉利昂",
|
||||
"countries.sb": "索羅門群島",
|
||||
"countries.sl": "獅子山",
|
||||
"countries.sv": "薩爾瓦多",
|
||||
"countries.sm": "聖馬力諾",
|
||||
"countries.so": "索馬裏",
|
||||
"countries.sm": "聖馬利諾",
|
||||
"countries.so": "索馬利亞",
|
||||
"countries.rs": "塞爾維亞",
|
||||
"countries.ss": "南蘇丹",
|
||||
"countries.st": "聖多美和普林西比",
|
||||
"countries.sr": "蘇裏南",
|
||||
"countries.st": "聖多美普林西比",
|
||||
"countries.sr": "蘇利南",
|
||||
"countries.sk": "斯洛伐克",
|
||||
"countries.si": "斯洛文尼亞",
|
||||
"countries.si": "斯洛維尼亞",
|
||||
"countries.se": "瑞典",
|
||||
"countries.sz": "斯威士蘭",
|
||||
"countries.sc": "塞舌爾",
|
||||
"countries.sz": "史瓦帝尼",
|
||||
"countries.sc": "塞席爾",
|
||||
"countries.sy": "敘利亞",
|
||||
"countries.td": "乍得",
|
||||
"countries.td": "查德",
|
||||
"countries.tg": "多哥",
|
||||
"countries.th": "泰國",
|
||||
"countries.tj": "塔吉克斯坦",
|
||||
"countries.tm": "土庫曼斯坦",
|
||||
"countries.tj": "塔吉克",
|
||||
"countries.tm": "土庫曼",
|
||||
"countries.tl": "東帝汶",
|
||||
"countries.to": "湯加",
|
||||
"countries.tt": "特立尼達和多巴哥",
|
||||
"countries.tn": "突尼斯",
|
||||
"countries.to": "東加",
|
||||
"countries.tt": "千里達及托巴哥",
|
||||
"countries.tn": "突尼西亞",
|
||||
"countries.tr": "土耳其",
|
||||
"countries.tv": "圖瓦盧",
|
||||
"countries.tz": "坦桑尼亞",
|
||||
"countries.ug": "烏幹達",
|
||||
"countries.tv": "吐瓦魯",
|
||||
"countries.tz": "坦尚尼亞",
|
||||
"countries.ug": "烏干達",
|
||||
"countries.ua": "烏克蘭",
|
||||
"countries.uy": "烏拉圭",
|
||||
"countries.us": "美國",
|
||||
"countries.uz": "烏茲別克斯",
|
||||
"countries.uz": "烏茲別克",
|
||||
"countries.va": "梵蒂岡",
|
||||
"countries.vc": "聖文森特和格林納丁斯",
|
||||
"countries.vc": "聖文森及格瑞那丁",
|
||||
"countries.ve": "委內瑞拉",
|
||||
"countries.vn": "越南",
|
||||
"countries.vu": "瓦努阿圖",
|
||||
"countries.vu": "萬那杜",
|
||||
"countries.ws": "薩摩亞",
|
||||
"countries.ye": "也門",
|
||||
"countries.ye": "葉門",
|
||||
"countries.za": "南非",
|
||||
"countries.zm": "贊比亞",
|
||||
"countries.zw": "津巴布韋",
|
||||
"countries.zm": "尚比亞",
|
||||
"countries.zw": "辛巴威",
|
||||
"continents.af": "非洲",
|
||||
"continents.an": "南極洲",
|
||||
"continents.as": "亞洲",
|
||||
|
|
@ -229,4 +235,4 @@
|
|||
"continents.na": "北美洲",
|
||||
"continents.oc": "大洋洲",
|
||||
"continents.sa": "南美洲"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '10.1.0',
|
||||
'version' => '10.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -63,7 +63,7 @@ return [
|
|||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '8.1.0',
|
||||
'version' => '8.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -81,11 +81,11 @@ return [
|
|||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
|
|
@ -116,11 +116,11 @@ return [
|
|||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
|
|
@ -135,21 +135,44 @@ return [
|
|||
'Java' => 'java',
|
||||
],
|
||||
],
|
||||
// [
|
||||
// 'key' => 'java',
|
||||
// 'name' => 'Java',
|
||||
// 'url' => '',
|
||||
// 'enabled' => false,
|
||||
// 'beta' => false,
|
||||
// 'dev' => false,
|
||||
// 'hidden' => false,
|
||||
// 'family' => APP_PLATFORM_CLIENT,
|
||||
// 'prism' => 'java',
|
||||
// 'source' => false,
|
||||
// 'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
|
||||
// 'gitRepoName' => 'sdk-for-java',
|
||||
// 'gitUserName' => 'appwrite',
|
||||
// ],
|
||||
[
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'version' => 'October 2021',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-graphql'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
[
|
||||
'key' => 'rest',
|
||||
'name' => 'REST',
|
||||
'version' => '',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-rest'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -162,7 +185,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '7.1.0',
|
||||
'version' => '7.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
|
|
@ -172,8 +195,8 @@ return [
|
|||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'prism' => 'console',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => null,
|
||||
'gitBranch' => null,
|
||||
'gitUrl' => '',
|
||||
'gitBranch' => '',
|
||||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
],
|
||||
|
|
@ -208,7 +231,7 @@ return [
|
|||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '8.1.0',
|
||||
'version' => '8.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -226,7 +249,7 @@ return [
|
|||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '6.1.0',
|
||||
'version' => '6.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -244,7 +267,7 @@ return [
|
|||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '7.1.0',
|
||||
'version' => '7.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -262,7 +285,7 @@ return [
|
|||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
|
|
@ -280,7 +303,7 @@ return [
|
|||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '7.1.0',
|
||||
'version' => '7.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -298,7 +321,7 @@ return [
|
|||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
|
|
@ -316,7 +339,7 @@ return [
|
|||
[
|
||||
'key' => 'java',
|
||||
'name' => 'Java',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-java',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
|
|
@ -334,7 +357,7 @@ return [
|
|||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => false,
|
||||
|
|
@ -352,7 +375,7 @@ return [
|
|||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '7.1.0',
|
||||
'version' => '7.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -370,11 +393,11 @@ return [
|
|||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
|
|
@ -392,11 +415,11 @@ return [
|
|||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '1.1.0',
|
||||
'version' => '1.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
|
|
@ -407,6 +430,44 @@ return [
|
|||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'main',
|
||||
],
|
||||
[
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'version' => 'October 2021',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-graphql'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
[
|
||||
'key' => 'rest',
|
||||
'name' => 'REST',
|
||||
'version' => '',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-rest'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
9
app/config/regions.php
Normal file
9
app/config/regions.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'name' => 'Default',
|
||||
'default' => true,
|
||||
'disabled' => false,
|
||||
]
|
||||
];
|
||||
|
|
@ -6,6 +6,7 @@ $member = [
|
|||
'public',
|
||||
'home',
|
||||
'console',
|
||||
'graphql',
|
||||
'account',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
|
|
@ -22,6 +23,7 @@ $member = [
|
|||
];
|
||||
|
||||
$admins = [
|
||||
'graphql',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
'documents.read',
|
||||
|
|
@ -58,6 +60,7 @@ return [
|
|||
'public',
|
||||
'home',
|
||||
'console',
|
||||
'graphql',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'files.read',
|
||||
|
|
@ -85,6 +88,6 @@ return [
|
|||
],
|
||||
Auth::USER_ROLE_APPS => [
|
||||
'label' => 'Applications',
|
||||
'scopes' => ['health.read'],
|
||||
'scopes' => ['health.read', 'graphql'],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -176,14 +176,14 @@ return [
|
|||
'graphql' => [
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'subtitle' => 'Appwrite\'s GraphQL Endpoint',
|
||||
'description' => 'GraphQL Endpoint',
|
||||
'subtitle' => 'The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.',
|
||||
'description' => '/docs/services/graphql.md',
|
||||
'controller' => 'api/graphql.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/graphql',
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/graphql.png',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
1
app/config/specs/open-api3-1.1.x-client.json
Normal file
1
app/config/specs/open-api3-1.1.x-client.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/open-api3-1.1.x-console.json
Normal file
1
app/config/specs/open-api3-1.1.x-console.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/open-api3-1.1.x-server.json
Normal file
1
app/config/specs/open-api3-1.1.x-server.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/open-api3-1.2.x-client.json
Normal file
1
app/config/specs/open-api3-1.2.x-client.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/open-api3-1.2.x-console.json
Normal file
1
app/config/specs/open-api3-1.2.x-console.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/open-api3-1.2.x-server.json
Normal file
1
app/config/specs/open-api3-1.2.x-server.json
Normal file
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/config/specs/swagger2-1.1.x-client.json
Normal file
1
app/config/specs/swagger2-1.1.x-client.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/swagger2-1.1.x-console.json
Normal file
1
app/config/specs/swagger2-1.1.x-console.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/swagger2-1.1.x-server.json
Normal file
1
app/config/specs/swagger2-1.1.x-server.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/swagger2-1.2.x-client.json
Normal file
1
app/config/specs/swagger2-1.2.x-client.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/swagger2-1.2.x-console.json
Normal file
1
app/config/specs/swagger2-1.2.x-console.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/swagger2-1.2.x-server.json
Normal file
1
app/config/specs/swagger2-1.2.x-server.json
Normal file
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
|
|
@ -170,8 +170,8 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_USAGE_AGGREGATION_INTERVAL',
|
||||
'description' => 'Deprecated since 1.0.0, use `_APP_USAGE_TIMESERIES_INTERVAL` and `_APP_USAGE_DATABASE_INTERVAL` instead.',
|
||||
'introduction' => '0.10.0',
|
||||
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to Database from TimeSeries data. The default value is 30 seconds. Reintroduced in 1.1.0.',
|
||||
'introduction' => '1.1.0',
|
||||
'default' => '30',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
|
|
@ -179,7 +179,7 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_USAGE_TIMESERIES_INTERVAL',
|
||||
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to Appwrite Database from Timeseries Database. The default value is 30 seconds.',
|
||||
'description' => 'Deprecated since 1.1.0 use _APP_USAGE_AGGREGATION_INTERVAL instead.',
|
||||
'introduction' => '1.0.0',
|
||||
'default' => '30',
|
||||
'required' => false,
|
||||
|
|
@ -188,7 +188,7 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_USAGE_DATABASE_INTERVAL',
|
||||
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats from data in Appwrite Database. The default value is 15 minutes.',
|
||||
'description' => 'Deprecated since 1.1.0 use _APP_USAGE_AGGREGATION_INTERVAL instead.',
|
||||
'introduction' => '1.0.0',
|
||||
'default' => '900',
|
||||
'required' => false,
|
||||
|
|
@ -482,9 +482,9 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DEVICE',
|
||||
'description' => 'Select default storage device. The default value is \'Local\'. List of supported adapters are \'Local\', \'S3\', \'DOSpaces\', \'Backblaze\', \'Linode\' and \'Wasabi\'.',
|
||||
'description' => 'Select default storage device. The default value is \'local\'. List of supported adapters are \'local\', \'s3\', \'dospaces\', \'backblaze\', \'linode\' and \'wasabi\'.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'Local',
|
||||
'default' => 'local',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
|
|
@ -857,7 +857,49 @@ return [
|
|||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_USAGE_HOURLY',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain hourly usage metrics. The default value is 8640000 seconds (100 days).',
|
||||
'introduction' => '',
|
||||
'default' => '8640000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'GraphQL',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_BATCH_SIZE',
|
||||
'description' => 'Maximum number of batched queries per request. The default value is 10.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '10',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_COMPLEXITY',
|
||||
'description' => 'Maximum complexity of a GraphQL query. One field adds one to query complexity. Lists multiply the complexity by the number of items requested. The default value is 250.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '250',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_DEPTH',
|
||||
'description' => 'Maximum depth of a GraphQL query. One nested field level adds one to query depth. The default value is 3.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '3',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
1
app/console
Submodule
1
app/console
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 5e2a40c1e397bd341a432698c9d76a3f96315841
|
||||
|
|
@ -5,14 +5,13 @@ use Appwrite\Auth\Auth;
|
|||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\URL;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\URL\URL as URLParser;
|
||||
|
|
@ -39,12 +38,11 @@ use Utopia\Database\Validator\UID;
|
|||
use Utopia\Locale\Locale;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$oauthDefaultSuccess = '/v1/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/v1/auth/oauth2/failure';
|
||||
$oauthDefaultSuccess = '/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/auth/oauth2/failure';
|
||||
|
||||
App::post('/v1/account')
|
||||
->desc('Create Account')
|
||||
|
|
@ -64,7 +62,7 @@ App::post('/v1/account')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('abuse-limit', 10)
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
|
|
@ -141,8 +139,8 @@ App::post('/v1/account')
|
|||
|
||||
App::post('/v1/account/sessions/email')
|
||||
->alias('/v1/account/sessions')
|
||||
->desc('Create Account Session with Email')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->desc('Create Email Session')
|
||||
->groups(['api', 'account', 'auth', 'session'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('scope', 'public')
|
||||
->label('auth.type', 'emailPassword')
|
||||
|
|
@ -165,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();
|
||||
|
|
@ -185,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(
|
||||
[
|
||||
|
|
@ -197,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']) : '--',
|
||||
|
|
@ -244,6 +244,7 @@ App::post('/v1/account/sessions/email')
|
|||
$session
|
||||
->setAttribute('current', true)
|
||||
->setAttribute('countryName', $countryName)
|
||||
->setAttribute('expire', $expire)
|
||||
;
|
||||
|
||||
$events
|
||||
|
|
@ -255,7 +256,7 @@ App::post('/v1/account/sessions/email')
|
|||
});
|
||||
|
||||
App::get('/v1/account/sessions/oauth2/:provider')
|
||||
->desc('Create Account Session with OAuth2')
|
||||
->desc('Create OAuth2 Session')
|
||||
->groups(['api', 'account'])
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->label('scope', 'public')
|
||||
|
|
@ -364,7 +365,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
|
|||
|
||||
App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->desc('OAuth2 Redirect')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'session'])
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('scope', 'public')
|
||||
|
|
@ -425,8 +426,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
|
||||
}
|
||||
|
||||
$state['failure'] = null;
|
||||
|
||||
$accessToken = $oauth2->getAccessToken($code);
|
||||
$refreshToken = $oauth2->getRefreshToken($code);
|
||||
$accessTokenExpiry = $oauth2->getAccessTokenExpiry($code);
|
||||
|
|
@ -450,7 +449,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);
|
||||
|
|
@ -525,10 +525,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(),
|
||||
|
|
@ -540,7 +541,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']) : '--',
|
||||
|
|
@ -571,6 +571,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())
|
||||
|
|
@ -619,7 +621,7 @@ App::post('/v1/account/sessions/magic-url')
|
|||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->inject('request')
|
||||
|
|
@ -711,11 +713,33 @@ App::post('/v1/account/sessions/magic-url')
|
|||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $loginSecret, 'expire' => $expire, 'project' => $project->getId()]);
|
||||
$url = Template::unParseURL($url);
|
||||
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project->getAttribute('name'));
|
||||
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.magicSession.subject");
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.magicSession.hello"))
|
||||
->setParam('{{name}}', '')
|
||||
->setParam('{{body}}', $locale->getText("emails.magicSession.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.magicSession.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.magicSession.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.magicSession.signature"))
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
$mails
|
||||
->setType(MAIL_TYPE_MAGIC_SESSION)
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->setUrl($url)
|
||||
->setLocale($locale->default)
|
||||
->trigger()
|
||||
;
|
||||
|
||||
|
|
@ -737,7 +761,7 @@ App::post('/v1/account/sessions/magic-url')
|
|||
|
||||
App::put('/v1/account/sessions/magic-url')
|
||||
->desc('Create Magic URL session (confirmation)')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'session'])
|
||||
->label('scope', 'public')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('audits.event', 'session.update')
|
||||
|
|
@ -759,10 +783,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 */
|
||||
|
||||
|
|
@ -778,10 +803,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(
|
||||
[
|
||||
|
|
@ -790,7 +816,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']) : '--',
|
||||
|
|
@ -850,6 +875,7 @@ App::put('/v1/account/sessions/magic-url')
|
|||
$session
|
||||
->setAttribute('current', true)
|
||||
->setAttribute('countryName', $countryName)
|
||||
->setAttribute('expire', $expire)
|
||||
;
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
|
|
@ -872,7 +898,7 @@ App::post('/v1/account/sessions/phone')
|
|||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
|
@ -977,7 +1003,7 @@ App::post('/v1/account/sessions/phone')
|
|||
|
||||
App::put('/v1/account/sessions/phone')
|
||||
->desc('Create Phone Session (confirmation)')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'session'])
|
||||
->label('scope', 'public')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('usage.metric', 'sessions.{scope}.requests.create')
|
||||
|
|
@ -996,10 +1022,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));
|
||||
|
||||
|
|
@ -1013,10 +1040,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(
|
||||
[
|
||||
|
|
@ -1025,7 +1053,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']) : '--',
|
||||
|
|
@ -1083,6 +1110,7 @@ App::put('/v1/account/sessions/phone')
|
|||
$session
|
||||
->setAttribute('current', true)
|
||||
->setAttribute('countryName', $countryName)
|
||||
->setAttribute('expire', $expire)
|
||||
;
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
|
|
@ -1090,7 +1118,7 @@ App::put('/v1/account/sessions/phone')
|
|||
|
||||
App::post('/v1/account/sessions/anonymous')
|
||||
->desc('Create Anonymous Session')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->groups(['api', 'account', 'auth', 'session'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('scope', 'public')
|
||||
->label('auth.type', 'anonymous')
|
||||
|
|
@ -1164,11 +1192,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(
|
||||
[
|
||||
|
|
@ -1177,7 +1205,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']) : '--',
|
||||
|
|
@ -1217,13 +1244,14 @@ App::post('/v1/account/sessions/anonymous')
|
|||
$session
|
||||
->setAttribute('current', true)
|
||||
->setAttribute('countryName', $countryName)
|
||||
->setAttribute('expire', $expire)
|
||||
;
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
App::post('/v1/account/jwt')
|
||||
->desc('Create Account JWT')
|
||||
->desc('Create JWT')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->label('scope', 'account')
|
||||
->label('auth.type', 'jwt')
|
||||
|
|
@ -1310,7 +1338,7 @@ App::get('/v1/account/prefs')
|
|||
});
|
||||
|
||||
App::get('/v1/account/sessions')
|
||||
->desc('List Account Sessions')
|
||||
->desc('List Sessions')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
|
|
@ -1324,10 +1352,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'));
|
||||
|
|
@ -1345,7 +1375,7 @@ App::get('/v1/account/sessions')
|
|||
});
|
||||
|
||||
App::get('/v1/account/logs')
|
||||
->desc('List Account Logs')
|
||||
->desc('List Logs')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
|
|
@ -1406,7 +1436,7 @@ App::get('/v1/account/logs')
|
|||
});
|
||||
|
||||
App::get('/v1/account/sessions/:sessionId')
|
||||
->desc('Get Session By ID')
|
||||
->desc('Get Session')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
|
|
@ -1422,11 +1452,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 */
|
||||
|
|
@ -1436,6 +1468,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);
|
||||
|
|
@ -1446,7 +1479,7 @@ App::get('/v1/account/sessions/:sessionId')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/name')
|
||||
->desc('Update Account Name')
|
||||
->desc('Update Name')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.name')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -1477,7 +1510,7 @@ App::patch('/v1/account/name')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/password')
|
||||
->desc('Update Account Password')
|
||||
->desc('Update Password')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.password')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -1517,7 +1550,7 @@ App::patch('/v1/account/password')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/email')
|
||||
->desc('Update Account Email')
|
||||
->desc('Update Email')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.email')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -1569,7 +1602,7 @@ App::patch('/v1/account/email')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/phone')
|
||||
->desc('Update Account Phone')
|
||||
->desc('Update Phone')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.phone')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -1617,7 +1650,7 @@ App::patch('/v1/account/phone')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/prefs')
|
||||
->desc('Update Account Preferences')
|
||||
->desc('Update Preferences')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.prefs')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -1646,7 +1679,7 @@ App::patch('/v1/account/prefs')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/status')
|
||||
->desc('Update Account Status')
|
||||
->desc('Update Status')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.status')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -1681,7 +1714,7 @@ App::patch('/v1/account/status')
|
|||
});
|
||||
|
||||
App::delete('/v1/account/sessions/:sessionId')
|
||||
->desc('Delete Account Session')
|
||||
->desc('Delete Session')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
|
|
@ -1702,11 +1735,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', []);
|
||||
|
|
@ -1752,7 +1787,7 @@ App::delete('/v1/account/sessions/:sessionId')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/sessions/:sessionId')
|
||||
->desc('Update Session (Refresh Tokens)')
|
||||
->desc('Update OAuth Session (Refresh Tokens)')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].update')
|
||||
|
|
@ -1777,9 +1812,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', []);
|
||||
|
|
@ -1820,6 +1855,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())
|
||||
|
|
@ -1834,7 +1873,7 @@ App::patch('/v1/account/sessions/:sessionId')
|
|||
});
|
||||
|
||||
App::delete('/v1/account/sessions')
|
||||
->desc('Delete All Account Sessions')
|
||||
->desc('Delete Sessions')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
|
|
@ -1873,6 +1912,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
|
||||
|
|
@ -1886,8 +1926,6 @@ App::delete('/v1/account/sessions')
|
|||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$numOfSessions = count($sessions);
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $session->getId());
|
||||
|
|
@ -1975,12 +2013,35 @@ App::post('/v1/account/recovery')
|
|||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
|
||||
$url = Template::unParseURL($url);
|
||||
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.recovery.subject");
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
|
||||
->setParam('{{name}}', $profile->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.recovery.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.recovery.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.recovery.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
|
||||
$mails
|
||||
->setType(MAIL_TYPE_RECOVERY)
|
||||
->setRecipient($profile->getAttribute('email', ''))
|
||||
->setUrl($url)
|
||||
->setLocale($locale->default)
|
||||
->setName($profile->getAttribute('name'))
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setSubject($subject)
|
||||
->trigger();
|
||||
;
|
||||
|
||||
|
|
@ -2135,11 +2196,32 @@ App::post('/v1/account/verification')
|
|||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
|
||||
$url = Template::unParseURL($url);
|
||||
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.verification.subject");
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
|
||||
->setParam('{{name}}', $user->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.verification.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.verification.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.verification.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.verification.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
$mails
|
||||
->setType(MAIL_TYPE_VERIFICATION)
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->setUrl($url)
|
||||
->setLocale($locale->default)
|
||||
->setName($user->getAttribute('name'))
|
||||
->trigger()
|
||||
;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\URL;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
|
|
@ -242,8 +242,8 @@ App::get('/v1/avatars/favicon')
|
|||
case 'jpeg':
|
||||
$size = \explode('x', \strtolower($sizes));
|
||||
|
||||
$sizeWidth = (int) $size[0] ?? 0;
|
||||
$sizeHeight = (int) $size[1] ?? 0;
|
||||
$sizeWidth = (int) ($size[0] ?? 0);
|
||||
$sizeHeight = (int) ($size[1] ?? 0);
|
||||
|
||||
if (($sizeWidth * $sizeHeight) >= $space) {
|
||||
$space = $sizeWidth * $sizeHeight;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ use Utopia\Database\Exception\Structure as StructureException;
|
|||
use Utopia\Locale\Locale;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\URL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
|
|
@ -69,7 +69,6 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
|||
$filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint
|
||||
$default = $attribute->getAttribute('default');
|
||||
|
||||
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
|
|
@ -163,7 +162,7 @@ App::post('/v1/databases')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_DATABASE) // Model for database needs to be created
|
||||
->param('databaseId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('databaseId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -384,7 +383,7 @@ App::get('/v1/databases/:databaseId/logs')
|
|||
|
||||
App::put('/v1/databases/:databaseId')
|
||||
->desc('Update Database')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'databases.write')
|
||||
->label('event', 'databases.[databaseId].update')
|
||||
->label('audits.event', 'database.update')
|
||||
|
|
@ -398,7 +397,7 @@ App::put('/v1/databases/:databaseId')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_DATABASE)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->param('name', null, new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
|
|
@ -427,7 +426,7 @@ App::put('/v1/databases/:databaseId')
|
|||
|
||||
App::delete('/v1/databases/:databaseId')
|
||||
->desc('Delete Database')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'databases.write')
|
||||
->label('event', 'databases.[databaseId].delete')
|
||||
->label('audits.event', 'database.delete')
|
||||
|
|
@ -489,9 +488,9 @@ App::post('/v1/databases/:databaseId/collections')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_COLLECTION)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('collectionId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permissions strings. By default no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -729,7 +728,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
|||
App::put('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
|
||||
->desc('Update Collection')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].update')
|
||||
->label('audits.event', 'collection.update')
|
||||
|
|
@ -746,7 +745,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
|||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default the current permission are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(), 'Is collection enabled?', true)
|
||||
->inject('response')
|
||||
|
|
@ -797,7 +796,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
|||
App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
|
||||
->desc('Delete Collection')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].delete')
|
||||
->label('audits.event', 'collection.delete')
|
||||
|
|
@ -854,7 +853,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default'])
|
||||
->desc('Create String Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -904,7 +903,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default'])
|
||||
->desc('Create Email Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -948,7 +947,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default'])
|
||||
->desc('Create Enum Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1008,7 +1007,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default'])
|
||||
->desc('Create IP Address Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1052,7 +1051,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default'])
|
||||
->desc('Create URL Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1096,7 +1095,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default'])
|
||||
->desc('Create Integer Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1169,7 +1168,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default'])
|
||||
->desc('Create Float Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1245,7 +1244,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default'])
|
||||
->desc('Create Boolean Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1444,7 +1443,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
|||
App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
|
||||
->desc('Delete Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete')
|
||||
->label('audits.event', 'attribute.delete')
|
||||
|
|
@ -1753,15 +1752,14 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
|||
$indexes = $collection->getAttribute('indexes');
|
||||
|
||||
// Search for index
|
||||
$indexIndex = array_search($key, array_column($indexes, 'key'));
|
||||
$indexIndex = array_search($key, array_map(fn($idx) => $idx['key'], $indexes));
|
||||
|
||||
if ($indexIndex === false) {
|
||||
throw new Exception(Exception::INDEX_NOT_FOUND);
|
||||
}
|
||||
|
||||
$index = new Document([\array_merge($indexes[$indexIndex], [
|
||||
'collectionId' => $database->getInternalId() . '_' . $collectionId,
|
||||
])]);
|
||||
$index = $indexes[$indexIndex];
|
||||
$index->setAttribute('collectionId', $database->getInternalId() . '_' . $collectionId);
|
||||
|
||||
$response->dynamic($index, Response::MODEL_INDEX);
|
||||
});
|
||||
|
|
@ -1855,10 +1853,10 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_DOCUMENT)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('documentId', '', new CustomId(), 'Document ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
|
|
@ -2246,7 +2244,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
|
|
@ -2467,8 +2465,8 @@ App::get('/v1/databases/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -2529,7 +2527,7 @@ App::get('/v1/databases/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
@ -2586,8 +2584,8 @@ App::get('/v1/databases/:databaseId/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -2643,7 +2641,7 @@ App::get('/v1/databases/:databaseId/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
@ -2706,8 +2704,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -2758,7 +2756,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ App::post('/v1/functions')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FUNCTION)
|
||||
->param('functionId', '', new CustomId(), 'Function ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('functionId', '', new CustomId(), 'Function ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.')
|
||||
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
|
||||
|
|
@ -237,8 +237,8 @@ App::get('/v1/functions/:functionId/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -292,7 +292,7 @@ App::get('/v1/functions/:functionId/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
@ -340,8 +340,8 @@ App::get('/v1/functions/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -395,7 +395,7 @@ App::get('/v1/functions/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
@ -619,14 +619,20 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
}
|
||||
|
||||
$file = $request->getFiles('code');
|
||||
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
|
||||
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', 0));
|
||||
$upload = new Upload();
|
||||
|
||||
// GraphQL multipart spec adds files with index keys
|
||||
if (empty($file)) {
|
||||
$file = $request->getFiles(0);
|
||||
}
|
||||
|
||||
if (empty($file)) {
|
||||
throw new Exception(Exception::STORAGE_FILE_EMPTY, 'No file sent');
|
||||
}
|
||||
|
||||
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
|
||||
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', 0));
|
||||
$upload = new Upload();
|
||||
|
||||
// Make sure we handle a single file and multiple files the same way
|
||||
$fileName = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
|
||||
$fileTmpName = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
|
||||
|
|
@ -832,6 +838,7 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
|
||||
$result->setAttribute('buildStderr', $build->getAttribute('stderr', ''));
|
||||
$result->setAttribute('buildStdout', $build->getAttribute('stdout', ''));
|
||||
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
|
|
@ -873,6 +880,11 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId')
|
|||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
$deployment->setAttribute('status', $build->getAttribute('status', 'processing'));
|
||||
$deployment->setAttribute('buildStderr', $build->getAttribute('stderr', ''));
|
||||
$deployment->setAttribute('buildStdout', $build->getAttribute('stdout', ''));
|
||||
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
});
|
||||
|
||||
|
|
@ -1074,7 +1086,7 @@ App::post('/v1/functions/:functionId/executions')
|
|||
'functionId' => $function->getId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => 'http', // http / schedule / event
|
||||
'status' => 'waiting', // waiting / processing / completed / failed
|
||||
'status' => $async ? 'waiting' : 'processing', // waiting / processing / completed / failed
|
||||
'statusCode' => 0,
|
||||
'response' => '',
|
||||
'stderr' => '',
|
||||
|
|
|
|||
|
|
@ -1,24 +1,297 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* 1. Map all objects, object-params, object-fields
|
||||
* 2. Parse GraphQL request payload (use: https://github.com/webonyx/graphql-php)
|
||||
* 3. Route request to relevant controllers (of REST API?) / resolvers and aggergate data
|
||||
* 4. Handle errors if any
|
||||
* 5. Returen JSON response
|
||||
* 6. Write tests!
|
||||
*/
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Promises\Adapter;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema as GQLSchema;
|
||||
use GraphQL\Validator\Rules\DisableIntrospection;
|
||||
use GraphQL\Validator\Rules\QueryComplexity;
|
||||
use GraphQL\Validator\Rules\QueryDepth;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::get('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.hide', true)
|
||||
->label('sdk.description', '/docs/references/graphql/get.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->param('query', '', new Text(0), 'The query to execute.')
|
||||
->param('operationName', '', new Text(256), 'The name of the operation to execute.', true)
|
||||
->param('variables', '', new Text(0), 'The JSON encoded variables to use in the query.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (string $query, string $operationName, string $variables, Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
if (!empty($operationName)) {
|
||||
$query['operationName'] = $operationName;
|
||||
}
|
||||
|
||||
if (!empty($variables)) {
|
||||
$query['variables'] = \json_decode($variables, true);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql/mutation')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.method', 'mutation')
|
||||
->label('sdk.methodType', 'graphql')
|
||||
->label('sdk.description', '/docs/references/graphql/post.md')
|
||||
->label('sdk.parameters', [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
])
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = $request->getParams();
|
||||
|
||||
if ($request->getHeader('x-sdk-graphql') == 'true') {
|
||||
$query = $query['query'];
|
||||
}
|
||||
|
||||
$type = $request->getHeader('content-type');
|
||||
|
||||
if (\str_starts_with($type, 'application/graphql')) {
|
||||
$query = parseGraphql($request);
|
||||
}
|
||||
|
||||
if (\str_starts_with($type, 'multipart/form-data')) {
|
||||
$query = parseMultipart($query, $request);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['api', 'graphql'])
|
||||
->label('scope', 'public')
|
||||
->action(
|
||||
function () {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GraphQL support is coming soon!', 503);
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.method', 'query')
|
||||
->label('sdk.methodType', 'graphql')
|
||||
->label('sdk.description', '/docs/references/graphql/post.md')
|
||||
->label('sdk.parameters', [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
])
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = $request->getParams();
|
||||
|
||||
if ($request->getHeader('x-sdk-graphql') == 'true') {
|
||||
$query = $query['query'];
|
||||
}
|
||||
|
||||
$type = $request->getHeader('content-type');
|
||||
|
||||
if (\str_starts_with($type, 'application/graphql')) {
|
||||
$query = parseGraphql($request);
|
||||
}
|
||||
|
||||
if (\str_starts_with($type, 'multipart/form-data')) {
|
||||
$query = parseMultipart($query, $request);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
/**
|
||||
* Execute a GraphQL request
|
||||
*
|
||||
* @param GQLSchema $schema
|
||||
* @param Adapter $promiseAdapter
|
||||
* @param array $query
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
function execute(
|
||||
GQLSchema $schema,
|
||||
Adapter $promiseAdapter,
|
||||
array $query
|
||||
): array {
|
||||
$maxBatchSize = App::getEnv('_APP_GRAPHQL_MAX_BATCH_SIZE', 10);
|
||||
$maxComplexity = App::getEnv('_APP_GRAPHQL_MAX_COMPLEXITY', 250);
|
||||
$maxDepth = App::getEnv('_APP_GRAPHQL_MAX_DEPTH', 3);
|
||||
|
||||
if (!empty($query) && !isset($query[0])) {
|
||||
$query = [$query];
|
||||
}
|
||||
if (empty($query)) {
|
||||
throw new Exception(Exception::GRAPHQL_NO_QUERY);
|
||||
}
|
||||
if (\count($query) > $maxBatchSize) {
|
||||
throw new Exception(Exception::GRAPHQL_TOO_MANY_QUERIES);
|
||||
}
|
||||
foreach ($query as $item) {
|
||||
if (empty($item['query'])) {
|
||||
throw new Exception(Exception::GRAPHQL_NO_QUERY);
|
||||
}
|
||||
}
|
||||
|
||||
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
|
||||
$validations = GraphQL::getStandardValidationRules();
|
||||
|
||||
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
$validations[] = new DisableIntrospection();
|
||||
$validations[] = new QueryComplexity($maxComplexity);
|
||||
$validations[] = new QueryDepth($maxDepth);
|
||||
}
|
||||
if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
|
||||
$flags = DebugFlag::NONE;
|
||||
}
|
||||
|
||||
$promises = [];
|
||||
foreach ($query as $indexed) {
|
||||
$promises[] = GraphQL::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$indexed['query'],
|
||||
variableValues: $indexed['variables'] ?? null,
|
||||
operationName: $indexed['operationName'] ?? null,
|
||||
validationRules: $validations
|
||||
);
|
||||
}
|
||||
|
||||
$output = [];
|
||||
$wg = new WaitGroup();
|
||||
$wg->add();
|
||||
$promiseAdapter->all($promises)->then(
|
||||
function (array $results) use (&$output, &$wg, $flags) {
|
||||
try {
|
||||
$output = processResult($results, $flags);
|
||||
} finally {
|
||||
$wg->done();
|
||||
}
|
||||
}
|
||||
);
|
||||
$wg->wait();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an "application/graphql" type request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
function parseGraphql(Request $request): array
|
||||
{
|
||||
return ['query' => $request->getRawPayload()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an "multipart/form-data" type request
|
||||
*
|
||||
* @param array $query
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
function parseMultipart(array $query, Request $request): array
|
||||
{
|
||||
$operations = \json_decode($query['operations'], true);
|
||||
$map = \json_decode($query['map'], true);
|
||||
|
||||
foreach ($map as $fileKey => $locations) {
|
||||
foreach ($locations as $location) {
|
||||
$items = &$operations;
|
||||
foreach (\explode('.', $location) as $key) {
|
||||
if (!isset($items[$key]) || !\is_array($items[$key])) {
|
||||
$items[$key] = [];
|
||||
}
|
||||
$items = &$items[$key];
|
||||
}
|
||||
$items = $request->getFiles($fileKey);
|
||||
}
|
||||
}
|
||||
|
||||
$query['query'] = $operations['query'];
|
||||
$query['variables'] = $operations['variables'];
|
||||
|
||||
unset($query['operations']);
|
||||
unset($query['map']);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an array of results for output.
|
||||
*
|
||||
* @param $result
|
||||
* @param $debugFlags
|
||||
* @return array
|
||||
*/
|
||||
function processResult($result, $debugFlags): array
|
||||
{
|
||||
// Only one query, return the result
|
||||
if (!isset($result[1])) {
|
||||
return $result[0]->toArray($debugFlags);
|
||||
}
|
||||
|
||||
// Batched queries, return an array of results
|
||||
return \array_map(
|
||||
static function ($item) use ($debugFlags) {
|
||||
return $item->toArray($debugFlags);
|
||||
},
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
App::shutdown()
|
||||
->groups(['schema'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
Schema::setDirty($project->getId());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ App::get('/v1/locale')
|
|||
$output['countryCode'] = $record['country']['iso_code'];
|
||||
$output['country'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
$output['continent'] = $locale->getText('continents.' . strtolower($record['continent']['code']), $locale->getText('locale.country.unknown'));
|
||||
$output['continent'] = (isset($continents[$record['continent']['code']])) ? $continents[$record['continent']['code']] : $locale->getText('locale.country.unknown');
|
||||
$output['continentCode'] = $record['continent']['code'];
|
||||
$output['eu'] = (\in_array($record['country']['iso_code'], $eu)) ? true : false;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ use Appwrite\Event\Certificate;
|
|||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\Network\Validator\Domain as DomainValidator;
|
||||
use Utopia\Validator\Domain as DomainValidator;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\URL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
|
|
@ -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;
|
||||
|
|
@ -55,9 +56,10 @@ App::post('/v1/projects')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('projectId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
|
||||
->param('teamId', '', new UID(), 'Team unique ID.')
|
||||
->param('region', App::getEnv('_APP_REGION', 'default'), new Whitelist(array_keys(array_filter(Config::getParam('regions'), fn($config) => !$config['disabled']))), 'Project Region.', true)
|
||||
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
|
||||
->param('logo', '', new Text(1024), 'Project logo.', true)
|
||||
->param('url', '', new URL(), 'Project URL.', true)
|
||||
|
|
@ -70,7 +72,7 @@ App::post('/v1/projects')
|
|||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $projectId, string $name, string $teamId, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Database $dbForProject) {
|
||||
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Database $dbForProject) {
|
||||
|
||||
$team = $dbForConsole->getDocument('teams', $teamId);
|
||||
|
||||
|
|
@ -79,7 +81,7 @@ App::post('/v1/projects')
|
|||
}
|
||||
|
||||
$auth = Config::getParam('auth', []);
|
||||
$auths = ['limit' => 0];
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
|
||||
foreach ($auth as $index => $method) {
|
||||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
|
|
@ -102,6 +104,7 @@ App::post('/v1/projects')
|
|||
'name' => $name,
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'region' => $region,
|
||||
'description' => $description,
|
||||
'logo' => $logo,
|
||||
'url' => $url,
|
||||
|
|
@ -125,7 +128,7 @@ App::post('/v1/projects')
|
|||
$collections = Config::getParam('collections', []);
|
||||
|
||||
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
||||
$dbForProject->create(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$dbForProject->create();
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$audit->setup();
|
||||
|
|
@ -269,8 +272,8 @@ App::get('/v1/projects/:projectId/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -293,9 +296,10 @@ App::get('/v1/projects/:projectId/usage')
|
|||
'project.$all.network.bandwidth',
|
||||
'project.$all.storage.size',
|
||||
'users.$all.count.total',
|
||||
'collections.$all.count.total',
|
||||
'databases.$all.count.total',
|
||||
'documents.$all.count.total',
|
||||
'executions.$all.compute.total',
|
||||
'buckets.$all.count.total'
|
||||
];
|
||||
|
||||
$stats = [];
|
||||
|
|
@ -325,7 +329,7 @@ App::get('/v1/projects/:projectId/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
@ -344,9 +348,10 @@ App::get('/v1/projects/:projectId/usage')
|
|||
'network' => $stats[$metrics[1]] ?? [],
|
||||
'storage' => $stats[$metrics[2]] ?? [],
|
||||
'users' => $stats[$metrics[3]] ?? [],
|
||||
'collections' => $stats[$metrics[4]] ?? [],
|
||||
'databases' => $stats[$metrics[4]] ?? [],
|
||||
'documents' => $stats[$metrics[5]] ?? [],
|
||||
'executions' => $stats[$metrics[6]] ?? [],
|
||||
'buckets' => $stats[$metrics[7]] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -442,12 +447,13 @@ App::patch('/v1/projects/:projectId/oauth2')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name', false)
|
||||
->param('appId', '', new Text(256), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', '', new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name')
|
||||
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $provider, string $appId, string $secret, Response $response, Database $dbForConsole) {
|
||||
->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
|
|
@ -456,8 +462,18 @@ App::patch('/v1/projects/:projectId/oauth2')
|
|||
}
|
||||
|
||||
$providers = $project->getAttribute('authProviders', []);
|
||||
$providers[$provider . 'Appid'] = $appId;
|
||||
$providers[$provider . 'Secret'] = $secret;
|
||||
|
||||
if ($appId !== null) {
|
||||
$providers[$provider . 'Appid'] = $appId;
|
||||
}
|
||||
|
||||
if ($secret !== null) {
|
||||
$providers[$provider . 'Secret'] = $secret;
|
||||
}
|
||||
|
||||
if ($enabled !== null) {
|
||||
$providers[$provider . 'Enabled'] = $enabled;
|
||||
}
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('authProviders', $providers));
|
||||
|
||||
|
|
@ -495,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'])
|
||||
|
|
@ -529,6 +576,37 @@ App::patch('/v1/projects/:projectId/auth/:method')
|
|||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
->desc('Update Project user sessions limit')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateAuthSessionsLimit')
|
||||
->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('limit', false, new Range(1, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of users allowed in this project. Value allowed is between 1-' . APP_LIMIT_USER_SESSIONS_MAX . '. Default is ' . APP_LIMIT_USER_SESSIONS_DEFAULT)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['maxSessions'] = $limit;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId')
|
||||
->desc('Delete Project')
|
||||
->groups(['api', 'projects'])
|
||||
|
|
@ -561,10 +639,6 @@ App::delete('/v1/projects/:projectId')
|
|||
->setDocument($project)
|
||||
;
|
||||
|
||||
if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project team from DB');
|
||||
}
|
||||
|
||||
if (!$dbForConsole->deleteDocument('projects', $projectId)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project from DB');
|
||||
}
|
||||
|
|
@ -1041,7 +1115,7 @@ App::post('/v1/projects/:projectId/platforms')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PLATFORM)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.')
|
||||
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.')
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
|
|
|
|||
|
|
@ -58,14 +58,14 @@ App::post('/v1/storage/buckets')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUCKET)
|
||||
->param('bucketId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('bucketId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Bucket name')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
|
||||
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', 'none', new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
|
||||
->inject('response')
|
||||
|
|
@ -232,12 +232,12 @@ App::put('/v1/storage/buckets/:bucketId')
|
|||
->label('sdk.response.model', Response::MODEL_BUCKET)
|
||||
->param('bucketId', '', new UID(), 'Bucket unique ID.')
|
||||
->param('name', null, new Text(128), 'Bucket name', false)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
|
||||
->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', 'none', new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
|
||||
->inject('response')
|
||||
|
|
@ -347,9 +347,9 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('file', [], new File(), 'Binary file.', false)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -417,7 +417,14 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Maximum bucket file size is larger than _APP_STORAGE_LIMIT');
|
||||
}
|
||||
|
||||
|
||||
$file = $request->getFiles('file');
|
||||
|
||||
// GraphQL multipart spec adds files with index keys
|
||||
if (empty($file)) {
|
||||
$file = $request->getFiles(0);
|
||||
}
|
||||
|
||||
if (empty($file)) {
|
||||
throw new Exception(Exception::STORAGE_FILE_EMPTY);
|
||||
}
|
||||
|
|
@ -494,7 +501,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
}
|
||||
|
||||
if ($chunksUploaded === $chunks) {
|
||||
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antivirus', true) && $fileSize <= APP_LIMIT_ANTIVIRUS && App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
|
||||
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antivirus', true) && $fileSize <= APP_LIMIT_ANTIVIRUS && strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) === Storage::DEVICE_LOCAL) {
|
||||
$antivirus = new Network(
|
||||
App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
|
||||
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)
|
||||
|
|
@ -509,14 +516,14 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
$mimeType = $deviceFiles->getFileMimeType($path); // Get mime-type before compression and encryption
|
||||
$data = '';
|
||||
// Compression
|
||||
$algorithm = $bucket->getAttribute('compression', 'none');
|
||||
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != 'none') {
|
||||
$algorithm = $bucket->getAttribute('compression', COMPRESSION_TYPE_NONE);
|
||||
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != COMPRESSION_TYPE_NONE) {
|
||||
$data = $deviceFiles->read($path);
|
||||
switch ($algorithm) {
|
||||
case 'zstd':
|
||||
case COMPRESSION_TYPE_ZSTD:
|
||||
$compressor = new Zstd();
|
||||
break;
|
||||
case 'gzip':
|
||||
case COMPRESSION_TYPE_GZIP:
|
||||
default:
|
||||
$compressor = new GZIP();
|
||||
break;
|
||||
|
|
@ -542,6 +549,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
$sizeActual = $deviceFiles->getFileSize($path);
|
||||
$fileHash = $deviceFiles->getFileHash($path);
|
||||
|
||||
$openSSLVersion = null;
|
||||
$openSSLCipher = null;
|
||||
$openSSLTag = null;
|
||||
$openSSLIV = null;
|
||||
|
||||
if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) {
|
||||
$openSSLVersion = '1';
|
||||
$openSSLCipher = OpenSSL::CIPHER_AES_128_GCM;
|
||||
|
|
@ -783,6 +795,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 +853,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 {
|
||||
|
|
@ -1258,7 +1268,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File unique ID.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission string. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
|
|
@ -1454,8 +1464,8 @@ App::get('/v1/storage/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -1513,7 +1523,7 @@ App::get('/v1/storage/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
@ -1571,8 +1581,8 @@ App::get('/v1/storage/:bucketId/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -1624,7 +1634,7 @@ App::get('/v1/storage/:bucketId/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use Appwrite\Event\Event;
|
|||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Utopia\Validator\Host;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
|
@ -54,7 +54,7 @@ App::post('/v1/teams')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->param('teamId', '', new CustomId(), 'Team ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
|
||||
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
|
||||
->inject('response')
|
||||
|
|
@ -434,14 +434,37 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$url = Template::unParseURL($url);
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
|
||||
$body->setParam('{{owner}}', $user->getAttribute('name'));
|
||||
$body->setParam('{{team}}', $team->getAttribute('name'));
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{name}}', $user->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
$mails
|
||||
->setType(MAIL_TYPE_INVITATION)
|
||||
->setRecipient($email)
|
||||
->setUrl($url)
|
||||
->setName($name)
|
||||
->setLocale($locale->default)
|
||||
->setTeam($team)
|
||||
->setUser($user)
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name'))
|
||||
->trigger()
|
||||
;
|
||||
}
|
||||
|
|
@ -677,9 +700,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);
|
||||
|
|
@ -723,7 +747,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
$user->setAttribute('emailVerification', true);
|
||||
$user = Authorization::skip(fn() => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
// Log user in
|
||||
|
||||
|
|
@ -731,7 +755,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 +765,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']) : '--',
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
'status' => true,
|
||||
'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null,
|
||||
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
|
|
@ -98,7 +98,7 @@ App::post('/v1/users')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', null, new Email(), 'User email.', true)
|
||||
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true)
|
||||
|
|
@ -129,7 +129,7 @@ App::post('/v1/users/bcrypt')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
|
|
@ -159,7 +159,7 @@ App::post('/v1/users/md5')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using MD5.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
|
|
@ -189,7 +189,7 @@ App::post('/v1/users/argon2')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Argon2.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
|
|
@ -219,7 +219,7 @@ App::post('/v1/users/sha')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using SHA.')
|
||||
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
|
||||
|
|
@ -256,7 +256,7 @@ App::post('/v1/users/phpass')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using PHPass.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
|
|
@ -286,7 +286,7 @@ App::post('/v1/users/scrypt')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Scrypt.')
|
||||
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
|
||||
|
|
@ -329,7 +329,7 @@ App::post('/v1/users/scrypt-modified')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Scrypt Modified.')
|
||||
->param('passwordSalt', '', new Text(128), 'Salt used to hash password.')
|
||||
|
|
@ -414,7 +414,7 @@ App::get('/v1/users/:userId')
|
|||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
|
|
@ -1004,7 +1004,8 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
|
|||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $sessionId);
|
||||
->setParam('sessionId', $sessionId)
|
||||
->setPayload($response->output($session, Response::MODEL_SESSION));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
|
@ -1116,8 +1117,8 @@ App::get('/v1/users/usage')
|
|||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$periods = [
|
||||
'24h' => [
|
||||
'period' => '30m',
|
||||
'limit' => 48,
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
'7d' => [
|
||||
'period' => '1d',
|
||||
|
|
@ -1171,7 +1172,7 @@ App::get('/v1/users/usage')
|
|||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1h' => 3600,
|
||||
'1d' => 86400,
|
||||
};
|
||||
$stats[$metric][] = [
|
||||
|
|
|
|||
|
|
@ -223,7 +223,9 @@ App::init()
|
|||
|
||||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->getProtocol() === 'https') {
|
||||
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +233,7 @@ App::init()
|
|||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
|
@ -382,7 +384,7 @@ App::options()
|
|||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
|
@ -394,20 +396,14 @@ App::error()
|
|||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->inject('project')
|
||||
->inject('logger')
|
||||
->inject('loggerBreadcrumbs')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
|
||||
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->match($request);
|
||||
|
||||
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
|
||||
if ($error instanceof PDOException) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
if ($logger) {
|
||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
try {
|
||||
|
|
@ -543,9 +539,10 @@ App::error()
|
|||
$template = ($route) ? $route->getLabel('error', null) : null;
|
||||
|
||||
if ($template) {
|
||||
$comp = new View($template);
|
||||
$layout = new View($template);
|
||||
|
||||
$comp
|
||||
$layout
|
||||
->setParam('title', $project->getAttribute('name') . ' - Error')
|
||||
->setParam('development', App::isDevelopment())
|
||||
->setParam('projectName', $project->getAttribute('name'))
|
||||
->setParam('projectURL', $project->getAttribute('url'))
|
||||
|
|
@ -554,14 +551,6 @@ App::error()
|
|||
->setParam('trace', $trace)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', $project->getAttribute('name') . ' - Error')
|
||||
->setParam('description', 'No Description')
|
||||
->setParam('body', $comp)
|
||||
->setParam('version', $version)
|
||||
->setParam('litespeed', false)
|
||||
;
|
||||
|
||||
$response->html($layout->render());
|
||||
}
|
||||
|
||||
|
|
@ -571,32 +560,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')
|
||||
|
|
@ -668,7 +631,6 @@ App::get('/.well-known/acme-challenge')
|
|||
});
|
||||
|
||||
include_once __DIR__ . '/shared/api.php';
|
||||
include_once __DIR__ . '/shared/web.php';
|
||||
|
||||
foreach (Config::getParam('services', []) as $service) {
|
||||
include_once $service['controller'];
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ global $utopia, $request, $response;
|
|||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Utopia\Validator\Host;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
|
|
|||
|
|
@ -6,16 +6,17 @@ use Appwrite\Event\Database as EventDatabase;
|
|||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\App;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
|
|
@ -47,6 +48,47 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
|
|||
return $label;
|
||||
};
|
||||
|
||||
$databaseListener = function (string $event, Document $document, Stats $usage) {
|
||||
$multiplier = 1;
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$multiplier = -1;
|
||||
}
|
||||
|
||||
$collection = $document->getCollection();
|
||||
switch ($collection) {
|
||||
case 'users':
|
||||
$usage->setParam('users.{scope}.count.total', 1 * $multiplier);
|
||||
break;
|
||||
case 'databases':
|
||||
$usage->setParam('databases.{scope}.count.total', 1 * $multiplier);
|
||||
break;
|
||||
case 'buckets':
|
||||
$usage->setParam('buckets.{scope}.count.total', 1 * $multiplier);
|
||||
break;
|
||||
case 'deployments':
|
||||
$usage->setParam('deployments.{scope}.storage.size', $document->getAttribute('size') * $multiplier);
|
||||
break;
|
||||
default:
|
||||
if (strpos($collection, 'bucket_') === 0) {
|
||||
$usage
|
||||
->setParam('bucketId', $document->getAttribute('bucketId'))
|
||||
->setParam('files.{scope}.storage.size', $document->getAttribute('sizeOriginal') * $multiplier)
|
||||
->setParam('files.{scope}.count.total', 1 * $multiplier);
|
||||
} elseif (strpos($collection, 'database_') === 0) {
|
||||
$usage
|
||||
->setParam('databaseId', $document->getAttribute('databaseId'));
|
||||
if (strpos($collection, '_collection_') !== false) {
|
||||
$usage
|
||||
->setParam('collectionId', $document->getAttribute('$collectionId'))
|
||||
->setParam('documents.{scope}.count.total', 1 * $multiplier);
|
||||
} else {
|
||||
$usage->setParam('collections.{scope}.count.total', 1 * $multiplier);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
|
|
@ -56,13 +98,12 @@ App::init()
|
|||
->inject('user')
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->inject('mails')
|
||||
->inject('usage')
|
||||
->inject('deletes')
|
||||
->inject('database')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
|
|
@ -136,10 +177,6 @@ App::init()
|
|||
->setProject($project)
|
||||
->setUser($user);
|
||||
|
||||
$mails
|
||||
->setProject($project)
|
||||
->setUser($user);
|
||||
|
||||
$audits
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
|
|
@ -149,6 +186,7 @@ App::init()
|
|||
->setUser($user);
|
||||
|
||||
$usage
|
||||
->setParam('projectInternalId', $project->getInternalId())
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('project.{scope}.network.requests', 1)
|
||||
->setParam('httpMethod', $request->getMethod())
|
||||
|
|
@ -158,22 +196,59 @@ App::init()
|
|||
$deletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
|
||||
$dbForProject->on(Database::EVENT_DOCUMENT_CREATE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
||||
|
||||
$dbForProject->on(Database::EVENT_DOCUMENT_DELETE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
||||
|
||||
$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']))
|
||||
;
|
||||
|
||||
|
|
@ -238,6 +313,45 @@ App::init()
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Limit user session
|
||||
*
|
||||
* Delete older sessions if the number of sessions have crossed
|
||||
* the session limit set for the project
|
||||
*/
|
||||
App::shutdown()
|
||||
->groups(['session'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
|
||||
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
|
||||
$session = $response->getPayload();
|
||||
$userId = $session['userId'] ?? '';
|
||||
if (empty($userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
if ($user->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$count = \count($sessions);
|
||||
if ($count <= $sessionLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < ($count - $sessionLimit); $i++) {
|
||||
$session = array_shift($sessions);
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $userId);
|
||||
});
|
||||
|
||||
App::shutdown()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
|
|
@ -361,7 +475,7 @@ App::shutdown()
|
|||
*/
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
$resource = null;
|
||||
$resource = $resourceType = null;
|
||||
$data = $response->getPayload();
|
||||
|
||||
if (!empty($data['payload'])) {
|
||||
|
|
@ -370,9 +484,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']),
|
||||
]) ;
|
||||
|
||||
|
|
@ -404,7 +525,6 @@ App::shutdown()
|
|||
if (
|
||||
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
||||
&& $project->getId()
|
||||
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
|
||||
&& !empty($route->getLabel('sdk.namespace', null))
|
||||
) { // Don't calculate console usage on admin mode
|
||||
$metric = $route->getLabel('usage.metric', '');
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\View;
|
||||
|
||||
App::init()
|
||||
->groups(['web'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (App $utopia, Request $request, Response $response, View $layout) {
|
||||
/* AJAX check */
|
||||
if (!empty($request->getQuery('version', ''))) {
|
||||
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
|
||||
}
|
||||
|
||||
$port = $request->getPort();
|
||||
$protocol = $request->getProtocol();
|
||||
$domain = $request->getHostname();
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('protocol', $protocol)
|
||||
->setParam('domain', $domain)
|
||||
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
|
||||
->setParam('home', App::getEnv('_APP_HOME'))
|
||||
->setParam('setup', App::getEnv('_APP_SETUP'))
|
||||
->setParam('class', 'unknown')
|
||||
->setParam('icon', '/images/favicon.png')
|
||||
->setParam('roles', [
|
||||
['type' => 'owner', 'label' => 'Owner'],
|
||||
['type' => 'developer', 'label' => 'Developer'],
|
||||
['type' => 'admin', 'label' => 'Admin'],
|
||||
])
|
||||
->setParam('runtimes', Config::getParam('runtimes'))
|
||||
->setParam('mode', App::getMode())
|
||||
;
|
||||
|
||||
$time = (60 * 60 * 24 * 45); // 45 days cache
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
|
||||
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
|
||||
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
|
||||
;
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
|
||||
|
||||
$scope = $route->getLabel('scope', '');
|
||||
|
||||
$layout
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
->setParam('isDev', App::isDevelopment())
|
||||
->setParam('class', $scope)
|
||||
;
|
||||
});
|
||||
|
|
@ -1,605 +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');
|
||||
|
||||
$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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -119,7 +119,7 @@ function logError(Throwable $error, string $action, Utopia\Route $route = null)
|
|||
|
||||
function getStorageDevice($root): Device
|
||||
{
|
||||
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
|
||||
switch (strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL))) {
|
||||
case Storage::DEVICE_LOCAL:
|
||||
default:
|
||||
return new Local($root);
|
||||
|
|
|
|||
11
app/http.php
11
app/http.php
|
|
@ -52,7 +52,7 @@ $http->on('AfterReload', function ($server, $workerId) {
|
|||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
Files::load(__DIR__ . '/../public');
|
||||
Files::load(__DIR__ . '/../console');
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
try {
|
||||
$redis->flushAll();
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$dbForConsole->create();
|
||||
} catch (\Exception $e) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
|
@ -317,13 +317,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
Console::error('[Error] File: ' . $th->getFile());
|
||||
Console::error('[Error] Line: ' . $th->getLine());
|
||||
|
||||
/**
|
||||
* Reset Database connection if PDOException was thrown.
|
||||
*/
|
||||
if ($th instanceof PDOException) {
|
||||
$db = null;
|
||||
}
|
||||
|
||||
$swooleResponse->setStatusCode(500);
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
|
|
|
|||
181
app/init.php
181
app/init.php
|
|
@ -18,17 +18,9 @@ ini_set('display_startup_errors', 1);
|
|||
ini_set('default_socket_timeout', -1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
use Appwrite\Extend\PDO;
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\SMS\Adapter\Mock;
|
||||
use Appwrite\SMS\Adapter\Telesign;
|
||||
use Appwrite\SMS\Adapter\TextMagic;
|
||||
use Appwrite\SMS\Adapter\Twilio;
|
||||
use Appwrite\SMS\Adapter\Msg91;
|
||||
use Appwrite\SMS\Adapter\Vonage;
|
||||
use Appwrite\DSN\DSN;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
|
|
@ -36,43 +28,53 @@ use Appwrite\Event\Delete;
|
|||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\PDO;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Registry\Registry;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Database\RedisConfig;
|
||||
use Swoole\Database\RedisPool;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
use Utopia\Messaging\Adapters\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapters\SMS\Telesign;
|
||||
use Utopia\Messaging\Adapters\SMS\TextMagic;
|
||||
use Utopia\Messaging\Adapters\SMS\Twilio;
|
||||
use Utopia\Messaging\Adapters\SMS\Vonage;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
use Utopia\Storage\Device\DOSpaces;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Device\S3;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Wasabi;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
|
|
@ -84,6 +86,8 @@ const APP_MODE_ADMIN = 'admin';
|
|||
const APP_PAGING_LIMIT = 12;
|
||||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10000;
|
||||
const APP_LIMIT_USER_SESSIONS_MAX = 100;
|
||||
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
|
||||
const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
|
||||
const APP_LIMIT_ENCRYPTION = 20000000; //20MB
|
||||
const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
||||
|
|
@ -92,10 +96,11 @@ const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element
|
|||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
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.2.0';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
|
@ -188,6 +193,7 @@ Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
|
|||
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
|
||||
Config::load('services', __DIR__ . '/config/services.php'); // List of services
|
||||
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
|
||||
Config::load('regions', __DIR__ . '/config/regions.php'); // List of available regions
|
||||
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
|
||||
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
|
||||
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
|
||||
|
|
@ -599,7 +605,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-2023-01.mmdb');
|
||||
});
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
|
|
@ -628,6 +634,9 @@ $register->set('cache', function () {
|
|||
|
||||
return $redis;
|
||||
});
|
||||
$register->set('promiseAdapter', function () {
|
||||
return new Swoole();
|
||||
});
|
||||
|
||||
/*
|
||||
* Localization
|
||||
|
|
@ -729,13 +738,6 @@ App::setResource('loggerBreadcrumbs', function () {
|
|||
|
||||
App::setResource('register', fn() => $register);
|
||||
|
||||
App::setResource('layout', function ($locale) {
|
||||
$layout = new View(__DIR__ . '/views/layouts/default.phtml');
|
||||
$layout->setParam('locale', $locale);
|
||||
|
||||
return $layout;
|
||||
}, ['locale']);
|
||||
|
||||
App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en')));
|
||||
|
||||
// Queues
|
||||
|
|
@ -753,7 +755,7 @@ App::setResource('clients', function ($request, $console, $project) {
|
|||
$console->setAttribute('platforms', [ // Always allow current host
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Current Host',
|
||||
'type' => 'web',
|
||||
'type' => Origin::CLIENT_TYPE_WEB,
|
||||
'hostname' => $request->getHostname(),
|
||||
], Document::SET_TYPE_APPEND);
|
||||
|
||||
|
|
@ -765,7 +767,7 @@ App::setResource('clients', function ($request, $console, $project) {
|
|||
fn ($node) => $node['hostname'],
|
||||
\array_filter(
|
||||
$console->getAttribute('platforms', []),
|
||||
fn ($node) => (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname']))
|
||||
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && isset($node['hostname']) && !empty($node['hostname']))
|
||||
)
|
||||
);
|
||||
|
||||
|
|
@ -776,7 +778,7 @@ App::setResource('clients', function ($request, $console, $project) {
|
|||
fn ($node) => $node['hostname'],
|
||||
\array_filter(
|
||||
$project->getAttribute('platforms', []),
|
||||
fn ($node) => (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname']))
|
||||
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB || $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && isset($node['hostname']) && !empty($node['hostname']))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -796,9 +798,11 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
Authorization::setDefaultStatus(true);
|
||||
|
||||
Auth::setCookieName('a_session_' . $project->getId());
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
Auth::setCookieName('a_session_' . $console->getId());
|
||||
$authDuration = Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession(
|
||||
|
|
@ -837,7 +841,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
|
||||
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']);
|
||||
}
|
||||
|
|
@ -907,7 +911,7 @@ App::setResource('console', function () {
|
|||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Localhost',
|
||||
'type' => 'web',
|
||||
'type' => Origin::CLIENT_TYPE_WEB,
|
||||
'hostname' => 'localhost',
|
||||
], // Current host is added on app init
|
||||
],
|
||||
|
|
@ -919,6 +923,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)) : [],
|
||||
|
|
@ -964,7 +969,7 @@ App::setResource('deviceBuilds', function ($project) {
|
|||
|
||||
function getDevice($root): Device
|
||||
{
|
||||
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
|
||||
switch (strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL))) {
|
||||
case Storage::DEVICE_LOCAL:
|
||||
default:
|
||||
return new Local($root);
|
||||
|
|
@ -1048,3 +1053,93 @@ App::setResource('servers', function () {
|
|||
|
||||
return $languages;
|
||||
});
|
||||
|
||||
App::setResource('promiseAdapter', function ($register) {
|
||||
return $register->get('promiseAdapter');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
|
||||
$complexity = function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
$query = Query::getByType($queries, Query::TYPE_LIMIT)[0] ?? null;
|
||||
$limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
|
||||
|
||||
return $complexity * $limit;
|
||||
};
|
||||
|
||||
$attributes = function (int $limit, int $offset) use ($dbForProject) {
|
||||
$attrs = Authorization::skip(fn() => $dbForProject->find('attributes', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]));
|
||||
|
||||
return \array_map(function ($attr) {
|
||||
return $attr->getArrayCopy();
|
||||
}, $attrs);
|
||||
};
|
||||
|
||||
$urls = [
|
||||
'list' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents";
|
||||
},
|
||||
'create' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents";
|
||||
},
|
||||
'read' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
'update' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
'delete' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
];
|
||||
|
||||
$params = [
|
||||
'list' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return [ 'queries' => $args['queries']];
|
||||
},
|
||||
'create' => function (string $databaseId, string $collectionId, array $args) {
|
||||
$id = $args['id'] ?? 'unique()';
|
||||
$permissions = $args['permissions'] ?? null;
|
||||
|
||||
unset($args['id']);
|
||||
unset($args['permissions']);
|
||||
|
||||
// Order must be the same as the route params
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'documentId' => $id,
|
||||
'collectionId' => $collectionId,
|
||||
'data' => $args,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
},
|
||||
'update' => function (string $databaseId, string $collectionId, array $args) {
|
||||
$documentId = $args['id'];
|
||||
$permissions = $args['permissions'] ?? null;
|
||||
|
||||
unset($args['id']);
|
||||
unset($args['permissions']);
|
||||
|
||||
// Order must be the same as the route params
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'documentId' => $documentId,
|
||||
'data' => $args,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
},
|
||||
];
|
||||
|
||||
return Schema::build(
|
||||
$utopia,
|
||||
$complexity,
|
||||
$attributes,
|
||||
$urls,
|
||||
$params,
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ foreach (
|
|||
realpath(__DIR__ . '/../vendor/symfony'),
|
||||
realpath(__DIR__ . '/../vendor/mongodb'),
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/websocket'), // TODO: remove workerman autoload
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/cache'), // TODO: remove memcache autoload
|
||||
] as $key => $value
|
||||
) {
|
||||
if ($value !== false) {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
'data' => [
|
||||
'events' => ['stats.connections'],
|
||||
'channels' => ['project'],
|
||||
'timestamp' => DateTime::now(),
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => [
|
||||
$projectId => $payload[$projectId]
|
||||
]
|
||||
|
|
@ -264,7 +264,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
'data' => [
|
||||
'events' => ['test.event'],
|
||||
'channels' => ['tests'],
|
||||
'timestamp' => DateTime::now(),
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => $payload
|
||||
]
|
||||
];
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -78,12 +78,11 @@ $cli
|
|||
->trigger();
|
||||
}
|
||||
|
||||
function notifyDeleteUsageStats(int $interval30m, int $interval1d)
|
||||
function notifyDeleteUsageStats(int $usageStatsRetentionHourly)
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_USAGE)
|
||||
->setDateTime1d(DateTime::addSeconds(new \DateTime(), -1 * $interval1d))
|
||||
->setDateTime30m(DateTime::addSeconds(new \DateTime(), -1 * $interval30m))
|
||||
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +98,6 @@ $cli
|
|||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_SESSIONS)
|
||||
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * Auth::TOKEN_EXPIRATION_LOGIN_LONG))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
@ -144,11 +142,11 @@ $cli
|
|||
$executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600');
|
||||
$auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600');
|
||||
$abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400');
|
||||
$usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600'); //36 hours
|
||||
$usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days
|
||||
$usageStatsRetentionHourly = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_HOURLY', '8640000'); //100 days
|
||||
|
||||
$cacheRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
|
||||
|
||||
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d, $cacheRetention) {
|
||||
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetentionHourly, $cacheRetention) {
|
||||
$database = getConsoleDB();
|
||||
|
||||
$time = DateTime::now();
|
||||
|
|
@ -157,7 +155,7 @@ $cli
|
|||
notifyDeleteExecutionLogs($executionLogsRetention);
|
||||
notifyDeleteAbuseLogs($abuseLogsRetention);
|
||||
notifyDeleteAuditLogs($auditLogRetention);
|
||||
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
|
||||
notifyDeleteUsageStats($usageStatsRetentionHourly);
|
||||
notifyDeleteConnections();
|
||||
notifyDeleteExpiredSessions();
|
||||
renewCertificates($database);
|
||||
|
|
|
|||
|
|
@ -9,10 +9,20 @@ use Utopia\Cache\Cache;
|
|||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
function clearProjectsCache(Redis $redis, Document $project)
|
||||
{
|
||||
try {
|
||||
$redis->del($redis->keys("cache-_{$project->getInternalId()}:*"));
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to clear project ("' . $project->getId() . '") cache with error: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$cli
|
||||
->task('migrate')
|
||||
->param('version', APP_VERSION_STABLE, new Text(32), 'Version to migrate to.', true)
|
||||
|
|
@ -30,7 +40,7 @@ $cli
|
|||
|
||||
$db = $register->get('db', true);
|
||||
$redis = $register->get('cache', true);
|
||||
$redis->flushAll();
|
||||
|
||||
$cache = new Cache(new RedisCache($redis));
|
||||
|
||||
$projectDB = new Database(new MariaDB($db), $cache);
|
||||
|
|
@ -70,6 +80,8 @@ $cli
|
|||
continue;
|
||||
}
|
||||
|
||||
clearProjectsCache($redis, $project);
|
||||
|
||||
try {
|
||||
$migration
|
||||
->setProject($project, $projectDB, $consoleDB)
|
||||
|
|
@ -78,6 +90,8 @@ $cli
|
|||
throw $th;
|
||||
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
|
||||
}
|
||||
|
||||
clearProjectsCache($redis, $project);
|
||||
}
|
||||
|
||||
$sum = \count($projects);
|
||||
|
|
@ -90,6 +104,5 @@ $cli
|
|||
}
|
||||
|
||||
Swoole\Event::wait(); // Wait for Coroutines to finish
|
||||
$redis->flushAll();
|
||||
Console::success('Data Migration Completed');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Spec\Swagger2;
|
||||
use Appwrite\SDK\SDK;
|
||||
use Appwrite\SDK\Language\Android;
|
||||
use Appwrite\SDK\Language\CLI;
|
||||
use Appwrite\SDK\Language\PHP;
|
||||
use Appwrite\SDK\Language\Web;
|
||||
use Appwrite\SDK\Language\Node;
|
||||
use Appwrite\SDK\Language\Python;
|
||||
use Appwrite\SDK\Language\Ruby;
|
||||
use Appwrite\SDK\Language\Dart;
|
||||
use Appwrite\SDK\Language\Deno;
|
||||
use Appwrite\SDK\Language\DotNet;
|
||||
use Appwrite\SDK\Language\Flutter;
|
||||
use Appwrite\SDK\Language\Go;
|
||||
use Appwrite\SDK\Language\GraphQL;
|
||||
use Appwrite\SDK\Language\Kotlin;
|
||||
use Appwrite\SDK\Language\Android;
|
||||
use Appwrite\SDK\Language\Node;
|
||||
use Appwrite\SDK\Language\PHP;
|
||||
use Appwrite\SDK\Language\Python;
|
||||
use Appwrite\SDK\Language\REST;
|
||||
use Appwrite\SDK\Language\Ruby;
|
||||
use Appwrite\SDK\Language\Swift;
|
||||
use Appwrite\SDK\Language\SwiftClient;
|
||||
use Appwrite\SDK\Language\Apple;
|
||||
use Appwrite\SDK\Language\Web;
|
||||
use Appwrite\SDK\SDK;
|
||||
use Appwrite\Spec\Swagger2;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
|
||||
$cli
|
||||
->task('sdks')
|
||||
|
|
@ -30,7 +32,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', '1.2.x', 'latest'])) {
|
||||
throw new Exception('Unknown version given');
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +150,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
$warning = $warning . "\n\n > This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)";
|
||||
break;
|
||||
case 'apple':
|
||||
$config = new SwiftClient();
|
||||
$config = new Apple();
|
||||
break;
|
||||
case 'dotnet':
|
||||
$cover = '';
|
||||
|
|
@ -161,9 +163,14 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
$config = new Kotlin();
|
||||
$warning = $warning . "\n\n > This is the Kotlin SDK for integrating with Appwrite from your Kotlin server-side code. If you're looking for the Android SDK you should check [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)";
|
||||
break;
|
||||
case 'graphql':
|
||||
$config = new GraphQL();
|
||||
break;
|
||||
case 'rest':
|
||||
$config = new REST();
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Language "' . $language['key'] . '" not supported');
|
||||
break;
|
||||
}
|
||||
|
||||
Console::info("Generating {$language['name']} SDK...");
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
global $cli, $register;
|
||||
|
||||
use Appwrite\Stats\Usage;
|
||||
use Appwrite\Stats\UsageDB;
|
||||
use Appwrite\Usage\Calculators\Aggregator;
|
||||
use Appwrite\Usage\Calculators\Database;
|
||||
use Appwrite\Usage\Calculators\TimeSeries;
|
||||
use InfluxDB\Database as InfluxDatabase;
|
||||
use Utopia\App;
|
||||
|
|
@ -114,63 +110,29 @@ $logError = function (Throwable $error, string $action = 'syncUsageStats') use (
|
|||
Console::warning($error->getTraceAsString());
|
||||
};
|
||||
|
||||
|
||||
function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB, callable $logError): void
|
||||
{
|
||||
$interval = (int) App::getEnv('_APP_USAGE_TIMESERIES_INTERVAL', '30'); // 30 seconds (by default)
|
||||
$usage = new TimeSeries($database, $influxDB, $logError);
|
||||
|
||||
Console::loop(function () use ($interval, $usage) {
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds");
|
||||
$loopStart = microtime(true);
|
||||
|
||||
$usage->collect();
|
||||
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
|
||||
}, $interval);
|
||||
}
|
||||
|
||||
function aggregateDatabase(UtopiaDatabase $database, callable $logError): void
|
||||
{
|
||||
$interval = (int) App::getEnv('_APP_USAGE_DATABASE_INTERVAL', '900'); // 15 minutes (by default)
|
||||
$usage = new Database($database, $logError);
|
||||
$aggregrator = new Aggregator($database, $logError);
|
||||
|
||||
Console::loop(function () use ($interval, $usage, $aggregrator) {
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregating database usage every {$interval} seconds.");
|
||||
$loopStart = microtime(true);
|
||||
$usage->collect();
|
||||
$aggregrator->collect();
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
|
||||
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
|
||||
}, $interval);
|
||||
}
|
||||
|
||||
$cli
|
||||
->task('usage')
|
||||
->param('type', 'timeseries', new WhiteList(['timeseries', 'database']))
|
||||
->desc('Schedules syncing data from influxdb to Appwrite console db')
|
||||
->action(function (string $type) use ($register, $logError) {
|
||||
->action(function () use ($register, $logError) {
|
||||
Console::title('Usage Aggregation V1');
|
||||
Console::success(APP_NAME . ' usage aggregation process v1 has started');
|
||||
|
||||
$database = getDatabase($register, '_console');
|
||||
$influxDB = getInfluxDB($register);
|
||||
|
||||
switch ($type) {
|
||||
case 'timeseries':
|
||||
aggregateTimeseries($database, $influxDB, $logError);
|
||||
break;
|
||||
case 'database':
|
||||
aggregateDatabase($database, $logError);
|
||||
break;
|
||||
default:
|
||||
Console::error("Unsupported usage aggregation type");
|
||||
}
|
||||
$interval = (int) App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '30'); // 30 seconds (by default)
|
||||
$region = App::getEnv('region', 'default');
|
||||
$usage = new TimeSeries($region, $database, $influxDB, $logError);
|
||||
|
||||
Console::loop(function () use ($interval, $usage) {
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds");
|
||||
$loopStart = microtime(true);
|
||||
|
||||
$usage->collect();
|
||||
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
|
||||
}, $interval);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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> <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> <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>
|
||||
|
||||
<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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,259 +0,0 @@
|
|||
<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 <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> 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> 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 margin-bottom-xl" name="name" required autocomplete="off" maxlength="128" />
|
||||
|
||||
<footer>
|
||||
<button type="submit">Create</button> <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>
|
||||
|
|
@ -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> <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> Unknown</span>
|
||||
<span data-ls-if="{{log.userName|escape}} === '' && {{log.userEmail}} === '' && {{log.mode}} !== 'key'"><i class="icon-user"></i> 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> 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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}}"> </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"> </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> <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> </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>
|
||||
|
|
@ -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"> </span>
|
||||
<span data-ls-if="(!{{project-document.$id}})" data-ls-bind="Document"> </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>
|
||||
|
|
@ -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>
|
||||
<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"> (Array)</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if($required): ?>
|
||||
<div class="text-size-xs text-danger text-fade"> required</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if(!$required): ?>
|
||||
<div class="text-size-xs text-fade"> 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>
|
||||
|
|
@ -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"> </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> <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>
|
||||
|
|
@ -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}} "> </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> </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> <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>
|
||||
<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=" | Created {{deployment.$createdAt|timeSince}} | {{deployment.size|humanFileSize}}{{deployment.size|humanFileUnit}}"></span>
|
||||
|
||||
<span data-ls-if="{{deployment.status}} == 'failed'"> | <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'"> | <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}}" />
|
||||
|
||||
| <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> <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> <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> </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> <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> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</footer>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -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> <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>
|
||||
|
|
@ -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}}"> </span>
|
||||
</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> Settings</a> </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> API Keys</a> </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> Webhooks</a> </li>
|
||||
</ul>
|
||||
|
||||
<div class="margin-bottom phones-only"> </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> <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> <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> <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"> iOS </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> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</form>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<h2 style="display: none"> Android </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> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<h2 style="display: none"> Linux </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> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<h2 style="display: none"> MacOS </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> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<h2 style="display: none"> Windows </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> <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"> iOS </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> <button data-ui-modal-close="" type="button" class="reverse">Back</button>
|
||||
</form>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<h2 style="display: none"> macOS </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> <button data-ui-modal-close="" type="button" class="reverse">Back</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<h2 style="display: none"> watchOS </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> <button data-ui-modal-close="" type="button" class="reverse">Back</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<h2 style="display: none"> tvOS </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> <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> <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> <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> <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> <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> <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> <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>
|
||||
|
|
@ -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> 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> 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"> </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"> </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>
|
||||
|
|
@ -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" />
|
||||
|
||||
<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> <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" />
|
||||
|
||||
<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> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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> </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 </span>
|
||||
<span class="text-size-small text-info" data-ls-if="true === {{domain.verification}}"><i class="icon-ok-circled"></i> Verified </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}}"> Pending Verification </span>
|
||||
<span class="text-size-small text-fade" data-ls-if="!{{domain.certificateId}} && true === {{domain.verification}}"> In Progress </span>
|
||||
<span class="text-size-small text-success" data-ls-if="{{domain.certificateId}}"><i class="icon-ok-circled"></i> Enabled </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> <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> <span class="tag" data-ls-bind="{{member.roles.0}}"></span> <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>
|
||||
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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}}"> </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> <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> <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> </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}}" /> 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}}" /> 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}}" /> 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>
|
||||
|
||||
|
|
@ -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"> </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> <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>
|
||||
|
|
@ -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> <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> <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> <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> <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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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}}" />
|
||||
|
|
@ -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}}" />
|
||||
|
|
@ -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}}" />
|
||||
|
|
@ -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}}" />
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue