Merge branch 'main' of https://github.com/ente-io/auth into mobile_face
This commit is contained in:
commit
f9dd509d61
341 changed files with 6010 additions and 4347 deletions
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Set line endings of shell scripts to LF, even on Windows, otherwise execution
|
||||
# within Docker fails.
|
||||
*.sh text eol=lf
|
9
.github/workflows/auth-crowdin.yml
vendored
9
.github/workflows/auth-crowdin.yml
vendored
|
@ -3,15 +3,16 @@ name: "Sync Crowdin translations (auth)"
|
|||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when auth's intl_en.arb is changed
|
||||
# Run workflow when auth's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/arb/app_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/auth-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "50 1 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
|
|
6
.github/workflows/auth-lint.yml
vendored
6
.github/workflows/auth-lint.yml
vendored
|
@ -1,11 +1,9 @@
|
|||
name: "Lint (auth)"
|
||||
|
||||
on:
|
||||
# Run on every push to branches (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes auth/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "auth/**"
|
||||
- ".github/workflows/auth-lint.yml"
|
||||
|
|
7
.github/workflows/auth-release.yml
vendored
7
.github/workflows/auth-release.yml
vendored
|
@ -29,7 +29,7 @@ on:
|
|||
- "auth-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
|
@ -118,14 +118,11 @@ jobs:
|
|||
updateOnlyUnreleased: true
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
# Temporarily disable GP upload, enable this once desktop build
|
||||
# testing is complete.
|
||||
if: false
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.auth
|
||||
releaseFiles: build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
releaseFiles: auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
|
||||
build-windows:
|
||||
|
|
2
.github/workflows/cli-release.yml
vendored
2
.github/workflows/cli-release.yml
vendored
|
@ -49,6 +49,6 @@ jobs:
|
|||
project_path: "./cli"
|
||||
pre_command: export CGO_ENABLED=0
|
||||
build_flags: "-trimpath"
|
||||
ldflags: "-s -w"
|
||||
ldflags: "-X main.AppVersion=${{ github.ref_name }} -s -w"
|
||||
md5sum: false
|
||||
sha256sum: true
|
||||
|
|
47
.github/workflows/docs-deploy.yml
vendored
Normal file
47
.github/workflows/docs-deploy.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
name: "Deploy (docs)"
|
||||
|
||||
on:
|
||||
# Run on every push to main that changes docs/
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/docs-deploy.yml"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build production site
|
||||
# Will create docs/.vitepress/dist
|
||||
run: yarn build
|
||||
|
||||
- name: Publish
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: help
|
||||
directory: docs/docs/.vitepress/dist
|
||||
wranglerVersion: "3"
|
37
.github/workflows/docs-verify-build.yml
vendored
Normal file
37
.github/workflows/docs-verify-build.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: "Verify build (docs)"
|
||||
|
||||
# Preflight build of docs. This allows us to ensure that yarn build is
|
||||
# succeeding before we merge the PR into main.
|
||||
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes docs/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/docs-verify-build.yml"
|
||||
|
||||
jobs:
|
||||
verify-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build production site
|
||||
run: yarn build
|
9
.github/workflows/mobile-crowdin.yml
vendored
9
.github/workflows/mobile-crowdin.yml
vendored
|
@ -3,15 +3,16 @@ name: "Sync Crowdin translations (mobile)"
|
|||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when mobiles's intl_en.arb is changed
|
||||
# Run workflow when mobiles's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/intl_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/mobile-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "40 1 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
|
|
6
.github/workflows/mobile-lint.yml
vendored
6
.github/workflows/mobile-lint.yml
vendored
|
@ -1,11 +1,9 @@
|
|||
name: "Lint (mobile)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes mobile/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "mobile/**"
|
||||
- ".github/workflows/mobile-lint.yml"
|
||||
|
|
6
.github/workflows/mobile-release.yml
vendored
6
.github/workflows/mobile-release.yml
vendored
|
@ -39,7 +39,9 @@ jobs:
|
|||
encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }}
|
||||
|
||||
- name: Build independent APK
|
||||
run: flutter build apk --release --flavor independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente.apk
|
||||
run: |
|
||||
flutter build apk --release --flavor independent
|
||||
mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
|
||||
|
@ -52,5 +54,5 @@ jobs:
|
|||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "mobile/build/app/outputs/flutter-apk/ente.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
|
||||
artifacts: "mobile/build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
|
||||
draft: true
|
||||
|
|
6
.github/workflows/server-lint.yml
vendored
6
.github/workflows/server-lint.yml
vendored
|
@ -1,11 +1,9 @@
|
|||
name: "Lint (server)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes server/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "server/**"
|
||||
- ".github/workflows/server-lint.yml"
|
||||
|
|
9
.github/workflows/web-crowdin.yml
vendored
9
.github/workflows/web-crowdin.yml
vendored
|
@ -3,15 +3,16 @@ name: "Sync Crowdin translations (web)"
|
|||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when web's en-US/translation.json is changed
|
||||
# Run workflow when web's en-US/translation.json is changed
|
||||
- "web/apps/photos/public/locales/en-US/translation.json"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/web-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "20 1 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
|
|
43
.github/workflows/web-deploy-accounts.yml
vendored
Normal file
43
.github/workflows/web-deploy-accounts.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
name: "Deploy (accounts)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/accounts
|
||||
branches: [deploy/accounts]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build accounts
|
||||
run: yarn build:accounts
|
||||
|
||||
- name: Publish accounts
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/accounts
|
||||
directory: web/apps/accounts/out
|
||||
wranglerVersion: "3"
|
43
.github/workflows/web-deploy-auth.yml
vendored
Normal file
43
.github/workflows/web-deploy-auth.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
name: "Deploy (auth)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/auth
|
||||
branches: [deploy/auth]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build auth
|
||||
run: yarn build:auth
|
||||
|
||||
- name: Publish auth
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/auth
|
||||
directory: web/apps/auth/out
|
||||
wranglerVersion: "3"
|
43
.github/workflows/web-deploy-cast.yml
vendored
Normal file
43
.github/workflows/web-deploy-cast.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
name: "Deploy (cast)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/cast
|
||||
branches: [deploy/cast]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build cast
|
||||
run: yarn build:cast
|
||||
|
||||
- name: Publish cast
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/cast
|
||||
directory: web/apps/cast/out
|
||||
wranglerVersion: "3"
|
43
.github/workflows/web-deploy-photos.yml
vendored
Normal file
43
.github/workflows/web-deploy-photos.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
name: "Deploy (photos)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/photos
|
||||
branches: [deploy/photos]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build photos
|
||||
run: yarn build:photos
|
||||
|
||||
- name: Publish photos
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/photos
|
||||
directory: web/apps/photos/out
|
||||
wranglerVersion: "3"
|
15
.github/workflows/web-lint.yml
vendored
15
.github/workflows/web-lint.yml
vendored
|
@ -1,20 +1,9 @@
|
|||
name: "Lint (web)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes web/
|
||||
push:
|
||||
# [Note: Specify branch when specifying a path filter]
|
||||
#
|
||||
# Path filters are ignored for tag pushes, which causes this workflow to
|
||||
# always run when we push a tag. Defining an explicit branch solves the
|
||||
# issue. From GitHub's docs:
|
||||
#
|
||||
# > if you define both branches/branches-ignore and paths/paths-ignore,
|
||||
# > the workflow will only run when both filters are satisfied.
|
||||
#
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "web/**"
|
||||
- ".github/workflows/web-lint.yml"
|
||||
|
|
94
.github/workflows/web-nightly.yml
vendored
Normal file
94
.github/workflows/web-nightly.yml
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
name: "Nightly (web)"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# [Note: Run every 24 hours]
|
||||
#
|
||||
# Run every 24 hours - First field is minute, second is hour of the day
|
||||
# This runs 23:15 UTC everyday - 1 and 15 are just arbitrary offset to
|
||||
# avoid scheduling it on the exact hour, as suggested by GitHub.
|
||||
#
|
||||
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
|
||||
# https://crontab.guru/
|
||||
#
|
||||
- cron: "15 23 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build accounts
|
||||
run: yarn build:accounts
|
||||
|
||||
- name: Publish accounts
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-accounts
|
||||
directory: web/apps/accounts/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build auth
|
||||
run: yarn build:auth
|
||||
|
||||
- name: Publish auth
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-auth
|
||||
directory: web/apps/auth/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build cast
|
||||
run: yarn build:cast
|
||||
|
||||
- name: Publish cast
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-cast
|
||||
directory: web/apps/cast/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build photos
|
||||
run: yarn build:photos
|
||||
env:
|
||||
NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT: https://albums.ente.sh
|
||||
|
||||
- name: Publish photos
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-photos
|
||||
directory: web/apps/photos/out
|
||||
wranglerVersion: "3"
|
52
.github/workflows/web-preview.yml
vendored
Normal file
52
.github/workflows/web-preview.yml
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
name: "Preview (web)"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
app:
|
||||
description: "App to build and deploy"
|
||||
type: choice
|
||||
required: true
|
||||
default: "photos"
|
||||
options:
|
||||
- "accounts"
|
||||
- "auth"
|
||||
- "cast"
|
||||
- "photos"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build ${{ inputs.app }}
|
||||
run: yarn build:${{ inputs.app }}
|
||||
|
||||
- name: Publish ${{ inputs.app }} to preview
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: preview
|
||||
directory: web/apps/${{ inputs.app }}/out
|
||||
wranglerVersion: "3"
|
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -9,10 +9,6 @@
|
|||
[submodule "auth/assets/simple-icons"]
|
||||
path = auth/assets/simple-icons
|
||||
url = https://github.com/simple-icons/simple-icons.git
|
||||
[submodule "desktop/thirdparty/next-electron-server"]
|
||||
path = desktop/thirdparty/next-electron-server
|
||||
url = https://github.com/ente-io/next-electron-server.git
|
||||
branch = desktop
|
||||
[submodule "mobile/thirdparty/flutter"]
|
||||
path = mobile/thirdparty/flutter
|
||||
url = https://github.com/flutter/flutter.git
|
||||
|
|
|
@ -50,13 +50,13 @@ Thank you for your support.
|
|||
|
||||
## Document
|
||||
|
||||
_Coming soon!_
|
||||
|
||||
The help guides and FAQs for users of Ente products are also open source, and
|
||||
can be edited in a wiki-esque manner by our community members. More than the
|
||||
quantity, we feel this helps improve the quality and approachability of the
|
||||
documentation by bringing in more diverse viewpoints and familiarity levels.
|
||||
|
||||
See [docs/](docs/README.md) for how to edit these documents.
|
||||
|
||||
## Code contributions
|
||||
|
||||
If you'd like to contribute code, it is best to start small.
|
||||
|
|
|
@ -70,7 +70,7 @@ existing users will be grandfathered in.
|
|||
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
|
||||
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
|
||||
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
|
||||
[<img height="42" src=".github/assets/github-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Av2.0.34&expanded=true)
|
||||
[<img height="42" src=".github/assets/github-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
||||
[<img height="42" src=".github/assets/web-badge.svg">](https://auth.ente.io)
|
||||
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ details as possible about whatever it is that you need help with, and we will
|
|||
get back to you as soon as possible.
|
||||
|
||||
In some cases, your query might already have been answered in our help
|
||||
documentation (_Coming soon!_).
|
||||
documentation at [help.ente.io](https://help.ente.io).
|
||||
|
||||
Other ways to get in touch are:
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ multi-device sync.
|
|||
### Android
|
||||
|
||||
This repository's [GitHub
|
||||
releases](https://github.com/ente-io/ente/releases/latest/download/ente-auth.apk)
|
||||
releases](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
||||
contains APKs, built straight from source. These builds keep themselves updated,
|
||||
without relying on third party stores.
|
||||
|
||||
|
|
|
@ -37,8 +37,7 @@
|
|||
{
|
||||
"title": "BorgBase",
|
||||
"altNames": ["borg"],
|
||||
"slug": "BorgBase",
|
||||
"hex": "222C31"
|
||||
"slug": "BorgBase"
|
||||
},
|
||||
{
|
||||
"title": "Brave Creators",
|
||||
|
@ -109,8 +108,7 @@
|
|||
{
|
||||
"title": "Gosuslugi",
|
||||
"altNames": ["Госуслуги"],
|
||||
"slug": "Gosuslugi",
|
||||
"hex": "EE2F53"
|
||||
"slug": "Gosuslugi"
|
||||
},
|
||||
{
|
||||
"title": "Healthchecks.io",
|
||||
|
@ -127,13 +125,11 @@
|
|||
},
|
||||
{
|
||||
"title": "IVPN",
|
||||
"slug": "IVPN",
|
||||
"hex": "FA3243"
|
||||
"slug": "IVPN"
|
||||
},
|
||||
{
|
||||
"title": "IceDrive",
|
||||
"slug": "Icedrive",
|
||||
"hex": "1F4FD0"
|
||||
"slug": "Icedrive"
|
||||
},
|
||||
{
|
||||
"title": "Jagex",
|
||||
|
@ -154,8 +150,7 @@
|
|||
"title": "Kite"
|
||||
},
|
||||
{
|
||||
"title": "Koofr",
|
||||
"hex": "71BA05"
|
||||
"title": "Koofr"
|
||||
},
|
||||
{
|
||||
"title": "Kraken",
|
||||
|
@ -184,8 +179,7 @@
|
|||
{
|
||||
"title": "Murena",
|
||||
"altNames": ["eCloud"],
|
||||
"slug": "ecloud",
|
||||
"hex": "EC6A55"
|
||||
"slug": "ecloud"
|
||||
},
|
||||
{
|
||||
"title": "Microsoft"
|
||||
|
@ -230,8 +224,7 @@
|
|||
},
|
||||
{
|
||||
"title": "pCloud",
|
||||
"slug": "pCloud",
|
||||
"hex": "1EBCC5"
|
||||
"slug": "pCloud"
|
||||
},
|
||||
{
|
||||
"title": "Peerberry",
|
||||
|
@ -371,8 +364,7 @@
|
|||
{
|
||||
"title": "Yandex",
|
||||
"altNames": ["Ya", "Яндекс"],
|
||||
"slug": "Yandex",
|
||||
"hex": "FC3F1D"
|
||||
"slug": "Yandex"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -71,8 +71,6 @@ PODS:
|
|||
- move_to_background (0.0.1):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner (5.0.11)
|
||||
- open_filex (0.0.2):
|
||||
- Flutter
|
||||
- OrderedSet (5.0.0)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
|
@ -126,7 +124,6 @@ DEPENDENCIES:
|
|||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
|
||||
|
@ -183,8 +180,6 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||
move_to_background:
|
||||
:path: ".symlinks/plugins/move_to_background/ios"
|
||||
open_filex:
|
||||
:path: ".symlinks/plugins/open_filex/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
|
@ -214,7 +209,7 @@ SPEC CHECKSUMS:
|
|||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
|
@ -226,7 +221,6 @@ SPEC CHECKSUMS:
|
|||
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
|
|
|
@ -167,7 +167,7 @@ class SuperLogging {
|
|||
await setupLogDir();
|
||||
}
|
||||
if (sentryIsEnabled) {
|
||||
await setupSentry();
|
||||
setupSentry().ignore();
|
||||
}
|
||||
|
||||
Logger.root.level = Level.ALL;
|
||||
|
@ -250,7 +250,7 @@ class SuperLogging {
|
|||
|
||||
// add error to sentry queue
|
||||
if (sentryIsEnabled && rec.error != null) {
|
||||
await _sendErrorToSentry(rec.error!, null);
|
||||
_sendErrorToSentry(rec.error!, null).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,8 @@
|
|||
"enterCodeHint": "Enter the 6-digit code from\nyour authenticator app",
|
||||
"lostDeviceTitle": "Lost device?",
|
||||
"twoFactorAuthTitle": "Two-factor authentication",
|
||||
"passkeyAuthTitle": "Passkey authentication",
|
||||
"passkeyAuthTitle": "Passkey verification",
|
||||
"verifyPasskey": "Verify passkey",
|
||||
"recoverAccount": "Recover account",
|
||||
"enterRecoveryKeyHint": "Enter your recovery key",
|
||||
"recover": "Recover",
|
||||
|
@ -407,7 +408,7 @@
|
|||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
||||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
|
||||
"waitingForBrowserRequest": "Waiting for browser request...",
|
||||
"launchPasskeyUrlAgain": "Launch passkey URL again",
|
||||
"waitingForVerification": "Waiting for verification...",
|
||||
"passkey": "Passkey",
|
||||
"developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
|
||||
"developerSettings": "Developer settings",
|
||||
|
|
|
@ -59,6 +59,12 @@
|
|||
"recreatePassword": "Återskapa lösenord",
|
||||
"useRecoveryKey": "Använd återställningsnyckel",
|
||||
"incorrectPasswordTitle": "Felaktigt lösenord",
|
||||
"welcomeBack": "Välkommen tillbaka!",
|
||||
"changePassword": "Ändra lösenord",
|
||||
"cancel": "Avbryt",
|
||||
"yes": "Ja",
|
||||
"no": "Nej",
|
||||
"settings": "Inställningar",
|
||||
"pleaseTryAgain": "Försök igen",
|
||||
"existingUser": "Befintlig användare",
|
||||
"delete": "Radera",
|
||||
|
@ -68,9 +74,23 @@
|
|||
"suggestFeatures": "Föreslå funktionalitet",
|
||||
"faq": "FAQ",
|
||||
"faq_q_1": "Hur säkert är ente Auth?",
|
||||
"scan": "Skanna",
|
||||
"twoFactorAuthTitle": "Tvåfaktorsautentisering",
|
||||
"enterRecoveryKeyHint": "Ange din återställningsnyckel",
|
||||
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
|
||||
"enterEmailHint": "Ange din e-postadress",
|
||||
"invalidEmailTitle": "Ogiltig e-postadress",
|
||||
"invalidEmailMessage": "Ange en giltig e-postadress.",
|
||||
"deleteAccount": "Radera konto",
|
||||
"yesSendFeedbackAction": "Ja, skicka feedback",
|
||||
"noDeleteAccountAction": "Nej, radera konto",
|
||||
"createNewAccount": "Skapa nytt konto",
|
||||
"weakStrength": "Svag",
|
||||
"strongStrength": "Stark",
|
||||
"moderateStrength": "Måttligt",
|
||||
"confirmPassword": "Bekräfta lösenord",
|
||||
"close": "Stäng",
|
||||
"language": "Språk",
|
||||
"searchHint": "Sök...",
|
||||
"search": "Sök",
|
||||
"sorryUnableToGenCode": "Tyvärr, det gick inte att generera en kod för {issuerName}",
|
||||
|
@ -83,5 +103,45 @@
|
|||
"copiedNextToClipboard": "Kopierade nästa kod till urklipp",
|
||||
"error": "Fel",
|
||||
"recoveryKeyCopiedToClipboard": "Återställningsnyckel kopierad till urklipp",
|
||||
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel."
|
||||
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel.",
|
||||
"saveKey": "Spara nyckel",
|
||||
"back": "Tillbaka",
|
||||
"createAccount": "Skapa konto",
|
||||
"password": "Lösenord",
|
||||
"privacyPolicyTitle": "Integritetspolicy",
|
||||
"termsOfServicesTitle": "Villkor",
|
||||
"encryption": "Kryptering",
|
||||
"changePasswordTitle": "Ändra lösenord",
|
||||
"resetPasswordTitle": "Återställ lösenord",
|
||||
"encryptionKeys": "Krypteringsnycklar",
|
||||
"continueLabel": "Fortsätt",
|
||||
"logInLabel": "Logga in",
|
||||
"logout": "Logga ut",
|
||||
"areYouSureYouWantToLogout": "Är du säker på att du vill logga ut?",
|
||||
"yesLogout": "Ja, logga ut",
|
||||
"invalidKey": "Ogiltig nyckel",
|
||||
"tryAgain": "Försök igen",
|
||||
"viewRecoveryKey": "Visa återställningsnyckel",
|
||||
"confirmRecoveryKey": "Bekräfta återställningsnyckel",
|
||||
"confirmYourRecoveryKey": "Bekräfta din återställningsnyckel",
|
||||
"confirm": "Bekräfta",
|
||||
"copyEmailAddress": "Kopiera e-postadress",
|
||||
"exportLogs": "Exportera loggar",
|
||||
"enterYourRecoveryKey": "Ange din återställningsnyckel",
|
||||
"about": "Om",
|
||||
"terms": "Villkor",
|
||||
"warning": "Varning",
|
||||
"pendingSyncs": "Varning",
|
||||
"activeSessions": "Aktiva sessioner",
|
||||
"enterPassword": "Ange lösenord",
|
||||
"export": "Exportera",
|
||||
"singIn": "Logga in",
|
||||
"androidCancelButton": "Avbryt",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
}
|
||||
}
|
|
@ -144,6 +144,7 @@
|
|||
"enterCodeHint": "从你的身份验证器应用中\n输入6位数字代码",
|
||||
"lostDeviceTitle": "丢失了设备吗?",
|
||||
"twoFactorAuthTitle": "双因素认证",
|
||||
"passkeyAuthTitle": "通行密钥认证",
|
||||
"recoverAccount": "恢复账户",
|
||||
"enterRecoveryKeyHint": "输入您的恢复密钥",
|
||||
"recover": "恢复",
|
||||
|
@ -404,5 +405,8 @@
|
|||
"signOutOtherDevices": "登出其他设备",
|
||||
"doNotSignOut": "不要退登",
|
||||
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
|
||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!"
|
||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
|
||||
"waitingForBrowserRequest": "正在等待浏览器请求...",
|
||||
"launchPasskeyUrlAgain": "再次启动 通行密钥 URL",
|
||||
"passkey": "通行密钥"
|
||||
}
|
13
auth/lib/models/account/two_factor.dart
Normal file
13
auth/lib/models/account/two_factor.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
enum TwoFactorType { totp, passkey }
|
||||
|
||||
// ToString for TwoFactorType
|
||||
String twoFactorTypeToString(TwoFactorType type) {
|
||||
switch (type) {
|
||||
case TwoFactorType.totp:
|
||||
return "totp";
|
||||
case TwoFactorType.passkey:
|
||||
return "passkey";
|
||||
default:
|
||||
return type.name;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,28 @@ class PasskeyService {
|
|||
return response.data!["accountsToken"] as String;
|
||||
}
|
||||
|
||||
Future<bool> isPasskeyRecoveryEnabled() async {
|
||||
final response = await _enteDio.get(
|
||||
"/users/two-factor/recovery-status",
|
||||
);
|
||||
return response.data!["isPasskeyRecoveryEnabled"] as bool;
|
||||
}
|
||||
|
||||
Future<void> configurePasskeyRecovery(
|
||||
String secret,
|
||||
String userEncryptedSecret,
|
||||
String userSecretNonce,
|
||||
) async {
|
||||
await _enteDio.post(
|
||||
"/users/two-factor/passkeys/configure-recovery",
|
||||
data: {
|
||||
"secret": secret,
|
||||
"userSecretCipher": userEncryptedSecret,
|
||||
"userSecretNonce": userSecretNonce,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> openPasskeyPage(BuildContext context) async {
|
||||
try {
|
||||
final jwtToken = await getJwtToken();
|
||||
|
|
|
@ -11,6 +11,7 @@ import 'package:ente_auth/core/event_bus.dart';
|
|||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/events/user_details_changed_event.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/models/api/user/srp.dart';
|
||||
import 'package:ente_auth/models/delete_account.dart';
|
||||
import 'package:ente_auth/models/key_attributes.dart';
|
||||
|
@ -762,7 +763,11 @@ class UserService {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> recoverTwoFactor(BuildContext context, String sessionID) async {
|
||||
Future<void> recoverTwoFactor(
|
||||
BuildContext context,
|
||||
String sessionID,
|
||||
TwoFactorType type,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
|
@ -770,6 +775,7 @@ class UserService {
|
|||
_config.getHttpEndpoint() + "/users/two-factor/recover",
|
||||
queryParameters: {
|
||||
"sessionID": sessionID,
|
||||
"twoFactorType": twoFactorTypeToString(type),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
|
@ -778,6 +784,7 @@ class UserService {
|
|||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return TwoFactorRecoveryPage(
|
||||
type,
|
||||
sessionID,
|
||||
response.data["encryptedSecret"],
|
||||
response.data["secretDecryptionNonce"],
|
||||
|
@ -788,6 +795,7 @@ class UserService {
|
|||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, context.l10n.sessionExpired);
|
||||
|
@ -809,6 +817,7 @@ class UserService {
|
|||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
|
@ -823,6 +832,7 @@ class UserService {
|
|||
|
||||
Future<void> removeTwoFactor(
|
||||
BuildContext context,
|
||||
TwoFactorType type,
|
||||
String sessionID,
|
||||
String recoveryKey,
|
||||
String encryptedSecret,
|
||||
|
@ -862,6 +872,7 @@ class UserService {
|
|||
data: {
|
||||
"sessionID": sessionID,
|
||||
"secret": secret,
|
||||
"twoFactorType": twoFactorTypeToString(type),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
|
@ -881,6 +892,7 @@ class UserService {
|
|||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, "Session expired");
|
||||
|
@ -902,6 +914,7 @@ class UserService {
|
|||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
@ -99,30 +101,50 @@ class _PasskeyPageState extends State<PasskeyPage> {
|
|||
}
|
||||
|
||||
Widget _getBody() {
|
||||
final l10n = context.l10n;
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.waitingForBrowserRequest,
|
||||
style: const TextStyle(
|
||||
height: 1.4,
|
||||
fontSize: 16,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.waitingForVerification,
|
||||
style: const TextStyle(
|
||||
height: 1.4,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: launchPasskey,
|
||||
child: Text(l10n.launchPasskeyUrlAgain),
|
||||
const SizedBox(height: 16),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: context.l10n.verifyPasskey,
|
||||
onTap: () => launchPasskey(),
|
||||
),
|
||||
),
|
||||
],
|
||||
const Padding(padding: EdgeInsets.all(30)),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
UserService.instance.recoverTwoFactor(
|
||||
context,
|
||||
widget.sessionID,
|
||||
TwoFactorType.passkey,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.recoverAccount,
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class AppUpdateDialog extends StatefulWidget {
|
||||
|
@ -114,116 +107,3 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ApkDownloaderDialog extends StatefulWidget {
|
||||
final LatestVersionInfo? versionInfo;
|
||||
|
||||
const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
|
||||
}
|
||||
|
||||
class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
|
||||
String? _saveUrl;
|
||||
double? _downloadProgress;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_saveUrl = Configuration.instance.getTempDirectory() +
|
||||
"ente-" +
|
||||
widget.versionInfo!.name! +
|
||||
".apk";
|
||||
_downloadApk();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: AlertDialog(
|
||||
title: const Text(
|
||||
"Downloading...",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
content: LinearProgressIndicator(
|
||||
value: _downloadProgress,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.alternativeColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _downloadApk() async {
|
||||
try {
|
||||
if (!File(_saveUrl!).existsSync()) {
|
||||
await Network.instance.getDio().download(
|
||||
widget.versionInfo!.url!,
|
||||
_saveUrl,
|
||||
onReceiveProgress: (count, _) {
|
||||
setState(() {
|
||||
_downloadProgress = count / widget.versionInfo!.size!;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
// ignore: unawaited_futures
|
||||
OpenFilex.open(_saveUrl);
|
||||
} catch (e) {
|
||||
Logger("ApkDownloader").severe(e);
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: const Text("Sorry"),
|
||||
content: const Text("The download could not be completed"),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text(
|
||||
"Ignore",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"Retry",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.alternativeColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ApkDownloaderDialog(widget.versionInfo);
|
||||
},
|
||||
barrierDismissible: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
barrierColor: Colors.black87,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import 'package:ente_auth/utils/dialog_util.dart';
|
|||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class SecuritySectionWidget extends StatefulWidget {
|
||||
const SecuritySectionWidget({Key? key}) : super(key: key);
|
||||
|
@ -32,6 +33,7 @@ class SecuritySectionWidget extends StatefulWidget {
|
|||
class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
final _config = Configuration.instance;
|
||||
late bool _hasLoggedIn;
|
||||
final Logger _logger = Logger('SecuritySectionWidget');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -75,7 +77,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () => PasskeyService.instance.openPasskeyPage(context),
|
||||
onTap: () async => await onPasskeyClick(context),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
|
@ -159,6 +161,31 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> onPasskeyClick(BuildContext buildContext) async {
|
||||
try {
|
||||
final isPassKeyResetEnabled =
|
||||
await PasskeyService.instance.isPasskeyRecoveryEnabled();
|
||||
if (!isPassKeyResetEnabled) {
|
||||
final Uint8List recoveryKey = Configuration.instance.getRecoveryKey();
|
||||
final resetKey = CryptoUtil.generateKey();
|
||||
final resetKeyBase64 = CryptoUtil.bin2base64(resetKey);
|
||||
final encryptionResult = CryptoUtil.encryptSync(
|
||||
resetKey,
|
||||
recoveryKey,
|
||||
);
|
||||
await PasskeyService.instance.configurePasskeyRecovery(
|
||||
resetKeyBase64,
|
||||
CryptoUtil.bin2base64(encryptionResult.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptionResult.nonce!),
|
||||
);
|
||||
}
|
||||
PasskeyService.instance.openPasskeyPage(buildContext).ignore();
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to open passkey page", e, s);
|
||||
await showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateEmailMFA(bool enableEmailMFA) async {
|
||||
try {
|
||||
final UserDetails details =
|
||||
|
|
|
@ -56,7 +56,8 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
|
|||
text: context.l10n.unlock,
|
||||
iconData: Icons.lock_open_outlined,
|
||||
onTap: () async {
|
||||
await _showLockScreen(source: "tapUnlock");
|
||||
// ignore: unawaited_futures
|
||||
_showLockScreen(source: "tapUnlock");
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/lifecycle_event_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -129,7 +130,11 @@ class _TwoFactorAuthenticationPageState
|
|||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
UserService.instance.recoverTwoFactor(context, widget.sessionID);
|
||||
UserService.instance.recoverTwoFactor(
|
||||
context,
|
||||
widget.sessionID,
|
||||
TwoFactorType.totp,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -9,8 +10,10 @@ class TwoFactorRecoveryPage extends StatefulWidget {
|
|||
final String sessionID;
|
||||
final String encryptedSecret;
|
||||
final String secretDecryptionNonce;
|
||||
final TwoFactorType type;
|
||||
|
||||
const TwoFactorRecoveryPage(
|
||||
this.type,
|
||||
this.sessionID,
|
||||
this.encryptedSecret,
|
||||
this.secretDecryptionNonce, {
|
||||
|
@ -72,6 +75,7 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
|
|||
? () async {
|
||||
await UserService.instance.removeTwoFactor(
|
||||
context,
|
||||
widget.type,
|
||||
widget.sessionID,
|
||||
_recoveryKey.text,
|
||||
widget.encryptedSecret,
|
||||
|
|
|
@ -197,10 +197,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.2"
|
||||
computer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -827,10 +827,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
version: "1.9.1"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -879,14 +879,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
open_filex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: open_filex
|
||||
sha256: "854aefd72dfd74219dc8c8d1767c34ec1eae64b8399a5be317bddb1ec2108915"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.2"
|
||||
otp:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1304,10 +1296,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.11.0"
|
||||
step_progress_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1320,10 +1312,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.1"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1368,26 +1360,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f
|
||||
sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.24.9"
|
||||
version: "1.24.3"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
version: "0.6.0"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a
|
||||
sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.9"
|
||||
version: "0.5.3"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1576,10 +1568,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "0.1.4-beta"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1637,5 +1629,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.2.0-194.0.dev <4.0.0"
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 2.0.35+235
|
||||
version: 2.0.42+242
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
@ -54,13 +54,11 @@ dependencies:
|
|||
intl: ^0.18.0
|
||||
json_annotation: ^4.5.0
|
||||
local_auth: ^2.1.7
|
||||
|
||||
local_auth_android: ^1.0.31
|
||||
local_auth_ios: ^1.1.3
|
||||
logging: ^1.0.1
|
||||
modal_bottom_sheet: ^3.0.0-pre
|
||||
move_to_background: ^1.0.2
|
||||
open_filex: ^4.3.2
|
||||
otp: ^3.1.1
|
||||
package_info_plus: ^4.1.0
|
||||
password_strength: ^0.2.0
|
||||
|
|
|
@ -64,7 +64,14 @@ ente account update --email email@domain.com --dir ~/photos
|
|||
ente export
|
||||
```
|
||||
|
||||
---
|
||||
### CLI Docs
|
||||
You can view more cli documents at [docs](docs/generated/ente.md).
|
||||
To update the docs, run the following command:
|
||||
|
||||
```shell
|
||||
go run main.go docs
|
||||
```
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ var updateAccCmd = &cobra.Command{
|
|||
fmt.Printf("invalid app. Accepted values are 'photos', 'locker', 'auth'")
|
||||
|
||||
}
|
||||
err := ctrl.UpdateAccount(context.Background(), model.UpdateAccountParams{
|
||||
err := ctrl.UpdateAccount(context.Background(), model.AccountCommandParams{
|
||||
Email: email,
|
||||
App: api.StringToApp(app),
|
||||
ExportDir: &exportDir,
|
||||
|
@ -73,12 +73,49 @@ var updateAccCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// Subcommand for 'account update'
|
||||
var getTokenCmd = &cobra.Command{
|
||||
Use: "get-token",
|
||||
Short: "Get token for an account for a specific app",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
recoverWithLog()
|
||||
app, _ := cmd.Flags().GetString("app")
|
||||
email, _ := cmd.Flags().GetString("email")
|
||||
if email == "" {
|
||||
|
||||
fmt.Println("email must be specified, use --help for more information")
|
||||
// print help
|
||||
return
|
||||
}
|
||||
|
||||
validApps := map[string]bool{
|
||||
"photos": true,
|
||||
"locker": true,
|
||||
"auth": true,
|
||||
}
|
||||
|
||||
if !validApps[app] {
|
||||
fmt.Printf("invalid app. Accepted values are 'photos', 'locker', 'auth'")
|
||||
|
||||
}
|
||||
err := ctrl.GetToken(context.Background(), model.AccountCommandParams{
|
||||
Email: email,
|
||||
App: api.StringToApp(app),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error updating account: %v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add 'config' subcommands to the root command
|
||||
rootCmd.AddCommand(accountCmd)
|
||||
// Add 'config' subcommands to the 'config' command
|
||||
updateAccCmd.Flags().String("dir", "", "update export directory")
|
||||
updateAccCmd.Flags().String("email", "", "email address of the account to update")
|
||||
updateAccCmd.Flags().String("email", "", "email address of the account")
|
||||
updateAccCmd.Flags().String("app", "photos", "Specify the app, default is 'photos'")
|
||||
accountCmd.AddCommand(listAccCmd, addAccCmd, updateAccCmd)
|
||||
getTokenCmd.Flags().String("email", "", "email address of the account")
|
||||
getTokenCmd.Flags().String("app", "photos", "Specify the app, default is 'photos'")
|
||||
accountCmd.AddCommand(listAccCmd, addAccCmd, updateAccCmd, getTokenCmd)
|
||||
}
|
||||
|
|
90
cli/cmd/admin.go
Normal file
90
cli/cmd/admin.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/pkg/model"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _adminCmd = &cobra.Command{
|
||||
Use: "admin",
|
||||
Short: "Commands for admin actions",
|
||||
Long: "Commands for admin actions like disable or enabling 2fa, bumping up the storage limit, etc.",
|
||||
}
|
||||
|
||||
var _userDetailsCmd = &cobra.Command{
|
||||
Use: "get-user-id",
|
||||
Short: "Get user id",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "user" {
|
||||
flags.UserEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
return ctrl.GetUserId(context.Background(), *flags)
|
||||
},
|
||||
}
|
||||
|
||||
var _disable2faCmd = &cobra.Command{
|
||||
Use: "disable-2fa",
|
||||
Short: "Disable 2fa for a user",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "user" {
|
||||
flags.UserEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
fmt.Println("Not supported yet")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var _updateFreeUserStorage = &cobra.Command{
|
||||
Use: "update-subscription",
|
||||
Short: "Update subscription for the free user",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
noLimit := false
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "user" {
|
||||
flags.UserEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "no-limit" {
|
||||
noLimit = strings.ToLower(f.Value.String()) == "true"
|
||||
}
|
||||
})
|
||||
return ctrl.UpdateFreeStorage(context.Background(), *flags, noLimit)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(_adminCmd)
|
||||
_ = _userDetailsCmd.MarkFlagRequired("admin-user")
|
||||
_ = _userDetailsCmd.MarkFlagRequired("user")
|
||||
_userDetailsCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
|
||||
_userDetailsCmd.Flags().StringP("user", "u", "", "The email of the user to fetch details for. (required)")
|
||||
_disable2faCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
|
||||
_disable2faCmd.Flags().StringP("user", "u", "", "The email of the user to disable 2FA for. (required)")
|
||||
_updateFreeUserStorage.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
|
||||
_updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)")
|
||||
// add a flag with no value --no-limit
|
||||
_updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _updateFreeUserStorage)
|
||||
}
|
|
@ -3,6 +3,7 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/pkg"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
|
@ -11,7 +12,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const AppVersion = "0.1.11"
|
||||
var version string
|
||||
|
||||
var ctrl *pkg.ClICtrl
|
||||
|
||||
|
@ -27,10 +28,15 @@ var rootCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
func GenerateDocs() error {
|
||||
return doc.GenMarkdownTree(rootCmd, "./docs/generated")
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute(controller *pkg.ClICtrl) {
|
||||
func Execute(controller *pkg.ClICtrl, ver string) {
|
||||
ctrl = controller
|
||||
version = ver
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
|
|
|
@ -12,7 +12,7 @@ var versionCmd = &cobra.Command{
|
|||
Short: "Prints the current version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Version %s\n", AppVersion)
|
||||
fmt.Printf("Version %s\n", version)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
10
cli/config.yaml.example
Normal file
10
cli/config.yaml.example
Normal file
|
@ -0,0 +1,10 @@
|
|||
# You can put this configuration file in the following locations:
|
||||
# - $HOME/.ente/config.yaml
|
||||
# - config.yaml in the current working directory
|
||||
# - $ENTE_CLI_CONFIG_PATH/config.yaml
|
||||
|
||||
endpoint:
|
||||
api: "http://localhost:8080"
|
||||
|
||||
log:
|
||||
http: false # log status code & time taken by requests
|
28
cli/docs/generated/ente.md
Normal file
28
cli/docs/generated/ente.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
## ente
|
||||
|
||||
CLI tool for exporting your photos from ente.io
|
||||
|
||||
### Synopsis
|
||||
|
||||
Start by creating a config file in your home directory:
|
||||
|
||||
```
|
||||
ente [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for ente
|
||||
-t, --toggle Help message for toggle
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
* [ente auth](ente_auth.md) - Authenticator commands
|
||||
* [ente export](ente_export.md) - Starts the export process
|
||||
* [ente version](ente_version.md) - Prints the current version
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
19
cli/docs/generated/ente_account.md
Normal file
19
cli/docs/generated/ente_account.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## ente account
|
||||
|
||||
Manage account settings
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for account
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
* [ente account add](ente_account_add.md) - Add a new account
|
||||
* [ente account get-token](ente_account_get-token.md) - Get token for an account for a specific app
|
||||
* [ente account list](ente_account_list.md) - list configured accounts
|
||||
* [ente account update](ente_account_update.md) - Update an existing account's export directory
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
19
cli/docs/generated/ente_account_add.md
Normal file
19
cli/docs/generated/ente_account_add.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## ente account add
|
||||
|
||||
Add a new account
|
||||
|
||||
```
|
||||
ente account add [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for add
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
21
cli/docs/generated/ente_account_get-token.md
Normal file
21
cli/docs/generated/ente_account_get-token.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
## ente account get-token
|
||||
|
||||
Get token for an account for a specific app
|
||||
|
||||
```
|
||||
ente account get-token [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--app string Specify the app, default is 'photos' (default "photos")
|
||||
--email string email address of the account
|
||||
-h, --help help for get-token
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
19
cli/docs/generated/ente_account_list.md
Normal file
19
cli/docs/generated/ente_account_list.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## ente account list
|
||||
|
||||
list configured accounts
|
||||
|
||||
```
|
||||
ente account list [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for list
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
22
cli/docs/generated/ente_account_update.md
Normal file
22
cli/docs/generated/ente_account_update.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
## ente account update
|
||||
|
||||
Update an existing account's export directory
|
||||
|
||||
```
|
||||
ente account update [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--app string Specify the app, default is 'photos' (default "photos")
|
||||
--dir string update export directory
|
||||
--email string email address of the account
|
||||
-h, --help help for update
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
22
cli/docs/generated/ente_admin.md
Normal file
22
cli/docs/generated/ente_admin.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
## ente admin
|
||||
|
||||
Commands for admin actions
|
||||
|
||||
### Synopsis
|
||||
|
||||
Commands for admin actions like disable or enabling 2fa, bumping up the storage limit, etc.
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for admin
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
* [ente admin disable-2fa](ente_admin_disable-2fa.md) - Disable 2fa for a user
|
||||
* [ente admin get-user-id](ente_admin_get-user-id.md) - Get user id
|
||||
* [ente admin update-subscription](ente_admin_update-subscription.md) - Update subscription for the free user
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
21
cli/docs/generated/ente_admin_disable-2fa.md
Normal file
21
cli/docs/generated/ente_admin_disable-2fa.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
## ente admin disable-2fa
|
||||
|
||||
Disable 2fa for a user
|
||||
|
||||
```
|
||||
ente admin disable-2fa [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --admin-user string The email of the admin user. (required)
|
||||
-h, --help help for disable-2fa
|
||||
-u, --user string The email of the user to disable 2FA for. (required)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
21
cli/docs/generated/ente_admin_get-user-id.md
Normal file
21
cli/docs/generated/ente_admin_get-user-id.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
## ente admin get-user-id
|
||||
|
||||
Get user id
|
||||
|
||||
```
|
||||
ente admin get-user-id [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --admin-user string The email of the admin user. (required)
|
||||
-h, --help help for get-user-id
|
||||
-u, --user string The email of the user to fetch details for. (required)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
22
cli/docs/generated/ente_admin_update-subscription.md
Normal file
22
cli/docs/generated/ente_admin_update-subscription.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
## ente admin update-subscription
|
||||
|
||||
Update subscription for the free user
|
||||
|
||||
```
|
||||
ente admin update-subscription [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --admin-user string The email of the admin user. (required)
|
||||
-h, --help help for update-subscription
|
||||
--no-limit string When true, sets 100TB as storage limit, and expiry to current date + 100 years (default "True")
|
||||
-u, --user string The email of the user to update subscription for. (required)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
16
cli/docs/generated/ente_auth.md
Normal file
16
cli/docs/generated/ente_auth.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
## ente auth
|
||||
|
||||
Authenticator commands
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for auth
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
* [ente auth decrypt](ente_auth_decrypt.md) - Decrypt authenticator export
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
19
cli/docs/generated/ente_auth_decrypt.md
Normal file
19
cli/docs/generated/ente_auth_decrypt.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## ente auth decrypt
|
||||
|
||||
Decrypt authenticator export
|
||||
|
||||
```
|
||||
ente auth decrypt [input] [output] [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for decrypt
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente auth](ente_auth.md) - Authenticator commands
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
19
cli/docs/generated/ente_export.md
Normal file
19
cli/docs/generated/ente_export.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## ente export
|
||||
|
||||
Starts the export process
|
||||
|
||||
```
|
||||
ente export [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for export
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
19
cli/docs/generated/ente_version.md
Normal file
19
cli/docs/generated/ente_version.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## ente version
|
||||
|
||||
Prints the current version
|
||||
|
||||
```
|
||||
ente version [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for version
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
27
cli/docs/selfhost.md
Normal file
27
cli/docs/selfhost.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
## Self Hosting
|
||||
If you are self-hosting the server, you can still configure CLI to export data & perform basic admin actions.
|
||||
|
||||
To do this, first configure the CLI to point to your server.
|
||||
Define a config.yaml and put it either in the same directory as CLI binary or path defined in env variable `ENTE_CLI_CONFIG_PATH`
|
||||
|
||||
```yaml
|
||||
endpoint:
|
||||
api: "http://localhost:8080"
|
||||
```
|
||||
|
||||
You should be able to [add an account](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_account_add.md), and subsequently increase the [storage and account validity](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_admin_update-subscription.md) using the CLI.
|
||||
|
||||
|
||||
For the admin actions, you first need to whitelist admin users. You can create `server/museum.yaml`, and whitelist add the admin userID `internal.admins`. See [local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml#L211C1-L232C1) in the server source code for details about how to define this.
|
||||
|
||||
You can use [account list](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_account_list.md) command to find the user id of any account.
|
||||
|
||||
```yaml
|
||||
# ....
|
||||
|
||||
internal:
|
||||
admins:
|
||||
# - 1580559962386440
|
||||
|
||||
# ....
|
||||
```
|
|
@ -12,10 +12,12 @@ require (
|
|||
|
||||
require (
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/danieljoos/wincred v1.2.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
|
@ -48,6 +48,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
|
||||
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
|
||||
|
@ -168,6 +169,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
|
|
57
cli/internal/api/admin.go
Normal file
57
cli/internal/api/admin.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/internal/api/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Client) GetUserIdFromEmail(ctx context.Context, email string) (*models.UserDetails, error) {
|
||||
var res models.UserDetails
|
||||
r, err := c.restClient.R().
|
||||
SetContext(ctx).
|
||||
SetResult(&res).
|
||||
SetQueryParam("email", email).
|
||||
Get("/admin/user/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.IsError() {
|
||||
return nil, &ApiError{
|
||||
StatusCode: r.StatusCode(),
|
||||
Message: r.String(),
|
||||
}
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
func (c *Client) UpdateFreePlanSub(ctx context.Context, userDetails *models.UserDetails, storageInBytes int64, expiryTimeInMicro int64) error {
|
||||
var res interface{}
|
||||
if userDetails.Subscription.ProductID != "free" {
|
||||
return fmt.Errorf("user is not on free plan")
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"userID": userDetails.User.ID,
|
||||
"expiryTime": expiryTimeInMicro,
|
||||
"transactionID": fmt.Sprintf("cli-on-%d", time.Now().Unix()),
|
||||
"productID": "free",
|
||||
"paymentProvider": "",
|
||||
"storage": storageInBytes,
|
||||
}
|
||||
r, err := c.restClient.R().
|
||||
SetContext(ctx).
|
||||
SetResult(&res).
|
||||
SetBody(payload).
|
||||
Put("/admin/user/subscription")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.IsError() {
|
||||
return &ApiError{
|
||||
StatusCode: r.StatusCode(),
|
||||
Message: r.String(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
|
@ -2,19 +2,30 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/ente-io/cli/utils/constants"
|
||||
"github.com/spf13/viper"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
downloadHost = "https://files.ente.io/?fileID="
|
||||
)
|
||||
|
||||
func downloadUrl(fileID int64) string {
|
||||
apiEndpoint := viper.GetString("endpoint.api")
|
||||
if apiEndpoint == "" || strings.Compare(apiEndpoint, constants.EnteApiUrl) == 0 {
|
||||
return downloadHost + strconv.FormatInt(fileID, 10)
|
||||
}
|
||||
return apiEndpoint + "/files/download/" + strconv.FormatInt(fileID, 10)
|
||||
}
|
||||
|
||||
func (c *Client) DownloadFile(ctx context.Context, fileID int64, absolutePath string) error {
|
||||
req := c.downloadClient.R().
|
||||
SetContext(ctx).
|
||||
SetOutput(absolutePath)
|
||||
attachToken(req)
|
||||
r, err := req.Get(downloadHost + strconv.FormatInt(fileID, 10))
|
||||
r, err := req.Get(downloadUrl(fileID))
|
||||
if r.IsError() {
|
||||
return &ApiError{
|
||||
StatusCode: r.StatusCode(),
|
||||
|
|
|
@ -30,6 +30,16 @@ func logRequest(req *resty.Request) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// log query params if present
|
||||
if len(req.QueryParam) > 0 {
|
||||
fmt.Println(color.GreenString("Query Params:"))
|
||||
for k, v := range req.QueryParam {
|
||||
if k == TokenQuery {
|
||||
v = []string{"REDACTED"}
|
||||
}
|
||||
fmt.Printf("%s: %s\n", color.CyanString(k), color.YellowString(strings.Join(v, ",")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logResponse(resp *resty.Response) {
|
||||
|
|
16
cli/internal/api/models/user_details.go
Normal file
16
cli/internal/api/models/user_details.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package models
|
||||
|
||||
type UserDetails struct {
|
||||
User struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"user"`
|
||||
Usage int64 `json:"usage"`
|
||||
Email string `json:"email"`
|
||||
|
||||
Subscription struct {
|
||||
ExpiryTime int64 `json:"expiryTime"`
|
||||
Storage int64 `json:"storage"`
|
||||
ProductID string `json:"productID"`
|
||||
PaymentProvider string `json:"paymentProvider"`
|
||||
} `json:"subscription"`
|
||||
}
|
|
@ -5,11 +5,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/internal/api"
|
||||
"golang.org/x/term"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func GetSensitiveField(label string) (string, error) {
|
||||
|
@ -81,6 +82,79 @@ func GetCode(promptText string, length int) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// parseStorageSize parses a string representing a storage size (e.g., "500MB", "2GB") into bytes.
|
||||
func parseStorageSize(input string) (int64, error) {
|
||||
units := map[string]int64{
|
||||
"MB": 1 << 20,
|
||||
"GB": 1 << 30,
|
||||
"TB": 1 << 40,
|
||||
}
|
||||
re := regexp.MustCompile(`(?i)^(\d+(?:\.\d+)?)(MB|GB|TB)$`)
|
||||
matches := re.FindStringSubmatch(input)
|
||||
|
||||
if matches == nil {
|
||||
return 0, errors.New("invalid format")
|
||||
}
|
||||
|
||||
number, err := strconv.ParseFloat(matches[1], 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid number: %s", matches[1])
|
||||
}
|
||||
|
||||
unit := strings.ToUpper(matches[2])
|
||||
bytes := int64(number * float64(units[unit]))
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func ConfirmAction(promptText string) (bool, error) {
|
||||
for {
|
||||
input, err := GetUserInput(promptText)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if input == "" {
|
||||
log.Fatal("No input entered")
|
||||
return false, errors.New("invalid input. Please enter 'y' or 'n'")
|
||||
}
|
||||
if input == "c" {
|
||||
return false, errors.New("cancelled")
|
||||
}
|
||||
if input == "y" {
|
||||
return true, nil
|
||||
}
|
||||
if input == "n" {
|
||||
return false, nil
|
||||
}
|
||||
fmt.Println("Invalid input. Please enter 'y' or 'n'.")
|
||||
}
|
||||
}
|
||||
|
||||
// GetStorageSize prompts the user for a storage size and returns the size in bytes.
|
||||
func GetStorageSize(promptText string) (int64, error) {
|
||||
for {
|
||||
input, err := GetUserInput(promptText)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if input == "" {
|
||||
log.Fatal("No storage size entered")
|
||||
return 0, errors.New("no storage size entered")
|
||||
}
|
||||
if input == "c" {
|
||||
return 0, errors.New("storage size entry cancelled")
|
||||
}
|
||||
|
||||
bytes, err := parseStorageSize(input)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid storage size format. Please use a valid format like '500MB', '2GB'.")
|
||||
continue
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetExportDir() string {
|
||||
for {
|
||||
exportDir, err := GetUserInput("Enter export directory")
|
||||
|
|
36
cli/main.go
36
cli/main.go
|
@ -8,12 +8,15 @@ import (
|
|||
"github.com/ente-io/cli/pkg"
|
||||
"github.com/ente-io/cli/pkg/secrets"
|
||||
"github.com/ente-io/cli/utils/constants"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var AppVersion = "0.1.12"
|
||||
|
||||
func main() {
|
||||
cliDBPath, err := GetCLIConfigPath()
|
||||
if secrets.IsRunningInContainer() {
|
||||
|
@ -23,10 +26,10 @@ func main() {
|
|||
log.Fatalf("Please mount a volume to %s to persist cli data\n%v\n", cliDBPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create cli config path\n%v\n", err)
|
||||
}
|
||||
initConfig(cliDBPath)
|
||||
newCliPath := fmt.Sprintf("%s/ente-cli.db", cliDBPath)
|
||||
if !strings.HasPrefix(cliDBPath, "/") {
|
||||
oldCliPath := fmt.Sprintf("%sente-cli.db", cliDBPath)
|
||||
|
@ -48,8 +51,8 @@ func main() {
|
|||
}
|
||||
ctrl := pkg.ClICtrl{
|
||||
Client: api.NewClient(api.Params{
|
||||
Debug: false,
|
||||
//Host: "http://localhost:8080",
|
||||
Debug: viper.GetBool("log.http"),
|
||||
Host: viper.GetString("endpoint.api"),
|
||||
}),
|
||||
DB: db,
|
||||
KeyHolder: secrets.NewKeyHolder(secrets.GetOrCreateClISecret()),
|
||||
|
@ -63,7 +66,32 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
}()
|
||||
cmd.Execute(&ctrl)
|
||||
|
||||
if len(os.Args) == 2 && os.Args[1] == "docs" {
|
||||
log.Println("Generating docs")
|
||||
err = cmd.GenerateDocs()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
cmd.Execute(&ctrl, AppVersion)
|
||||
}
|
||||
|
||||
func initConfig(cliConfigPath string) {
|
||||
viper.SetConfigName("config") // name of config file (without extension)
|
||||
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
|
||||
viper.AddConfigPath(cliConfigPath + "/") // path to look for the config file in
|
||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
||||
|
||||
viper.SetDefault("endpoint.api", constants.EnteApiUrl)
|
||||
viper.SetDefault("log.http", false)
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
} else {
|
||||
// Config file was found but another error was produced
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetCLIConfigPath returns the path to the .ente-cli folder and creates it if it doesn't exist.
|
||||
|
|
|
@ -142,7 +142,7 @@ func (c *ClICtrl) ListAccounts(cxt context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) UpdateAccount(ctx context.Context, params model.UpdateAccountParams) error {
|
||||
func (c *ClICtrl) UpdateAccount(ctx context.Context, params model.AccountCommandParams) error {
|
||||
accounts, err := c.GetAccounts(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -177,5 +177,27 @@ func (c *ClICtrl) UpdateAccount(ctx context.Context, params model.UpdateAccountP
|
|||
return b.Put([]byte(accountKey), accInfoBytes)
|
||||
})
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (c *ClICtrl) GetToken(ctx context.Context, params model.AccountCommandParams) error {
|
||||
accounts, err := c.GetAccounts(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var acc *model.Account
|
||||
for _, a := range accounts {
|
||||
if a.Email == params.Email && a.App == params.App {
|
||||
acc = &a
|
||||
break
|
||||
}
|
||||
}
|
||||
if acc == nil {
|
||||
return fmt.Errorf("account not found, use `account list` to list accounts")
|
||||
}
|
||||
secretInfo, err := c.KeyHolder.LoadSecrets(*acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(secretInfo.TokenStr())
|
||||
return nil
|
||||
}
|
||||
|
|
114
cli/pkg/admin_actions.go
Normal file
114
cli/pkg/admin_actions.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package pkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/internal"
|
||||
"github.com/ente-io/cli/pkg/model"
|
||||
"github.com/ente-io/cli/utils"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *ClICtrl) GetUserId(ctx context.Context, params model.AdminActionForUser) error {
|
||||
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := c.Client.GetUserIdFromEmail(accountCtx, params.UserEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(id.User.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) UpdateFreeStorage(ctx context.Context, params model.AdminActionForUser, noLimit bool) error {
|
||||
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userDetails, err := c.Client.GetUserIdFromEmail(accountCtx, params.UserEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if noLimit {
|
||||
// set storage to 100TB and expiry to + 100 years
|
||||
err := c.Client.UpdateFreePlanSub(accountCtx, userDetails, 100*1024*1024*1024*1024, time.Now().AddDate(100, 0, 0).UnixMicro())
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("Successfully updated storage and expiry date for user")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
storageSize, err := internal.GetStorageSize("Enter a storage size (e.g.'5MB', '10GB', '2Tb'): ")
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
dateStr, err := internal.GetUserInput("Enter sub expiry date in YYYY-MM-DD format (e.g.'2040-12-31')")
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
date, err := _parseDateOrDateTime(dateStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Updating storage for user %s to %s (old %s) with new expirty %s (old %s) \n",
|
||||
params.UserEmail,
|
||||
utils.ByteCountDecimalGIB(storageSize), utils.ByteCountDecimalGIB(userDetails.Subscription.Storage),
|
||||
date.Format("2006-01-02"),
|
||||
time.UnixMicro(userDetails.Subscription.ExpiryTime).Format("2006-01-02"))
|
||||
// press y to confirm
|
||||
confirmed, _ := internal.ConfirmAction("Are you sure you want to update the storage ('y' or 'n')?")
|
||||
if !confirmed {
|
||||
return nil
|
||||
} else {
|
||||
err := c.Client.UpdateFreePlanSub(accountCtx, userDetails, storageSize, date.UnixMicro())
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("Successfully updated storage and expiry date for user")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) buildAdminContext(ctx context.Context, adminEmail string) (context.Context, error) {
|
||||
accounts, err := c.GetAccounts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var acc *model.Account
|
||||
for _, a := range accounts {
|
||||
if a.Email == adminEmail {
|
||||
acc = &a
|
||||
break
|
||||
}
|
||||
}
|
||||
if acc == nil {
|
||||
return nil, fmt.Errorf("account not found for %s, use `account list` to list accounts", adminEmail)
|
||||
}
|
||||
secretInfo, err := c.KeyHolder.LoadSecrets(*acc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accountCtx := c.buildRequestContext(ctx, *acc)
|
||||
c.Client.AddToken(acc.AccountKey(), secretInfo.TokenStr())
|
||||
return accountCtx, nil
|
||||
}
|
||||
|
||||
func _parseDateOrDateTime(input string) (time.Time, error) {
|
||||
var layout string
|
||||
if strings.Contains(input, " ") {
|
||||
// If the input contains a space, assume it's a date-time format
|
||||
layout = "2006-01-02 15:04:05"
|
||||
} else {
|
||||
// If there's no space, assume it's just a date
|
||||
layout = "2006-01-02"
|
||||
}
|
||||
return time.Parse(layout, input)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/internal/api"
|
||||
)
|
||||
|
@ -17,7 +18,7 @@ type Account struct {
|
|||
ExportDir string `json:"exportDir"`
|
||||
}
|
||||
|
||||
type UpdateAccountParams struct {
|
||||
type AccountCommandParams struct {
|
||||
Email string
|
||||
App api.App
|
||||
ExportDir *string
|
||||
|
@ -37,3 +38,7 @@ type AccSecretInfo struct {
|
|||
Token []byte
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
func (a *AccSecretInfo) TokenStr() string {
|
||||
return base64.URLEncoding.EncodeToString(a.Token)
|
||||
}
|
||||
|
|
6
cli/pkg/model/admin.go
Normal file
6
cli/pkg/model/admin.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package model
|
||||
|
||||
type AdminActionForUser struct {
|
||||
UserEmail string
|
||||
AdminEmail string
|
||||
}
|
|
@ -1,5 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Fetch the latest tag that starts with "cli-"
|
||||
# shellcheck disable=SC2046
|
||||
# shellcheck disable=SC2006
|
||||
LATEST_TAG=$(git describe --tags `git rev-list --tags='cli-*' --max-count=1`)
|
||||
|
||||
# Check if the LATEST_TAG variable is empty
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
echo "No 'cli-' tag found. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
VERSION=${LATEST_TAG#cli-}
|
||||
# Create a "bin" directory if it doesn't exist
|
||||
mkdir -p bin
|
||||
|
||||
|
@ -29,7 +40,7 @@ do
|
|||
fi
|
||||
|
||||
# Build the binary and place it in the "bin" directory
|
||||
go build -ldflags="-s -w" -trimpath -o "bin/$BINARY_NAME" main.go
|
||||
go build -ldflags="-X main.AppVersion=${VERSION} -s -w" -trimpath -o "bin/$BINARY_NAME" main.go
|
||||
|
||||
# Print a message indicating the build is complete for the current OS and architecture
|
||||
echo "Built for $OS ($ARCH) as bin/$BINARY_NAME"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
package constants
|
||||
|
||||
const CliDataPath = "/cli-data/"
|
||||
const EnteApiUrl = "https://api.ente.io"
|
||||
|
|
31
cli/utils/convert.go
Normal file
31
cli/utils/convert.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ByteCountDecimal(b int64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
||||
func ByteCountDecimalGIB(b int64) string {
|
||||
const unit = 1024
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
@ -10,16 +9,3 @@ func TimeTrack(start time.Time, name string) {
|
|||
elapsed := time.Since(start)
|
||||
log.Printf("%s took %s", name, elapsed)
|
||||
}
|
||||
|
||||
func ByteCountDecimal(b int64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
|
3
desktop/.github/workflows/build.yml
vendored
3
desktop/.github/workflows/build.yml
vendored
|
@ -52,7 +52,4 @@ jobs:
|
|||
# macOS notarization API key
|
||||
API_KEY_ID: ${{ secrets.api_key_id }}
|
||||
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id}}
|
||||
# setry crash reporting token
|
||||
SENTRY_AUTH_TOKEN: ${{secrets.sentry_auth_token}}
|
||||
NEXT_PUBLIC_DISABLE_SENTRY: ${{secrets.next_public_disable_sentry}}
|
||||
USE_HARD_LINKS: false
|
||||
|
|
29
desktop/.gitignore
vendored
29
desktop/.gitignore
vendored
|
@ -1,12 +1,21 @@
|
|||
node_modules
|
||||
app
|
||||
.next/
|
||||
dist
|
||||
.vscode
|
||||
buildingSteps.md
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.idea/
|
||||
build/.DS_Store
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.electron-symbols/
|
||||
models/
|
||||
.env.*.local
|
||||
|
||||
# tsc transpiles src/**/*.ts and emits the generated JS into app
|
||||
app/
|
||||
|
||||
# out is a symlink to the photos web app's dir
|
||||
out
|
||||
|
||||
# electron-builder
|
||||
dist/
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
if [ "$branch" = "main" ]; then
|
||||
echo "You can't commit directly to main branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npx lint-staged
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"bracketSameLine": true
|
||||
}
|
||||
"plugins": [
|
||||
"prettier-plugin-organize-imports",
|
||||
"prettier-plugin-packagejson"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
network-timeout 500000
|
|
@ -4,128 +4,128 @@
|
|||
|
||||
### New
|
||||
|
||||
- Option to select file download location.
|
||||
- Add support for searching popular cities
|
||||
- Sorted duplicates in desecending order of size
|
||||
- Add Counter to upload section
|
||||
- Display full name and collection name on hover on dedupe screen photos
|
||||
- Option to select file download location.
|
||||
- Add support for searching popular cities
|
||||
- Sorted duplicates in desecending order of size
|
||||
- Add Counter to upload section
|
||||
- Display full name and collection name on hover on dedupe screen photos
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix add to album padding issue
|
||||
- Fix double uncategorized album issue
|
||||
- Hide Hidden collection files from all section
|
||||
- Fix add to album padding issue
|
||||
- Fix double uncategorized album issue
|
||||
- Hide Hidden collection files from all section
|
||||
|
||||
## v1.6.62
|
||||
|
||||
### New
|
||||
|
||||
- Integrated onnx clip runner
|
||||
- Integrated onnx clip runner
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixes login button requiring double click issue
|
||||
- Fixes Collection sort state not preserved issue
|
||||
- Fixes continuous export causing app crash
|
||||
- Improves ML related copies for better distinction from clip
|
||||
- Added Better favicon for light mode
|
||||
- Fixed face indexing issues
|
||||
- Fixed thumbnail load issue
|
||||
- Fixes login button requiring double click issue
|
||||
- Fixes Collection sort state not preserved issue
|
||||
- Fixes continuous export causing app crash
|
||||
- Improves ML related copies for better distinction from clip
|
||||
- Added Better favicon for light mode
|
||||
- Fixed face indexing issues
|
||||
- Fixed thumbnail load issue
|
||||
|
||||
## v1.6.60
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix Thumbnail Orientation issue
|
||||
- Fix ML logging issue
|
||||
- Fix Thumbnail Orientation issue
|
||||
- Fix ML logging issue
|
||||
|
||||
## v1.6.59
|
||||
|
||||
### New
|
||||
|
||||
- Added arm64 builds for linux
|
||||
- Added arm64 builds for linux
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix Editor file not loading issue
|
||||
- Fix ML results missing thumbnail issue
|
||||
- Fix Editor file not loading issue
|
||||
- Fix ML results missing thumbnail issue
|
||||
|
||||
## v1.6.58
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix File load issue
|
||||
- Fix File load issue
|
||||
|
||||
## v1.6.57
|
||||
|
||||
### New Features
|
||||
|
||||
- Added encrypted Disk caching for files
|
||||
- Added option to customize cache folder location
|
||||
- Added encrypted Disk caching for files
|
||||
- Added option to customize cache folder location
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed caching issue,causing multiple download of file during ml sync
|
||||
- Fixed caching issue,causing multiple download of file during ml sync
|
||||
|
||||
## v1.6.55
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Added manage family portal option if add-on is active
|
||||
- Fixed filename date parsing issue
|
||||
- Fixed storage limit ui glitch
|
||||
- Fixed dedupe page layout issue
|
||||
- Fixed ElectronAPI refactoring issue
|
||||
- Fixed Search related issues
|
||||
- Added manage family portal option if add-on is active
|
||||
- Fixed filename date parsing issue
|
||||
- Fixed storage limit ui glitch
|
||||
- Fixed dedupe page layout issue
|
||||
- Fixed ElectronAPI refactoring issue
|
||||
- Fixed Search related issues
|
||||
|
||||
## v1.6.54
|
||||
|
||||
### New Features
|
||||
|
||||
- Added support for HEIC and raw image in photo editor
|
||||
- Added support for HEIC and raw image in photo editor
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed 16bit HDR HEIC images support
|
||||
- Fixed blocked login due safe storage issue
|
||||
- Fixed Search related issues
|
||||
- Fixed issue of watch folder not cleared on logout
|
||||
- other under the hood ui/ux improvements
|
||||
- Fixed 16bit HDR HEIC images support
|
||||
- Fixed blocked login due safe storage issue
|
||||
- Fixed Search related issues
|
||||
- Fixed issue of watch folder not cleared on logout
|
||||
- other under the hood ui/ux improvements
|
||||
|
||||
## v1.6.53
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed watch folder disabled issue
|
||||
- Fixed BF Add on related issues
|
||||
- Fixed clip sync issue and added better logging
|
||||
- Fixed mov file upload
|
||||
- Fixed clip extraction related issue
|
||||
- Fixed watch folder disabled issue
|
||||
- Fixed BF Add on related issues
|
||||
- Fixed clip sync issue and added better logging
|
||||
- Fixed mov file upload
|
||||
- Fixed clip extraction related issue
|
||||
|
||||
## v1.6.52
|
||||
|
||||
### New Features
|
||||
|
||||
- Added Clip Desktop on windows
|
||||
- Added Clip Desktop on windows
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fixed google json matching issue
|
||||
- other under-the-hood changes to improve performance and bug fixes
|
||||
- fixed google json matching issue
|
||||
- other under-the-hood changes to improve performance and bug fixes
|
||||
|
||||
## v1.6.50
|
||||
|
||||
### New Features
|
||||
|
||||
- Added Clip desktop
|
||||
- Added Clip desktop
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed desktop downloaded file had extra dot in the name
|
||||
- Cleanup error messages
|
||||
- fix the motion photo clustering issue
|
||||
- Add option to disable cf proxy locally
|
||||
- other under-the-hood changes to improve UX
|
||||
- Fixed desktop downloaded file had extra dot in the name
|
||||
- Cleanup error messages
|
||||
- fix the motion photo clustering issue
|
||||
- Add option to disable cf proxy locally
|
||||
- other under-the-hood changes to improve UX
|
||||
|
||||
## v1.6.49
|
||||
|
||||
|
@ -137,54 +137,54 @@ Check out our [blog](https://ente.io/blog/introducing-web-desktop-photo-editor/)
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed misaligned icons in photo-viewer
|
||||
- Fixed issue with Motion photo upload
|
||||
- Fixed issue with Live-photo upload
|
||||
- other minor ux improvement
|
||||
- Fixed misaligned icons in photo-viewer
|
||||
- Fixed issue with Motion photo upload
|
||||
- Fixed issue with Live-photo upload
|
||||
- other minor ux improvement
|
||||
|
||||
## v1.6.46
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixes OOM crashes during file upload [#1379](https://github.com/ente-io/photos-web/pull/1379)
|
||||
- Fixes OOM crashes during file upload [#1379](https://github.com/ente-io/photos-web/pull/1379)
|
||||
|
||||
## v1.6.45
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed app keeps reloading issue [#235](https://github.com/ente-io/photos-desktop/pull/235)
|
||||
- Fixed dng and arw preview issue [#1378](https://github.com/ente-io/photos-web/pull/1378)
|
||||
- Added view crash report option (help menu) for user to share electron crash report locally
|
||||
- Fixed app keeps reloading issue [#235](https://github.com/ente-io/photos-desktop/pull/235)
|
||||
- Fixed dng and arw preview issue [#1378](https://github.com/ente-io/photos-web/pull/1378)
|
||||
- Added view crash report option (help menu) for user to share electron crash report locally
|
||||
|
||||
## v1.6.44
|
||||
|
||||
- Upgraded electron to get latest security patches and other improvements.
|
||||
- Upgraded electron to get latest security patches and other improvements.
|
||||
|
||||
## v1.6.43
|
||||
|
||||
### Added
|
||||
|
||||
- #### Check for update and changelog option
|
||||
- #### Check for update and changelog option
|
||||
|
||||
Added options to check for update manually and a view changelog via the app menubar
|
||||
|
||||
- #### Opt out of crash reporting
|
||||
- #### Opt out of crash reporting
|
||||
|
||||
Added option to out of a crash reporting, it can accessed from the settings -> preferences -> disable crash reporting
|
||||
|
||||
- #### Type search
|
||||
- #### Type search
|
||||
|
||||
Added new search option to search files based on file type i.e, image, video, live-photo.
|
||||
|
||||
- #### Manual Convert Button
|
||||
- #### Manual Convert Button
|
||||
|
||||
In case the video is not playable, Now there is a convert button which can be used to trigger conversion of the video to supported format.
|
||||
|
||||
- #### File Download Progress
|
||||
- #### File Download Progress
|
||||
|
||||
The file loader now also shows the exact percentage download progress, instead of just a simple loader.
|
||||
|
||||
- #### Bug fixes & other enhancements
|
||||
- #### Bug fixes & other enhancements
|
||||
|
||||
We have squashed a few pesky bugs that were reported by our community
|
||||
|
||||
|
@ -192,21 +192,21 @@ Check out our [blog](https://ente.io/blog/introducing-web-desktop-photo-editor/)
|
|||
|
||||
### Added
|
||||
|
||||
- #### Hidden albums
|
||||
- #### Hidden albums
|
||||
|
||||
You can now hide albums, just like individual memories.
|
||||
|
||||
- #### Email verification
|
||||
- #### Email verification
|
||||
|
||||
We have now made email verification optional, so you can sign in with just your email address and password, without waiting for a verification code.
|
||||
|
||||
You can opt in / out of email verification from Settings > Security.
|
||||
|
||||
- #### Download Album
|
||||
- #### Download Album
|
||||
|
||||
You can now chose the download location for downloading albums. Along with that we have also added progress bar for album download.
|
||||
|
||||
- #### Bug fixes & other enhancements
|
||||
- #### Bug fixes & other enhancements
|
||||
|
||||
We have squashed a few pesky bugs that were reported by our community
|
||||
|
||||
|
|
|
@ -10,33 +10,28 @@ To know more about Ente, see [our main README](../README.md) or visit
|
|||
|
||||
## Building from source
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> We moved a few things around when switching to a monorepo recently, so this
|
||||
> folder might not build with the instructions below. Hang tight, we're on it,
|
||||
> will fix things if.
|
||||
|
||||
Fetch submodules
|
||||
|
||||
```sh
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
|
||||
Run the app
|
||||
Run in development mode (with hot reload)
|
||||
|
||||
```sh
|
||||
yarn start
|
||||
yarn dev
|
||||
```
|
||||
|
||||
To recompile automatically using electron-reload, run this in a separate
|
||||
terminal:
|
||||
> [!CAUTION]
|
||||
>
|
||||
> `yarn dev` is currently not working (we'll fix soon). If you just want to
|
||||
> build from source and use the generated binary, use `yarn build`.
|
||||
|
||||
```bash
|
||||
yarn watch
|
||||
Or create a binary for your platform
|
||||
|
||||
```sh
|
||||
yarn build
|
||||
```
|
||||
|
||||
That's the gist of it. For more development related documentation, see
|
||||
[docs](docs/README.md).
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ente Photos</title>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ente Photos</title>
|
||||
</head>
|
||||
|
||||
<body style="background-color: black;">
|
||||
<div style=" height: 95vh;width: 96vw; display: grid; place-items: center; color: white;">
|
||||
<div>
|
||||
<div style="margin-bottom: 10px;">Site unreachable, please try again later</div>
|
||||
<button onClick="window[`ElectronAPIs`].reloadWindow()">Reload</button>
|
||||
<body style="background-color: black">
|
||||
<div
|
||||
style="
|
||||
height: 95vh;
|
||||
width: 96vw;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div style="margin-bottom: 10px">
|
||||
Site unreachable, please try again later
|
||||
</div>
|
||||
<button onClick="window[`ElectronAPIs`].reloadWindow()">
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,30 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ente Photos</title>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ente Photos</title>
|
||||
</head>
|
||||
|
||||
<body style="background-color: black;">
|
||||
<div style="display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 90vh;">
|
||||
<div style="width:64px;"><svg version="1.1" id="L9" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100"
|
||||
enable-background="new 0 0 0 0" xml:space="preserve">
|
||||
<path fill="#2dc262"
|
||||
d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
|
||||
<animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s"
|
||||
from="0 50 50" to="360 50 50" repeatCount="indefinite" />
|
||||
</path>
|
||||
</svg>
|
||||
<body style="background-color: black">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 90vh;
|
||||
"
|
||||
>
|
||||
<div style="width: 64px">
|
||||
<svg
|
||||
version="1.1"
|
||||
id="L9"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
enable-background="new 0 0 0 0"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<path
|
||||
fill="#2dc262"
|
||||
d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
dur="1s"
|
||||
from="0 50 50"
|
||||
to="360 50 50"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Electron Updater Example</title>
|
||||
</head>
|
||||
<body>
|
||||
Current version: <span id="version">vX.Y.Z</span>
|
||||
<div id="messages"></div>
|
||||
<script>
|
||||
// Display the current version
|
||||
let version = window.location.hash.substring(1);
|
||||
document.getElementById('version').innerText = version;
|
||||
<head>
|
||||
<title>Electron Updater Example</title>
|
||||
</head>
|
||||
<body>
|
||||
Current version: <span id="version">vX.Y.Z</span>
|
||||
<div id="messages"></div>
|
||||
<script>
|
||||
// Display the current version
|
||||
let version = window.location.hash.substring(1);
|
||||
document.getElementById("version").innerText = version;
|
||||
|
||||
// Listen for messages
|
||||
const {ipcRenderer} = require('electron');
|
||||
ipcRenderer.on('message', function(event, text) {
|
||||
var container = document.getElementById('messages');
|
||||
var message = document.createElement('div');
|
||||
message.innerHTML = text;
|
||||
container.appendChild(message);
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
// Listen for messages
|
||||
const { ipcRenderer } = require("electron");
|
||||
ipcRenderer.on("message", function (event, text) {
|
||||
var container = document.getElementById("messages");
|
||||
var message = document.createElement("div");
|
||||
message.innerHTML = text;
|
||||
container.appendChild(message);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
Notes on how to upload electron symbols directly to sentry instance (bypassing the CF limits) cc @abhi just for future reference
|
||||
|
||||
To upload electron symbols
|
||||
|
||||
1. Create a tunnel
|
||||
```
|
||||
ssh -p 7426 -N -L 8080:localhost:9000 sentry
|
||||
```
|
||||
|
||||
2. Add the following env file
|
||||
```
|
||||
NEXT_PUBLIC_IS_SENTRY_ENABLED = yes
|
||||
SENTRY_ORG = ente
|
||||
SENTRY_PROJECT = bhari-frame
|
||||
SENTRY_URL2 = https://sentry.ente.io/
|
||||
SENTRY_URL = http://localhost:8080/
|
||||
SENTRY_AUTH_TOKEN = xxx
|
||||
SENTRY_LOG_LEVEL = debug
|
||||
```
|
||||
|
||||
3. Run
|
||||
|
||||
```
|
||||
node sentry-symbols.js
|
||||
```
|
11
desktop/docs/README.md
Normal file
11
desktop/docs/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Developer docs
|
||||
|
||||
If you just want to run the Ente Photos desktop app locally or develop it, you
|
||||
can do:
|
||||
|
||||
yarn install
|
||||
yarn dev
|
||||
|
||||
The docs in this directory provide more details that some developers might find
|
||||
useful. You might also find the developer docs for
|
||||
[web](../../web/docs/README.md) useful.
|
14
desktop/docs/dependencies.md
Normal file
14
desktop/docs/dependencies.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Dependencies
|
||||
|
||||
See [web/docs/dependencies.md](../../web/docs/dependencies.md) for general web
|
||||
specific dependencies. See [electron.md](electron.md) for our main dependency,
|
||||
Electron. The rest of this document describes the remaining, desktop specific
|
||||
dependencies that are used by the Photos desktop app.
|
||||
|
||||
## Electron related
|
||||
|
||||
### next-electron-server
|
||||
|
||||
This spins up a server for serving files using a protocol handler inside our
|
||||
Electron process. This allows us to directly use the output produced by `next
|
||||
build` for loading into our renderer process.
|
4
desktop/docs/dev.md
Normal file
4
desktop/docs/dev.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Development tips
|
||||
|
||||
- `yarn build:quick` is a variant of `yarn build` that uses the
|
||||
`--config.compression=store` flag to (slightly) speed up electron-builder.
|
21
desktop/docs/electron.md
Normal file
21
desktop/docs/electron.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Electron
|
||||
|
||||
[Electron](https://www.electronjs.org) is a cross-platform (Linux, Windows,
|
||||
macOS) way for creating desktop apps using TypeScript.
|
||||
|
||||
Electron embeds Chromium and Node.js in the generated app's binary. The
|
||||
generated app thus consists of two separate processes - the _main_ process, and
|
||||
a _renderer_ process.
|
||||
|
||||
- The _main_ process is runs the embedded node. This process can deal with the
|
||||
host OS - it is conceptually like a `node` repl running on your machine. In our
|
||||
case, the TypeScript code (in the `src/` directory) gets transpiled by `tsc`
|
||||
into JavaScript in the `build/app/` directory, which gets bundled in the
|
||||
generated app's binary and is loaded by the node (main) process when the app
|
||||
starts.
|
||||
|
||||
- The _renderer_ process is a regular web app that gets loaded into the embedded
|
||||
Chromium. When the main process starts, it creates a new "window" that shows
|
||||
this embedded Chromium. In our case, we build and bundle a static export of
|
||||
the [Photos web app](../web/README.md) in the generated app. This gets loaded
|
||||
by the embedded Chromium at runtime, acting as the app's UI.
|
|
@ -1,10 +1,65 @@
|
|||
{
|
||||
"name": "ente",
|
||||
"productName": "ente",
|
||||
"version": "1.6.63",
|
||||
"private": true,
|
||||
"description": "Desktop client for ente.io",
|
||||
"description": "Desktop client for Ente Photos",
|
||||
"author": "Ente <code@ente.io>",
|
||||
"main": "app/main.js",
|
||||
"scripts": {
|
||||
"build": "yarn build-renderer && yarn build-main",
|
||||
"build-main": "tsc && electron-builder",
|
||||
"build-main:quick": "tsc && electron-builder --config.compression=store",
|
||||
"build-renderer": "cd ../web && yarn install && yarn build:photos && cd ../desktop && rm -f out && ln -sf ../web/apps/photos/out",
|
||||
"build:quick": "yarn build-renderer && yarn build-main:quick",
|
||||
"dev": "concurrently --names 'main,rndr,tscw' \"yarn dev-main\" \"yarn dev-renderer\" \"yarn dev-main-watch\"",
|
||||
"dev-main": "tsc && electron app/main.js",
|
||||
"dev-main-watch": "tsc --watch --preserveWatchOutput",
|
||||
"dev-renderer": "cd ../web && yarn install && yarn dev:photos",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"lint": "yarn prettier --check . && eslint \"src/**/*.ts\"",
|
||||
"lint-fix": "yarn prettier --write . && eslint --fix src"
|
||||
},
|
||||
"dependencies": {
|
||||
"any-shell-escape": "^0.1.1",
|
||||
"auto-launch": "^5.0.5",
|
||||
"chokidar": "^3.5.3",
|
||||
"compare-versions": "^6.1.0",
|
||||
"electron-log": "^4.3.5",
|
||||
"electron-reload": "^2.0.0-alpha.1",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.3.8",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"get-folder-size": "^2.0.1",
|
||||
"html-entities": "^2.4.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"next-electron-server": "^1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"onnxruntime-node": "^1.16.3",
|
||||
"promise-fs": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.2",
|
||||
"@types/ffmpeg-static": "^3.0.1",
|
||||
"@types/get-folder-size": "^2.0.0",
|
||||
"@types/node": "18.15.0",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/promise-fs": "^2.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
||||
"@typescript-eslint/parser": "^5.28.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"electron": "^25.8.4",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron-builder-notarize": "^1.2.0",
|
||||
"electron-download": "^4.1.1",
|
||||
"eslint": "^7.23.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^3",
|
||||
"prettier-plugin-organize-imports": "^3.2",
|
||||
"prettier-plugin-packagejson": "^2.4",
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"build": {
|
||||
"appId": "io.ente.bhari-frame",
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
|
@ -42,7 +97,7 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"icon": "./build/icon.icns",
|
||||
"icon": "./resources/icon.icns",
|
||||
"category": "Photography"
|
||||
},
|
||||
"mac": {
|
||||
|
@ -57,98 +112,24 @@
|
|||
"x64ArchFiles": "Contents/Resources/ggmlclip-mac"
|
||||
},
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "build",
|
||||
"to": "resources",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"asarUnpack": [
|
||||
"node_modules/ffmpeg-static/bin/${os}/${arch}/ffmpeg",
|
||||
"node_modules/ffmpeg-static/index.js",
|
||||
"node_modules/ffmpeg-static/package.json"
|
||||
],
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "build",
|
||||
"to": "resources"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"app/**/*",
|
||||
{
|
||||
"from": "ui/apps/photos",
|
||||
"to": "ui",
|
||||
"filter": [
|
||||
"!**/*",
|
||||
"out/**/*"
|
||||
]
|
||||
}
|
||||
"out"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"prebuild": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
|
||||
"prepare": "husky install",
|
||||
"lint": "eslint -c .eslintrc --ext .ts src",
|
||||
"watch": "tsc -w",
|
||||
"build-main": "yarn install && tsc",
|
||||
"start-main": "yarn build-main && electron app/main.js",
|
||||
"start-renderer": "cd ui && yarn install && yarn dev:photos",
|
||||
"start": "concurrently \"yarn start-main\" \"yarn start-renderer\"",
|
||||
"build-renderer": "cd ui && yarn install && yarn export:photos",
|
||||
"build": "yarn build-renderer && yarn build-main",
|
||||
"test-release": "cross-env IS_TEST_RELEASE=true yarn build && electron-builder --config.compression=store"
|
||||
},
|
||||
"author": "ente <code@ente.io>",
|
||||
"devDependencies": {
|
||||
"@sentry/cli": "^1.68.0",
|
||||
"@types/auto-launch": "^5.0.2",
|
||||
"@types/ffmpeg-static": "^3.0.1",
|
||||
"@types/get-folder-size": "^2.0.0",
|
||||
"@types/node": "18.15.0",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/promise-fs": "^2.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
||||
"@typescript-eslint/parser": "^5.28.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^25.8.4",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron-builder-notarize": "^1.2.0",
|
||||
"electron-download": "^4.1.1",
|
||||
"eslint": "^7.23.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.1",
|
||||
"prettier": "2.5.1",
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/electron": "^2.5.1",
|
||||
"any-shell-escape": "^0.1.1",
|
||||
"auto-launch": "^5.0.5",
|
||||
"chokidar": "^3.5.3",
|
||||
"compare-versions": "^6.1.0",
|
||||
"electron-log": "^4.3.5",
|
||||
"electron-reload": "^2.0.0-alpha.1",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "^4.3.8",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"get-folder-size": "^2.0.1",
|
||||
"html-entities": "^2.4.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"next-electron-server": "file:./thirdparty/next-electron-server",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"onnxruntime-node": "^1.16.3",
|
||||
"promise-fs": "^2.1.1"
|
||||
},
|
||||
"productName": "ente",
|
||||
"standard": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{js,jsx,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write --ignore-unknown"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
let SentryCli;
|
||||
let download;
|
||||
|
||||
try {
|
||||
SentryCli = require('@sentry/cli');
|
||||
download = require('electron-download');
|
||||
} catch (e) {
|
||||
console.error('ERROR: Missing required packages, please run:');
|
||||
console.error('npm install --save-dev @sentry/cli electron-download');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const SYMBOL_CACHE_FOLDER = '.electron-symbols';
|
||||
const sentryCli = new SentryCli('./sentry.properties');
|
||||
|
||||
async function main() {
|
||||
const version = getElectronVersion();
|
||||
if (!version) {
|
||||
console.error('Cannot detect electron version, check that electron is installed');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('We are starting to download all possible electron symbols');
|
||||
console.log('We need it in order to symbolicate native crashes');
|
||||
console.log(
|
||||
'This step is only needed once whenever you update your electron version',
|
||||
);
|
||||
console.log('Just call this script again it should do everything for you.');
|
||||
|
||||
let zipPath = await downloadSymbols({
|
||||
version,
|
||||
platform: 'darwin',
|
||||
arch: 'x64',
|
||||
dsym: true,
|
||||
});
|
||||
await sentryCli.execute(['upload-dif', '-t', 'dsym', zipPath], true);
|
||||
|
||||
zipPath = await downloadSymbols({
|
||||
version,
|
||||
platform: 'win32',
|
||||
arch: 'ia32',
|
||||
symbols: true,
|
||||
});
|
||||
await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true);
|
||||
|
||||
zipPath = await downloadSymbols({
|
||||
version,
|
||||
platform: 'win32',
|
||||
arch: 'x64',
|
||||
symbols: true,
|
||||
});
|
||||
await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true);
|
||||
|
||||
zipPath = await downloadSymbols({
|
||||
version,
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
symbols: true,
|
||||
});
|
||||
await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true);
|
||||
|
||||
console.log('Finished downloading and uploading to Sentry');
|
||||
console.log(`Feel free to delete the ${SYMBOL_CACHE_FOLDER}`);
|
||||
}
|
||||
|
||||
function getElectronVersion() {
|
||||
try {
|
||||
return require('electron/package.json').version;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadSymbols(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
download(
|
||||
{
|
||||
...options,
|
||||
cache: SYMBOL_CACHE_FOLDER,
|
||||
},
|
||||
(err, zipPath) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(zipPath);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(e => console.error(e));
|
|
@ -1,3 +0,0 @@
|
|||
defaults.url=https://sentry.ente.io/
|
||||
defaults.org=ente
|
||||
defaults.project=desktop-photos
|
|
@ -1,16 +1,16 @@
|
|||
import { ipcRenderer } from 'electron/renderer';
|
||||
import path from 'path';
|
||||
import { existsSync, mkdir, rmSync } from 'promise-fs';
|
||||
import { DiskCache } from '../services/diskCache';
|
||||
import { ipcRenderer } from "electron/renderer";
|
||||
import path from "path";
|
||||
import { existsSync, mkdir, rmSync } from "promise-fs";
|
||||
import { DiskCache } from "../services/diskCache";
|
||||
|
||||
const ENTE_CACHE_DIR_NAME = 'ente';
|
||||
const ENTE_CACHE_DIR_NAME = "ente";
|
||||
|
||||
export const getCacheDirectory = async () => {
|
||||
const customCacheDir = await getCustomCacheDirectory();
|
||||
if (customCacheDir && existsSync(customCacheDir)) {
|
||||
return customCacheDir;
|
||||
}
|
||||
const defaultSystemCacheDir = await ipcRenderer.invoke('get-path', 'cache');
|
||||
const defaultSystemCacheDir = await ipcRenderer.invoke("get-path", "cache");
|
||||
return path.join(defaultSystemCacheDir, ENTE_CACHE_DIR_NAME);
|
||||
};
|
||||
|
||||
|
@ -22,7 +22,7 @@ const getCacheBucketDir = async (cacheName: string) => {
|
|||
|
||||
export async function openDiskCache(
|
||||
cacheName: string,
|
||||
cacheLimitInBytes?: number
|
||||
cacheLimitInBytes?: number,
|
||||
) {
|
||||
const cacheBucketDir = await getCacheBucketDir(cacheName);
|
||||
if (!existsSync(cacheBucketDir)) {
|
||||
|
@ -42,11 +42,11 @@ export async function deleteDiskCache(cacheName: string) {
|
|||
}
|
||||
|
||||
export async function setCustomCacheDirectory(
|
||||
directory: string
|
||||
directory: string,
|
||||
): Promise<void> {
|
||||
await ipcRenderer.invoke('set-custom-cache-directory', directory);
|
||||
await ipcRenderer.invoke("set-custom-cache-directory", directory);
|
||||
}
|
||||
|
||||
async function getCustomCacheDirectory(): Promise<string> {
|
||||
return await ipcRenderer.invoke('get-custom-cache-directory');
|
||||
return await ipcRenderer.invoke("get-custom-cache-directory");
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
import { ipcRenderer } from 'electron';
|
||||
import { writeStream } from '../services/fs';
|
||||
import { isExecError } from '../utils/error';
|
||||
import { parseExecError } from '../utils/error';
|
||||
import { Model } from '../types';
|
||||
import { ipcRenderer } from "electron";
|
||||
import { writeStream } from "../services/fs";
|
||||
import { Model } from "../types";
|
||||
import { isExecError, parseExecError } from "../utils/error";
|
||||
|
||||
export async function computeImageEmbedding(
|
||||
model: Model,
|
||||
imageData: Uint8Array
|
||||
imageData: Uint8Array,
|
||||
): Promise<Float32Array> {
|
||||
let tempInputFilePath = null;
|
||||
try {
|
||||
tempInputFilePath = await ipcRenderer.invoke('get-temp-file-path', '');
|
||||
tempInputFilePath = await ipcRenderer.invoke("get-temp-file-path", "");
|
||||
const imageStream = new Response(imageData.buffer).body;
|
||||
await writeStream(tempInputFilePath, imageStream);
|
||||
const embedding = await ipcRenderer.invoke(
|
||||
'compute-image-embedding',
|
||||
"compute-image-embedding",
|
||||
model,
|
||||
tempInputFilePath
|
||||
tempInputFilePath,
|
||||
);
|
||||
return embedding;
|
||||
} catch (err) {
|
||||
|
@ -28,20 +27,20 @@ export async function computeImageEmbedding(
|
|||
}
|
||||
} finally {
|
||||
if (tempInputFilePath) {
|
||||
await ipcRenderer.invoke('remove-temp-file', tempInputFilePath);
|
||||
await ipcRenderer.invoke("remove-temp-file", tempInputFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function computeTextEmbedding(
|
||||
model: Model,
|
||||
text: string
|
||||
text: string,
|
||||
): Promise<Float32Array> {
|
||||
try {
|
||||
const embedding = await ipcRenderer.invoke(
|
||||
'compute-text-embedding',
|
||||
"compute-text-embedding",
|
||||
model,
|
||||
text
|
||||
text,
|
||||
);
|
||||
return embedding;
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,44 +1,39 @@
|
|||
import { ipcRenderer } from 'electron/renderer';
|
||||
import { logError } from '../services/logging';
|
||||
import { ipcRenderer } from "electron/renderer";
|
||||
import { logError } from "../services/logging";
|
||||
|
||||
export const selectDirectory = async (): Promise<string> => {
|
||||
try {
|
||||
return await ipcRenderer.invoke('select-dir');
|
||||
return await ipcRenderer.invoke("select-dir");
|
||||
} catch (e) {
|
||||
logError(e, 'error while selecting root directory');
|
||||
logError(e, "error while selecting root directory");
|
||||
}
|
||||
};
|
||||
|
||||
export const getAppVersion = async (): Promise<string> => {
|
||||
try {
|
||||
return await ipcRenderer.invoke('get-app-version');
|
||||
return await ipcRenderer.invoke("get-app-version");
|
||||
} catch (e) {
|
||||
logError(e, 'failed to get release version');
|
||||
logError(e, "failed to get release version");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const openDirectory = async (dirPath: string): Promise<void> => {
|
||||
try {
|
||||
await ipcRenderer.invoke('open-dir', dirPath);
|
||||
await ipcRenderer.invoke("open-dir", dirPath);
|
||||
} catch (e) {
|
||||
logError(e, 'error while opening directory');
|
||||
logError(e, "error while opening directory");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const getPlatform = async (): Promise<'mac' | 'windows' | 'linux'> => {
|
||||
export const getPlatform = async (): Promise<"mac" | "windows" | "linux"> => {
|
||||
try {
|
||||
return await ipcRenderer.invoke('get-platform');
|
||||
return await ipcRenderer.invoke("get-platform");
|
||||
} catch (e) {
|
||||
logError(e, 'failed to get platform');
|
||||
logError(e, "failed to get platform");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
logToDisk,
|
||||
openLogDirectory,
|
||||
getSentryUserID,
|
||||
updateOptOutOfCrashReports,
|
||||
} from '../services/logging';
|
||||
export { logToDisk, openLogDirectory } from "../services/logging";
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { keysStore } from '../stores/keys.store';
|
||||
import { safeStorageStore } from '../stores/safeStorage.store';
|
||||
import { uploadStatusStore } from '../stores/upload.store';
|
||||
import { logError } from '../services/logging';
|
||||
import { userPreferencesStore } from '../stores/userPreferences.store';
|
||||
import { watchStore } from '../stores/watch.store';
|
||||
import { logError } from "../services/logging";
|
||||
import { keysStore } from "../stores/keys.store";
|
||||
import { safeStorageStore } from "../stores/safeStorage.store";
|
||||
import { uploadStatusStore } from "../stores/upload.store";
|
||||
import { watchStore } from "../stores/watch.store";
|
||||
|
||||
export const clearElectronStore = () => {
|
||||
try {
|
||||
|
@ -11,9 +10,8 @@ export const clearElectronStore = () => {
|
|||
keysStore.clear();
|
||||
safeStorageStore.clear();
|
||||
watchStore.clear();
|
||||
userPreferencesStore.delete('optOutOfCrashReports');
|
||||
} catch (e) {
|
||||
logError(e, 'error while clearing electron store');
|
||||
logError(e, "error while clearing electron store");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue