Compare commits

...

119 commits

Author SHA1 Message Date
Weblate 95e51732fc Update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Cleanup translation files" hook in Weblate.

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/
2024-07-22 17:59:07 +00:00
Inex Code 48f5a6a5fb Merge remote-tracking branch 'weblate/master'
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-22 20:57:16 +03:00
Inex Code 65322257ec refactor: Only use ws updates if the API version supports it
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-10 19:14:12 +03:00
Inex Code 7c28f92581 fix: Use graphqlTransportWs to automatically ping server and prevent timeouts 2024-07-10 19:14:12 +03:00
Inex Code e0232bfa44 feat: Use Websockets to update server jobs status 2024-07-10 19:14:12 +03:00
Inex Code 135ed30ee3 chore: Update GraphQL API schema 2024-07-10 19:14:12 +03:00
Inex Code 82541bf698 chore: Add graphql_codegen as a dev dependency 2024-07-10 19:14:12 +03:00
Inex Code 37341c7c27 ci: add a symlink to config
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-10 17:32:51 +04:00
Inex Code 59cf273fa9 Merge pull request 'refactor: Introduce a new storage for tokens' (#505) from inex/april-refactor into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #505
2024-07-09 20:06:18 +03:00
Dmitri B c06ea7b0c8 Translated using Weblate (Estonian)
Currently translated at 100.0% (553 of 553 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/et/
2024-07-05 19:30:55 +00:00
Inex Code 1ad8fccbb6 fix: App didn't save the server type and location correctly 2024-07-04 18:05:01 +04:00
Aliaksei Tratseuski ad910d564a Merge remote-tracking branch 'sp/master' into inex/april-refactor 2024-06-30 18:51:24 +04:00
Aliaksei Tratseuski 2fbcc3c232 feat: some more decomposition in hive_config 2024-06-30 18:50:21 +04:00
Aliaksei Tratseuski 4bda23eaf9 fix: re-enabled dio logs (developer debug console) 2024-06-30 18:49:07 +04:00
Aliaksei Tratseuski f7f791cc0c feat: console_page - cleaned up dead code and unused l10n strings 2024-06-30 18:47:26 +04:00
Inex Code e98b1f8e3e Translated using Weblate (Russian)
Currently translated at 99.6% (551 of 553 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/
2024-06-27 18:28:57 +00:00
Inex Code 6fc6bc8548 Translated using Weblate (English)
Currently translated at 100.0% (553 of 553 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/en/
2024-06-27 18:28:57 +00:00
Weblate 0a5adee971 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_ssh
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_ssh/
2024-06-27 18:28:57 +00:00
Weblate 7c9aa8d57e Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/
2024-06-27 18:28:57 +00:00
Inex Code d5386dec1d feat(i18l): Enable Japanese
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-27 21:28:34 +03:00
Inex Code c4951def02 chore: Define a strict requirement of the minor Flutter version.
Some checks reported errors
continuous-integration/drone/push Build was killed
2024-06-27 01:30:21 +03:00
Inex Code 6d8626ca22 Merge pull request 'chore: Update translations' (#525) from translations into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #525
2024-06-26 19:14:57 +03:00
Inex Code 8318a7a7eb Merge branch 'master' into translations
# Conflicts:
#	assets/markdown/how_fallback_ssh-ja.md
#	assets/translations/ar.json
#	assets/translations/az.json
#	assets/translations/be.json
#	assets/translations/cs.json
#	assets/translations/de.json
#	assets/translations/es.json
#	assets/translations/et.json
#	assets/translations/fr.json
#	assets/translations/kk.json
#	assets/translations/pl.json
#	assets/translations/ru.json
#	assets/translations/sk.json
#	assets/translations/th.json
#	assets/translations/uk.json
#	assets/translations/zh-Hans.json
2024-06-26 19:05:36 +03:00
Inex Code 8960298685 feat(ui): Allow selecting text from Markdown text
All checks were successful
continuous-integration/drone/push Build is passing
Closes #470
2024-06-26 18:41:10 +03:00
Aliaksei Tratseuski 85bc997776 feat: db versioning and better logging 2024-06-26 02:11:36 +04:00
Inex Code 78b026ed42 chore: Post-merge cleanup 2024-06-25 18:06:50 +03:00
Inex Code 4ce7b0bcdb Merge branch 'master' into inex/april-refactor
# Conflicts:
#	lib/config/get_it_config.dart
#	lib/config/hive_config.dart
#	lib/logic/api_maps/graphql_maps/graphql_api_map.dart
#	lib/logic/cubit/server_installation/server_installation_repository.dart
#	lib/logic/cubit/server_installation/server_installation_state.dart
#	lib/logic/get_it/api_config.dart
2024-06-25 18:02:51 +03:00
Inex Code ff512dec34 Merge pull request 'language picker, console_page refactor, app settings controller' (#482) from misterfourtytwo/selfprivacy.org.app:feat_token_management into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #482
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
2024-06-25 16:52:09 +03:00
Aliaksei Tratseuski 3fc3a6e7f4 feat: doc comment with clarification of console_logs blacklistedHeaders and hideList 2024-06-19 18:01:13 +04:00
Aliaksei Tratseuski 82a606e320 fix: removed horizontal dividers from app_settings_page 2024-06-19 16:07:36 +04:00
Aliaksei Tratseuski 99a9e5bfed fix: translate server settings page title 2024-06-19 16:06:49 +04:00
Aliaksei Tratseuski dd036890b2 fix: l10n assets format fix, whitespace and keyname 2024-06-19 15:20:15 +04:00
Aliaksei Tratseuski 05800f5900 feat: hide/show console header value button, changed icons from cupertino to material 2024-06-19 15:12:34 +04:00
Aliaksei Tratseuski bd090b646d feat: reset locale to system default from language settings, removed dead code theme_picker code 2024-06-16 04:13:10 +04:00
Aliaksei Tratseuski 38a896ec2e fix: app_settings page UI updates 2024-06-13 22:11:08 +04:00
Aliaksei Tratseuski efd3dfbde5 feat: obscure/remove auth headers from console logs 2024-06-13 21:53:06 +04:00
Aliaksei Tratseuski dfef56c25f Merge remote-tracking branch 'sp/master' into feat_token_management 2024-06-13 18:43:58 +04:00
dettlaff d77dcc7167 fix: update english howto get token from the ssh
Some checks failed
continuous-integration/drone/push Build is failing
2024-06-06 18:26:10 +03:00
Dmitri B f540d2a5de Translated using Weblate (Estonian)
Currently translated at 100.0% (529 of 529 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/et/
2024-05-27 14:25:51 +00:00
TabithiS ef9f00d2a4 Translated using Weblate (Kazakh)
Currently translated at 100.0% (527 of 527 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/kk/
2024-05-25 09:32:25 +00:00
TabithiS f4fa30527a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (527 of 527 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/uk/
2024-05-25 09:32:25 +00:00
Dmitri B 4a92c4f102 Translated using Weblate (Estonian)
Currently translated at 100.0% (527 of 527 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/et/
2024-05-25 09:32:25 +00:00
NaiJi ✨ 0cefe4aac9 Translated using Weblate (Russian)
Currently translated at 99.8% (526 of 527 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/
2024-05-25 09:32:25 +00:00
Weblate c0cf71e749 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_ssh
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_ssh/
2024-05-25 09:32:25 +00:00
Weblate fb7b3e3481 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_terminal
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_terminal/
2024-05-25 09:32:25 +00:00
Thary 71c4893d01 Translated using Weblate (Polish)
Currently translated at 60.0% (3 of 5 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_ssh
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_ssh/pl/
2024-05-25 09:32:25 +00:00
Thary 12da37396a Translated using Weblate (Belarusian)
Currently translated at 100.0% (5 of 5 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_ssh
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_ssh/be/
2024-05-25 09:32:25 +00:00
Thary a80c1aca83 Translated using Weblate (Polish)
Currently translated at 75.0% (6 of 8 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_terminal
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_terminal/pl/
2024-05-25 09:32:25 +00:00
Thary ebb076d9b6 Translated using Weblate (Belarusian)
Currently translated at 87.5% (7 of 8 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_terminal
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_terminal/be/
2024-05-25 09:32:25 +00:00
Thary aff4116796 Translated using Weblate (Belarusian)
Currently translated at 75.0% (389 of 518 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/be/
2024-05-25 09:32:25 +00:00
Thary f8684cb9a8 Translated using Weblate (Polish)
Currently translated at 80.6% (418 of 518 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/pl/
2024-05-25 09:32:25 +00:00
Thary c2b3f65803 Translated using Weblate (Russian)
Currently translated at 99.8% (517 of 518 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/
2024-05-25 09:32:25 +00:00
Inex Code 6df5afd8bc Added translation using Weblate (Esperanto) 2024-05-25 09:32:25 +00:00
shirahara 81a836146d Translated using Weblate (Japanese)
Currently translated at 99.2% (514 of 518 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ja/
2024-05-25 09:32:25 +00:00
shirahara 89bf7f7857 Translated using Weblate (Japanese)
Currently translated at 100.0% (2 of 2 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_hetzner
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_hetzner/ja/
2024-05-25 09:32:25 +00:00
shirahara 7ab2e608b2 Translated using Weblate (Japanese)
Currently translated at 100.0% (2 of 2 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_cloudflare
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_cloudflare/ja/
2024-05-25 09:32:25 +00:00
shirahara b2470b114f Translated using Weblate (Japanese)
Currently translated at 100.0% (2 of 2 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_backblaze
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_backblaze/ja/
2024-05-25 09:32:25 +00:00
shirahara 729138d593 Translated using Weblate (Japanese)
Currently translated at 100.0% (1 of 1 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_digital_ocean
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_digital_ocean/ja/
2024-05-25 09:32:25 +00:00
shirahara e53f7e6725 Translated using Weblate (Japanese)
Currently translated at 100.0% (8 of 8 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_terminal
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_terminal/ja/
2024-05-25 09:32:25 +00:00
shirahara d2cc9bef7d Translated using Weblate (Japanese)
Currently translated at 98.8% (512 of 518 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ja/
2024-05-25 09:32:25 +00:00
shirahara a84dedba29 Translated using Weblate (Japanese)
Currently translated at 100.0% (5 of 5 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_ssh
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_ssh/ja/
2024-05-25 09:32:25 +00:00
shirahara c4df752866 Translated using Weblate (Japanese)
Currently translated at 50.0% (1 of 2 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_hetzner
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_hetzner/ja/
2024-05-25 09:32:25 +00:00
shirahara a95f9cd2b4 Translated using Weblate (Japanese)
Currently translated at 100.0% (2 of 2 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_old
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_old/ja/
2024-05-25 09:32:25 +00:00
shirahara e84f229896 Translated using Weblate (Japanese)
Currently translated at 37.5% (3 of 8 strings)

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_fallback_terminal
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_fallback_terminal/ja/
2024-05-25 09:32:25 +00:00
shirahara cc3fac3e4e Translated using Weblate (Japanese)
Currently translated at 76.0% (394 of 518 strings)

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ja/
2024-05-25 09:32:25 +00:00
Weblate d469f56871 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: SelfPrivacy/SelfPrivacy App
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/
2024-05-25 09:32:25 +00:00
Weblate 93c1abedc3 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: SelfPrivacy/SelfPrivacy App Markdown: how_digital_ocean
Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app-markdown-how_digital_ocean/
2024-05-25 09:32:25 +00:00
NaiJi ✨ 291a6507ae feat(jobs): Implement garbage collection job (#506)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #506
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
Co-authored-by: NaiJi <naijiworld@protonmail.com>
Co-committed-by: NaiJi <naijiworld@protonmail.com>
2024-05-25 12:32:21 +03:00
Aliaksei Tratseuski 4e0779f5e7 feat: some more work on console_page
* console_log's copy data is now a valid json object for all log types
* graphQLResponse now provides raw response object for copy
* console_model now handles pause in itself, so UI pipeline doesn't disturb pause (like when revisiting page / hot reloading)
* some minor console_page UI tweaks
2024-05-20 03:19:30 +04:00
Aliaksei Tratseuski 0ee46e1c1e feat: native language names for locale picker 2024-05-18 02:45:05 +04:00
Inex Code 9e56afba50 fix: Clear serverInstallationWizardData after the wizard is finished 2024-05-15 19:23:39 +03:00
Aliaksei Tratseuski c70edb957d fix: center empty_page_placeholder title alignment 2024-05-15 20:20:59 +04:00
Aliaksei Tratseuski a7ed0d20b2 Merge remote-tracking branch 'sp/master' into feat_token_management 2024-05-15 20:12:13 +04:00
Aliaksei Tratseuski 71bd5e5ad2 fix: brand_header now extends preferred_size_widget 2024-05-15 20:01:27 +04:00
Aliaksei Tratseuski 70b2fc28ab chore: my personal generated files on macos. (inex asked for em) 2024-05-15 19:49:50 +04:00
Aliaksei Tratseuski 5e27b369ca chore: some missing async/awaits. 2024-05-15 19:47:41 +04:00
Aliaksei Tratseuski fcf120bc0c feat: list_tiles ink(button effects) now has circular(12) border. 2024-05-15 19:47:00 +04:00
Aliaksei Tratseuski 1e75dbcb81 feat: root_scaffold_with_subroutes
rewrote root_scaffold_with_navigation:
* extracted common code
* removed dead one
* cleaned up remaining one
* fixed translations update on language change
2024-05-15 19:45:04 +04:00
Aliaksei Tratseuski ea2cc28ac9 feat: introduced app_controller, rehooked dependencies from app_settings_cubit, added language picker to settings_page 2024-05-15 19:39:16 +04:00
Inex Code e9f13c5471 fix: Server couldn't install due to a faulty null check 2024-05-15 17:58:24 +03:00
Inex Code 1e024a236b refactor: Move information about tokens to the Resources model, and introduce a WizardData model 2024-05-15 17:16:59 +03:00
Aliaksei Tratseuski 0ad15061a3 chore: updated api codegen code 2024-05-15 15:04:32 +04:00
Aliaksei Tratseuski 53ea69a000 fix: minor code tweaks (no functional changes) 2024-05-15 14:57:52 +04:00
Aliaksei Tratseuski 161c5b7fc5 fix: made root destination list const, removed translations from objects 2024-05-15 14:45:24 +04:00
Aliaksei Tratseuski 1c8cb82e2a feat(strings): added new application_settings strings, sorted keys to correspond to ui order, fixed some be translations 2024-05-15 14:43:38 +04:00
Aliaksei Tratseuski 5033fa3b49 chore: version bump, changed discountinued package_info to package_info_plus 2024-05-15 14:41:31 +04:00
Inex Code 4930fc2387 feat: Show the error screen when libsecret fails
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-02 15:05:38 +03:00
Aliaksei Tratseuski c2a77b9fc5 fix: removed duplicate insertion of localization widget (was both in main and in app widget) 2024-05-01 03:19:18 +04:00
Aliaksei Tratseuski 844039e0f2 feat: simplified digital ocean town name to country mapper. 2024-05-01 02:59:47 +04:00
Aliaksei Tratseuski 6eb5299d46 feat: proper separate getter for clipboard content 2024-04-30 23:25:51 +04:00
Aliaksei Tratseuski 47f3d5f53c feat: added translations to some of console page elements, empty view when there are 0 logs in console yet. 2024-04-30 02:49:06 +04:00
Aliaksei Tratseuski 8919a50bf3 Merge remote-tracking branch 'sp/master' into feat_token_management
Keeping PR up-to-date.
2024-04-29 18:02:46 +04:00
Aliaksei Tratseuski acc007894c feat: cleaned up connection status bloc code 2024-04-29 18:02:23 +04:00
Inex Code 11d0e58334 fix: Flatpak builds didn't work 2024-04-26 18:08:04 +03:00
NaiJi ✨ a6b846cc78 feat(backups): Show how much space a service uses on backup (#500)
All checks were successful
continuous-integration/drone/push Build is passing
Fixes #434

![image](/attachments/351cc025-8dae-44f2-9bca-18f8950e0780)

Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #500
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
Co-authored-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
Co-committed-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
2024-04-24 13:18:02 +03:00
NaiJi ✨ 6819192219 feat: Add country names to installation process (#501)
All checks were successful
continuous-integration/drone/push Build is passing
Fixes #494

Reviewed-on: #501
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
Co-authored-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
Co-committed-by: NaiJi  <naiji@noreply.git.selfprivacy.org>
2024-04-24 12:54:32 +03:00
Aliaksei Tratseuski 00545c34b4 feat: console log feature refactor.
listing scroll performance fix, uniform code and widget UI for different log item types, dialog data can now be selected & copy-pasted
2024-04-20 13:53:55 +04:00
Aliaksei Tratseuski 22fbbb051e feat: infobox changed to use wrap.
shown as 1 line when content fits, wraps into column when not.
2024-04-20 13:44:14 +04:00
Aliaksei Tratseuski 4f200ae757 fix: typos in field names 2024-04-20 13:37:04 +04:00
Aliaksei Tratseuski 06513b6fa6 fix: typo in provider constructors.
Changed `isAuthotized` to `isAuthorized`.
2024-04-20 03:19:26 +04:00
Aliaksei Tratseuski 32769c9d9f fix: selectable new device key.
In devices menu, when key for the connection of new device is created, one can select key text for copy.
2024-04-20 03:16:38 +04:00
Aliaksei Tratseuski 551305b55a fix: disable automatic scrollbar addition for desktop builds.
If view needs a scrollbar, it should be added on all platforms. Framework, by default, adds them only on desktop, so if we add scrollbars in some places (our main builds are still smartphones), on desktop we will get double scrollbars.
2024-04-20 03:11:08 +04:00
NaiJi ✨ ffdb9d92fb Merge pull request 'fix(backups): Implement filtering for enabled services for backups' (#499) from filter-enabled-backup-services into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #499
Reviewed-by: Inex Code <inex.code@selfprivacy.org>
2024-04-17 18:48:56 +03:00
Aliaksei Tratseuski d8ac8cdbef Merge branch 'master' into feat_token_management 2024-04-17 11:57:18 +04:00
Aliaksei Tratseuski e10869d2a3 Merge remote-tracking branch 'sp/master' 2024-04-17 11:42:23 +04:00
NaiJi ✨ 1c42598787 fix(backups): Implement filtering for enabled services for backups
- Resolve: #433
2024-04-16 23:03:11 +04:00
Inex Code 16d1c8a918 refactor: Remove unused Users Hive box 2024-04-12 15:41:32 +03:00
Inex Code 81f4f93d7c refactor: Fix typos in variable names 2024-04-12 15:13:30 +03:00
dettlaff c179a109fd fix: add subtitle for flash button (#462)
All checks were successful
continuous-integration/drone/push Build is passing
closes #453

![image](/attachments/398ae5b1-df90-43cf-8389-0be4bafde9fd)

idk how to change hover

Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #462
Co-authored-by: dettlaff <dettlaff@riseup.net>
Co-committed-by: dettlaff <dettlaff@riseup.net>
2024-04-11 13:53:31 +03:00
def add2366e6b feat: add copy link to service page (#461)
All checks were successful
continuous-integration/drone/push Build is passing
closes #452

Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #461
Co-authored-by: def <dettlaff@riseup.net>
Co-committed-by: def <dettlaff@riseup.net>
2024-04-11 13:14:20 +03:00
dettlaff 0dc281a4f6 feat: add route to service cards in storage page (#446)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: Inex Code <inex.code@selfprivacy.org>
Reviewed-on: #446
Co-authored-by: dettlaff <dettlaff@riseup.net>
Co-committed-by: dettlaff <dettlaff@riseup.net>
2024-04-11 13:04:22 +03:00
dettlaff a4737e9f05 feat: cubit.state.progress check for connect_to_existing
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-03 13:00:27 +03:00
Inex Code bf66717854 fix(docs): Digital Ocean DNS used wrong manual
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-03 12:54:33 +03:00
Inex Code d3b7f31c65 chore: Upgrade flutter and dependencies 2024-04-02 18:11:29 +03:00
Aliaksei Tratseuski 754d1bace2 fix: migrated gradle plugin application to new style 2024-03-12 03:48:26 +03:00
Aliaksei Tratseuski ba2481fbf0 feat: vscode launch scripts with flavors 2024-03-12 03:46:03 +03:00
Aliaksei Tratseuski a516d60f68 chore: dependencies bump 2024-03-12 03:44:04 +03:00
Aliaksei Tratseuski 93db65dc53 Merge remote-tracking branch 'sp/master' 2024-03-12 01:51:41 +03:00
Aliaksei Tratseuski a3d29400bb added gap package dependency 2024-01-31 08:47:31 +04:00
187 changed files with 6855 additions and 2832 deletions

View file

@ -7,6 +7,7 @@ steps:
commands:
- ln -s /var/lib/drone-runner-exec/.local $HOME/.local
- ln -s /var/lib/drone-runner-exec/fdroid $HOME/fdroid
- ln -s /var/lib/drone-runner-exec/.config $HOME/.config
- name: Run Tests
commands:

6
.gitignore vendored
View file

@ -40,3 +40,9 @@ app.*.symbols
# Obfuscation related
app.*.map.json
# Flatpak
.flatpak-builder/
flatpak-build/
flatpak-repo/
*.flatpak

53
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,53 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "debug",
"request": "launch",
"type": "dart"
},
{
"name": "profile mode",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "release mode",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
{
"name": "debug (fdroid)",
"request": "launch",
"type": "dart",
"args": [
"--flavor",
"fdroid"
]
},
{
"name": "debug (production flavor)",
"request": "launch",
"type": "dart",
"args": [
"--flavor",
"production"
]
},
{
"name": "debug (nightly flavor)",
"request": "launch",
"type": "dart",
"args": [
"--flavor",
"nightly"
]
}
]
}

View file

@ -1,3 +1,10 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
@ -6,10 +13,6 @@ if (localPropertiesFile.exists()) {
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
@ -21,14 +24,9 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
namespace 'org.selfprivacy.app'
compileSdkVersion flutter.compileSdkVersion
namespace 'org.selfprivacy.app'
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
sourceSets {
@ -43,45 +41,50 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "org.selfprivacy.app"
minSdkVersion 21
targetSdkVersion 33
compileSdkVersion 33
targetSdkVersion 34
compileSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
debug {
flavorDimensions "default"
productFlavors {
fdroid {
applicationId "pro.kherel.selfprivacy"
}
production {
applicationIdSuffix ""
profile {
}
nightly {
applicationIdSuffix ".nightly"
versionCode project.getVersionCode()
versionName "nightly-" + project.getVersionCode()
release {
}
}
buildFeatures {
flavorDimensions = ["default"]
}
flavorDimensions "default"
productFlavors {
fdroid {
dimension 'default'
applicationId "pro.kherel.selfprivacy"
}
production {
applicationIdSuffix ""
dimension 'default'
}
nightly {
dimension 'default'
applicationIdSuffix ".nightly"
versionCode project.getVersionCode()
versionName "nightly-" + project.getVersionCode()
@ -93,6 +96,5 @@ flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
dependencies {}

View file

@ -1,6 +1,5 @@
buildscript {
ext.kotlin_version = '1.9.21'
ext.getVersionCode = { ->
ext.getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
@ -13,15 +12,6 @@ buildscript {
return -1
}
}
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {

View file

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View file

@ -1,11 +1,25 @@
include ':app'
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.9.21" apply false
}
include ":app"

View file

@ -1,8 +1,8 @@
### How to get Backblaze API Token
1. Visit the following link and authorize: https://secure.backblaze.com/user_signin.htm
2. On the left side of the interface, select **App Keys** in the **B2 Cloud Storage** subcategory.
3. Click on the blue **Generate New Master Application Key** button.
4. In the appeared pop-up window confirm the generation.
5. Save _keyID_ and _applicationKey_ in the safe place. For example, in the password manager.
### BackblazeのAPIトークンの取得方法
1. 次のリンクを開き、認証: https://secure.backblaze.com/user_signin.htm
2. インターフェースの左側にある **B2 Cloud Storage** のサブカテゴリーから **App Keys** (アプリの鍵) を選択。
3. 青色の **Generate New Master Application Key** (新しいマスターアプリケーション鍵を作成) ボタンをクリック。
4. ポップアップウィンドウが表示されるので、作成を承認。
5. _keyID__applicationKey_ をパスワードマネージャーなどの安全な場所に保管してください。
![Backblaze token setup](resource:assets/images/gifs/Backblaze.gif)
![Backblazeのトークン設定](resource:assets/images/gifs/Backblaze.gif)

View file

@ -1,17 +1,17 @@
### How to get Cloudflare API Token
1. Visit the following link: https://dash.cloudflare.com/
2. the right corner, click on the profile icon (a man in a circle). For the mobile version of the site, in the upper left corner, click the **Menu** button (three horizontal bars), in the dropdown menu, click on **My Profile**
3. There are four configuration categories to choose from: *Communication*, *Authentication*, **API Tokens**, *Session*. Choose **API Tokens**.
4. Click on **Create Token** button.
5. Go down to the bottom and see the **Create Custom Token** field and press **Get Started** button on the right side.
6. In the **Token Name** field, give your token a name.
7. Next we have Permissions. In the leftmost field, select **Zone**. In the longest field, center, select **DNS**. In the rightmost field, select **Edit**.
8. Next, right under this line, click Add More. Similar field will appear.
9. In the leftmost field of the new line, select, similar to the last line — **Zone**. In the center — a little different. Here choose the same as in the left — **Zone**. In the rightmost field, select **Read**.
10. Next look at **Zone Resources**. Under this inscription there is a line with two fields. The left must have **Include** and the right must have **Specific Zone**. Once you select Specific Zone, another field appears on the right. Choose your domain in it.
11. Flick to the bottom and press the blue **Continue to Summary** button.
12. Check if you got everything right. A similar string must be present: *Domain — DNS:Edit, Zone:Read*.
13. Click on **Create Token**.
14. We copy the created token, and save it in a reliable place (preferably in the password manager).
### CloudflareのAPIトークンの取得方法
1. 次のリンクを開く: https://dash.cloudflare.com/
2. 右隅にあるプロフィールのアイコン(丸の内部の人間)をクリック。モバイル版のウェブページの場合は、**Menu** ボタン(三本の水平線)をクリックして、ドロップダウンメニューの **My Profile** をクリック。
3. 4つのカテゴリーが表示されるので、 **API Tokens** を選択。
4. **Create Token** のボタンをクリック。
5. 下部に移動し **Create Custom Token** の欄を確認したら、右側にある **Get Started** のボタンをクリック。
6. **Token Name** の欄に、任意のトークン名を入力。
7. 次に権限を設定。最も左にある欄で **Zone** を選択。最も長い欄で **DNS** を選択。最も右にある欄で **Edit** を選択。
8. 次に、この行の下にある Add More をクリック。同様の欄が表示されます。
9. 新しい行の最も左にある欄で **Zone** を選択。中央には、左欄と同じく **Zone** を選択。最も右にある欄で **Read** を選択。
10. 次に **Zone Resources** を確認。その下に、2つの欄の行が表示され、左には **Include** とあり、右には **Specific Zone** とあるはずです。Specific Zoneを選択したら、別の欄が右側に表示されます。あなたのドメインを選択してください。
11. 最も下に移動して、青色の **Continue to Summary** ボタンをクリック。
12. 全てを正しく設定しているか確認してください。以下のように表示されているはずです。*Domain — DNS:Edit, Zone:Read*。
13. **Create Token** をクリック。
14. トークンが表示されるので、パスワードマネージャーなどの安全な場所に保管してください。
![Cloudflare token setup](resource:assets/images/gifs/CloudFlare.gif)
![Cloudflareのトークンの設定](resource:assets/images/gifs/CloudFlare.gif)

View file

@ -1,12 +1,10 @@
### How to get Digital Ocean API Token
1. Visit the following [link](https://cloud.digitalocean.com/) and sign
into newly created account.
2. Enter into previously created project. If you haven't created one,
then please proceed.
3. Go to the "API" link on the left bar.
4. Click on the "Generate New Token".
5. Enter any name for the token.
6. Put expiration time to "No expiry".
7. Check the "Write (optional)" checkbox.
8. Now click on the "Generate Token" button.
9. After that, the token will be shown. Store it in any reliable place, preferably a password manager.
### Digital OceanのAPIトークンの取得方法
1. [このリンク](https://cloud.digitalocean.com/)を開き、新しく作成したアカウントにサインイン。
2. 以前に作成したプロジェクトを開く。プロジェクトを作成していない場合は、作成してください。
3. 左側のバーにある「API」リンクを開く。
4. 「Generate New Token」新しいトークンを作成をクリック。
5. 任意のトークン名を入力。
6. 期限に「No expiry」無期限を指定。
7. 「Write (optional)」(書き込み、オプション)のボックスをチェック。
8. 「Generate Token」トークンを作成ボタンをクリック。
9. トークンが表示されるので、パスワードマネージャーなどの安全な場所に保管してください。

View file

@ -1,5 +1,5 @@
### Как получить API-токен Digital Ocean
1. Перейдите по следующей [link](https://cloud.digitalocean.com/) и войдите
1. Перейдите по следующей [ссылке](https://cloud.digitalocean.com/) и войдите
в только что созданную учетную запись.
2. Войдите в ранее созданный проект. Если вы еще не создали проект,
тогда приступайте.

View file

@ -3,9 +3,9 @@
1. Перейдіть за цим [посилання](https://cloud.digitalocean.com/) і увійдіть у щойно створений обліковий запис.
2. Увійдіть у раніше створений проект. Якщо ви його не створили, тоді, будь ласка, продовжуйте.
3. Перейдіть за посиланням «API» на лівій панелі.
4. Натисніть «Створити новий токен».
4. Натисніть «Generate New Token».
5. Введіть будь-яку назву токену.
6. Встановіть для терміну дії значення «Без терміну дії».
7. Встановіть прапорець «Написати (необов’язково)».
8. Тепер натисніть кнопку «Створити токен».
6. Встановіть для терміну дії значення «No expiry».
7. Встановіть прапорець «Write (optional)».
8. Тепер натисніть кнопку «Generate Token».
9. Після цього буде показано токен. Зберігайте його в будь-якому надійному місці, бажано в менеджері паролів.

View file

@ -1,3 +1,3 @@
In the next window, enter the token obtained from the console of the previous version of the application.
次のウィンドウに、以前のバージョンのアプリケーションのコンソールから取得したトークンを入力してください。
Enter it without the word *Bearer*.
*Bearer* を外して入力してください。

View file

@ -1,19 +1,7 @@
Увайдзіце на ваш сервер як root карыстальнік і праглядзіце змесціва файла `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
Гэты файл будзе мець падобную канструкцыю:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Скапіруйце токен з файла і ўстаўце яго ў наступнае акно.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Přihlaste se k serveru jako uživatel root a podívejte se na obsah souboru `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
Tento soubor bude mít podobnou konstrukci:
```json
{
"tokens": [
{
"token": "token_ke_zkopírování",
"name": "název_zařízení",
"date": "datum"
}
```
Zkopírujte token ze souboru a vložte jej do dalšího okna.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Melden Sie sich als Root-Benutzer auf Ihrem Server an und sehen Sie sich den Inhalt der Datei `/etc/nixos/userdata/tokens.json` an
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
Diese Datei hat einen ähnlichen Aufbau:
```json
{
"tokens": [
{
"token": "token_zum_Kopieren",
"name": "Gerätname",
"date": "Datum"
}
```
Kopieren Sie das Token aus der Datei und fügen Sie es im nächsten Fenster ein.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Zaloguj się jako użytkownik root na swoim serwerze i przejrzyj zawartość pliku `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
W tym pliku będzie podobny wzór:
`json
{
{ "tokens": [
{
{ "token": "token_który_był_skopedowany",
{ "name": "device_name",
{ "data": "data"
}
```
Skopiuj token z pliku i wklej w kolejnym oknie.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Войдите как root пользователь на свой сервер и посмотрите содержимое файла `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
В этом файле будет схожая конструкция:
```json
{
"tokens": [
{
"token": "токен_который_надо_скопировать",
"name": "имя_устройства",
"date": "дата"
}
```
Скопируйте токен из файла и вставьте в следующем окне.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Prihláste sa ako používateľ root na server a pozrite sa na obsah súboru `/etc/nixos/userdata/tokens.json
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
V tomto súbore bude podobný dizajn:
``json
{
{"tokens": [
{
{"token": "token_which_has_been_scoped",
{"name": "device_name",
{"date": "date"
}
```
Skopírujte token zo súboru a vložte ho do ďalšieho okna.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Login as root user to your server and look at the contents of the file `/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
This file will have a similar construction:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Copy the token from the file and paste it in the next window.
Copy the token from the terminal and paste it in the next window.

View file

@ -1,19 +1,7 @@
Увійдіть від користувача root до вашого сервера і подивіться на вміст файла`/etc/nixos/userdata/tokens.json`
[Login as root user to your server](https://selfprivacy.org/docs/how-to-guides/root_ssh/) and enter this command:
```sh
cat /etc/nixos/userdata/tokens.json
sp-print-api-token
```
Цей файл буде мати подібну конструкцію:
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
}
```
Скопіюйте токен з файла і вставте його в наступне вікно.
Copy the token from the terminal and paste it in the next window.

View file

@ -11,7 +11,7 @@ mount /dev/sda1 /mnt
cat /mnt/etc/nixos/userdata/tokens.json
```
Гэты файл будзе мець падобную канструкцыю:
Гэты файл будзе мець падобны змест:
```json
{

View file

@ -1,26 +1,26 @@
In the Hetzner server control panel, go to the **Rescue** tab. Then, click on **Enable rescue & power cycle**.
Hetznerのサーバーコントロールパネルで **Rescue** タブを開いてください。その後 **Enable rescue & power cycle** をクリックしてください。
In *Choose a Recue OS* select **linux64**, and in *SSH Key* select your key if it has been added to your Hetzner account.
*Choose a Recue OS* で **linux64** を選択し、 *SSH Key* で、HetznerのアカウントにSSH鍵を登録している場合は、これを選択してください。
Click **Enable rescue & power cycle** and wait for the server to reboot. The login and password will be displayed on the screen. Login to the root user using your login and password information.
**Enable rescue & power cycle** をクリックして、サーバーが再起動するまでお待ちください。ログインとパスワードがスクリーンに表示されます。ログインとパスワード情報を使用して、ルートユーザーにログインしてください。
Mount your server file system and see the contents of the token file:
サーバーのファイルシステムをマウントして、トークンファイルの中身を確認してください。
```sh
mount /dev/sda1 /mnt
cat /mnt/etc/nixos/userdata/tokens.json
```
This file will have a similar construction:
このファイルは同様の構造になっています。
```json
{
"tokens": [
{
"token": "token_to_copy",
"name": "device_name",
"date": "date"
"token": "コピーするトークン",
"name": "端末名",
"date": "日付"
}
```
Copy the token from the file and paste it in the next window.
ファイルからトークンをコピーして、次のウィンドウ内に貼り付けてください。

View file

@ -23,4 +23,4 @@ W tym pliku będzie podobny wzór:
}
```
Skopiuj token z pliku i wklej w kolejnym oknie.
Skopiuj token z pliku i wklej w następnym oknie.

View file

@ -1,23 +1,12 @@
### How to get Hetzner API Token
1. Visit the following [link](https://console.hetzner.cloud/) and sign
into newly created account.
2. Enter into previously created project. If you haven't created one,
then please proceed.
3. Hover side panel with mouse cursor. Panel should expand and show us
a menu. We're interested in the last one — **Security** (icon of a
key).
4. Next, in the upper part of an interface, we can see approximately
the following: **SSH Keys, API Tokens, Certificates, Members.** You
need **API Tokens**. Click on it.
5. In the right part of the interface, there should be **Generate API
token** button. If you're using mobile version og a webpage, in the
lower right corner you'll see **red cross**. Push that button.
6. In the **Description** field, give our token a name (this can be any
name that you like. It doesn't influence the essence.
7. Under the **Description** field we can see a possibility to choose
**permissions**. Pick **Read & Write**.
8. Click **Generate API Token.**
9. After that, our key will be shown. Store it in the reliable place,
or in the password manager, which is better.
### HetznerのAPIトークンの取得方法
1. [このリンク](https://console.hetzner.cloud/)を開き、新しく作成したアカウントにサインイン。
2. 以前に作成したプロジェクトを開く。プロジェクトを作成していない場合は、作成してください。
3. マウスカーソルでサイドパネルをホバリング。パネルが開き、メニューが表示されるので、**Security** (鍵のアイコン)を選択。
4. インターフェースの上部に、 **SSH Keys, API Tokens, Certificates, Members** とあるので、 **API Tokens** をクリック。
5. インターフェースの右側に、 **Generate API token** ボタンがあります。モバイル版のウェブページでは、右下の角に **赤い十字** が表示されるので、環境に応じていずれかをクリック。
6. **Description** の欄に、任意のトークン名を記入。
7. **Description** の欄の下に、 **permissions** があるので、 **Read & Write** を選択。
8. **Generate API Token** をクリック。
9. トークンが表示されるので、パスワードマネージャーなどの安全な場所に保管してください。
![Hetzner token setup](resource:assets/images/gifs/Hetzner.gif)
![Hetznerのトークンの設定](resource:assets/images/gifs/Hetzner.gif)

View file

@ -173,7 +173,6 @@
"destroy_server": "هل تريد إنهاء هذا الخادم وإنشاء واحد جديد؟",
"try_again": "هل تريد المحاولة مرة أخرى؟",
"purge_all_keys": "هل تريد محو جميع مفاتيح الّتي مُنحت الموافقة؟",
"delete_server_volume": "هل تريد حذف الخادم ووحدة التخزين؟",
"reboot": "قم بإعادة التشغيل",
"yes": "نعم",
"no": "لا"
@ -304,7 +303,6 @@
},
"console_page": {
"title": "لوحة التحكم",
"waiting": "جاري الإنتظار للتهيئة…",
"copy": "انسخ"
},
"about_application_page": {
@ -333,14 +331,12 @@
},
"application_settings": {
"title": "إعدادات التطبيق",
"system_dark_theme_title": "الوضع الافتراضي للنظام",
"system_dark_theme_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
"system_theme_mode_title": "الوضع الافتراضي للنظام",
"system_theme_mode_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام",
"dark_theme_title": "الوضع الداكن",
"change_application_theme": "قم بتبديل وضع التطبيق",
"dangerous_settings": "إعدادات خطرة",
"reset_config_title": "قم بإعادة ضبط إعدادات التطبيق",
"delete_server_title": "قم بحذف الخادم",
"delete_server_description": "سيزيل هذا الخادم الخاص بك، حيث أنه لن تتمكن من الوصول إليه بعد ذلك.",
"dark_theme_description": "قم بتبديل وضع التطبيق",
"reset_config_description": "قم بإعادة ضبط مفاتيح API والمستخدم المميز."
},
"ssh": {

View file

@ -43,7 +43,6 @@
},
"console_page": {
"title": "Konsol",
"waiting": "Başlama gözlənilir…",
"copy": "Kopyalayın"
},
"about_application_page": {
@ -54,15 +53,13 @@
},
"application_settings": {
"title": "Tətbiq parametrləri",
"system_theme_mode_title": "Defolt sistem mövzusu",
"system_theme_mode_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin",
"dark_theme_title": "Qaranlıq mövzu",
"change_application_theme": "Rəng mövzusunu dəyişdirin",
"dangerous_settings": "Təhlükəli Parametrlər",
"reset_config_title": "Tətbiq Sıfırlayın",
"reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın.",
"delete_server_title": "Serveri silin",
"dark_theme_description": "Rəng mövzusunu dəyişdirin",
"delete_server_description": "Əməliyyat serveri siləcək. Bundan sonra o, əlçatmaz olacaq.",
"system_dark_theme_title": "Defolt sistem mövzusu",
"system_dark_theme_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin",
"dangerous_settings": "Təhlükəli Parametrlər"
"reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın."
},
"ssh": {
"title": "SSH açarları",
@ -381,7 +378,6 @@
"are_you_sure": "Sən əminsən?",
"purge_all_keys": "Bütün avtorizasiya açarları silinsin?",
"purge_all_keys_confirm": "Bəli, bütün düymələri silin",
"delete_server_volume": "Server və yaddaş silinsin?",
"reboot": "Yenidən yükləyin",
"yes": "Bəli",
"no": "Yox"

View file

@ -51,7 +51,7 @@
"connect_to_server_provider": "Аўтарызавацца ў ",
"connect_to_server_provider_text": "З дапамогай API токена праграма SelfPrivacy зможа ад вашага імя замовіць і наладзіць сервер",
"steps": {
"nixos_installation": "Ўстаноўка NixOS",
"nixos_installation": "Ўсталёўка NixOS",
"hosting": "Хостынг",
"server_type": "Тып сервера",
"dns_provider": "DNS правайдэр",
@ -59,7 +59,7 @@
"domain": "Дамен",
"master_account": "Майстар акаўнт",
"server": "Сервер",
"dns_setup": "Устаноўка DNS",
"dns_setup": "Усталёўка DNS",
"server_reboot": "Перазагрузка сервера",
"final_checks": "Фінальныя праверкі"
},
@ -100,7 +100,7 @@
"modal_confirmation_dns_invalid": "Зваротны DNS паказвае на іншы дамен",
"modal_confirmation_ip_invalid": "IP не супадае з паказаным у DNS запісу",
"fallback_select_provider_console": "Доступ да кансолі хостынгу.",
"provider_connected_description": "Сувязь устаноўлена. Увядзіце свой токен з доступам да {}:",
"provider_connected_description": "Сувязь наладжана. Увядзіце свой токен з доступам да {}:",
"choose_server": "Выберыце сервер",
"no_servers": "На вашым акаўнце няма даступных сэрвэраў.",
"modal_confirmation_description": "Падлучэнне да няправільнага сервера можа прывесці да дэструктыўных наступстваў.",
@ -114,7 +114,7 @@
"authorize_new_device": "Аўтарызаваць новую прыладу",
"access_granted_on": "Доступ выдадзены {}",
"tip": "Націсніце на прыладу, каб адклікаць доступ.",
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыкладанне SelfPrivacy."
"description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыладу SelfPrivacy."
},
"add_new_device_screen": {
"description": "Увядзіце гэты ключ на новай прыладзе:",
@ -127,7 +127,7 @@
"revoke_device_alert": {
"header": "Адклікаць доступ?",
"yes": "Адклікаць",
"no": "Адменіць",
"no": "Адхіліць",
"description": "Прылада {} больш не зможа кіраваць серверам."
}
},
@ -143,7 +143,7 @@
"later": "Прапусціць і наладзіць потым",
"no_data": "Няма дадзеных",
"services": "Сэрвісы",
"users": "Ужыткоўнікі",
"users": "Карыстальнікі",
"more": "Дадаткова",
"got_it": "Зразумеў",
"settings": "Налады",
@ -157,7 +157,7 @@
"saving": "Захаванне…",
"username": "Імя ўжыткоўніка",
"loading": "Загрузка…",
"connect_to_existing": "Падключыцца да існуючага сервера",
"connect_to_existing": "У мяне ўжо ёсць сервер SelfPrivacy!",
"reset": "Скінуць",
"details": "Падрабязная інфармацыя",
"wait": "Пачакайце",
@ -166,7 +166,9 @@
"done": "Гатова",
"continue": "Працягнуць",
"alert": "Паведамленне",
"app_name": "SelfPrivacy"
"app_name": "SelfPrivacy",
"copied_to_clipboard": "Скапіявана да буфэра абмену!",
"network_error": "Памылка ў сетцы"
},
"recovery_key": {
"key_connection_error": "Не ўдалося злучыцца з серверам.",
@ -203,7 +205,6 @@
"dns_removal_error": "Немагчыма выдаліць запісы DNS.",
"server_deletion_error": "Немагчыма выдаліць сервер.",
"unexpected_error": "Непрадбачаная памылка з боку правайдэра.",
"delete_server_volume": "Выдаліць сервер і сховішча?",
"volume_creation_error": "Не ўдалося стварыць том."
},
"timer": {
@ -234,26 +235,44 @@
},
"more_page": {
"configuration_wizard": "Майстар наладкі",
"onboarding": "Прівітанне",
"onboarding": "Прывітанне",
"create_ssh_key": "SSH ключы адміністратара"
},
"about_application_page": {
"application_version_text": "Версія праграмы",
"title": "Аб праграме",
"title": "Інфармацыя & дапамога",
"api_version_text": "Версія API сервера",
"privacy_policy": "Палітыка прыватнасці"
"privacy_policy": "Палітыка прыватнасці",
"telegram_channel": "Канал у Telegram",
"get_support": "Атрымаць дапамогу",
"versions": "Версіі",
"open_source_licenses": "FLOSS ліцэнзіі",
"links": "Спасылкі",
"website": "Наш вэб-сайт",
"documentation": "Дакументацыя",
"matrix_channel": "Канал у Matrix",
"matrix_support_chat": "Чат дапамогі ў Matrix",
"telegram_support_chat": "Чат дапамогі ў Telegram",
"email_support": "Падтрымка праз e-mail",
"contribute": "Дапамагчы",
"source_code": "Зыходны код",
"bug_report": "Паведаміць аб памылцы",
"bug_report_subtitle": "З прычыны спама патрабуецца ручное пацвярджэнне карыстальніка. Звяжыцеся з намі ў чаце падтрымкі для актывацыі карыстальніка.",
"help_translate": "Дапамагчы з перакладам",
"matrix_contributors_chat": "Чат распрацоўшчыкаў у Matrix",
"telegram_contributors_chat": "Чат распрацоўшчыкаў у Telegram"
},
"application_settings": {
"reset_config_description": "Скінуць API ключы i суперкарыстальніка.",
"delete_server_description": "Дзеянне прывядзе да выдалення сервера. Пасля гэтага ён будзе недаступны.",
"title": "Налады праграмы",
"system_theme_mode_title": "Сістэмная тэма па-змаўчанні",
"system_theme_mode_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
"dark_theme_title": "Цёмная тэма",
"dark_theme_description": "Змяніць каляровую тэму",
"reset_config_title": "Скід налад",
"delete_server_title": "Выдаліць сервер",
"system_dark_theme_title": "Сістэмная тэма па-змаўчанні",
"system_dark_theme_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад",
"dangerous_settings": "Небяспечныя наладкі"
"change_application_theme": "Змяніць каляровую тэму",
"language": "Мова",
"click_to_change_locale": "Націсніце, каб адчыніць меню выбару мовы",
"dangerous_settings": "Небяспечныя налады",
"reset_config_title": "Скід налад"
},
"ssh": {
"root_subtitle": "Уладальнікі паказаных тут ключоў атрымліваюць поўны доступ да дадзеных і налад сервера. Дадавайце выключна свае ключы.",
@ -263,7 +282,8 @@
"delete_confirm_question": "Вы ўпэўненыя, што хочаце выдаліць наступны ключ?",
"no_key_name": "Безназоўны ключ",
"root_title": "Гэта ключы суперкарыстальніка",
"input_label": "Публічны ED25519 або RSA ключ"
"input_label": "Публічны ED25519, ECDSA або RSA ключ",
"ssh_disabled_warning": "SSH не ўключаны. Вы можаце ўключыць яго ў наладах сервера."
},
"onboarding": {
"page1_title": "Лічбавая незалежнасць, даступная ўсім нам",
@ -354,7 +374,6 @@
},
"console_page": {
"title": "Кансоль",
"waiting": "Чакаем ініцыялізацыі…",
"copy": "Капіяваць"
},
"resource_chart": {
@ -391,7 +410,11 @@
"few": "{} ядра",
"many": "{} ядраў",
"other": "{} ядраў"
}
},
"settings": "Налады сервера",
"enable_ssh": "Уключыць SSH",
"enable_ssh_hint": "Дазволіць доступ да сервера цераз SSH",
"allow_password_authentication": "Дазволіць уваход цераз SSH з дапамогай пароля"
},
"not_ready_card": {
"in_menu": "Сервер яшчэ не наладжаны. Калі ласка, завяршыце наладку з дапамогай майстра наладкі для далейшай працы."

View file

@ -49,20 +49,17 @@
},
"console_page": {
"title": "Konzole",
"waiting": "Čekání na inicializaci…",
"copy": "Kopírovat"
},
"application_settings": {
"title": "Nastavení aplikace",
"system_theme_mode_title": "Výchozí téma systému",
"system_theme_mode_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému",
"dark_theme_title": "Tmavé téma",
"change_application_theme": "Přepnutí tématu aplikace",
"dangerous_settings": "Nebezpečná nastavení",
"reset_config_title": "Obnovení konfigurace aplikace",
"reset_config_description": "Obnovení klíčů API a uživatele root.",
"delete_server_title": "Odstranit server",
"dark_theme_description": "Přepnutí tématu aplikace",
"delete_server_description": "Tím odstraníte svůj server. Nebude již přístupný.",
"system_dark_theme_title": "Výchozí téma systému",
"system_dark_theme_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému",
"dangerous_settings": "Nebezpečná nastavení"
"reset_config_description": "Obnovení klíčů API a uživatele root."
},
"ssh": {
"title": "Klíče SSH",
@ -429,7 +426,6 @@
"are_you_sure": "Jste si jistý?",
"purge_all_keys": "Vyčistit všechny ověřovací klíče?",
"purge_all_keys_confirm": "Ano, vyčistěte všechny mé žetony",
"delete_server_volume": "Smazat server a svazek?",
"reboot": "Restartovat",
"yes": "Ano",
"no": "Ne",

View file

@ -46,7 +46,6 @@
},
"console_page": {
"title": "Konsole",
"waiting": "Warten auf Initialisierung…",
"copy": "Kopieren"
},
"about_application_page": {
@ -57,15 +56,13 @@
},
"application_settings": {
"title": "Anwendungseinstellungen",
"system_theme_mode_title": "Standard-Systemthema",
"system_theme_mode_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema",
"dark_theme_title": "Dunkles Thema",
"dark_theme_description": "Ihr Anwendungsdesign wechseln",
"change_application_theme": "Ihr Anwendungsdesign wechseln",
"dangerous_settings": "Gefährliche Einstellungen",
"reset_config_title": "Anwendungseinstellungen zurücksetzen",
"reset_config_description": "API Sclüssel und root Benutzer zurücksetzen.",
"delete_server_title": "Server löschen",
"delete_server_description": "Das wird Ihren Server löschen. Es wird nicht mehr zugänglich sein.",
"system_dark_theme_title": "Standard-Systemthema",
"system_dark_theme_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema",
"dangerous_settings": "Gefährliche Einstellungen"
"reset_config_description": "API Sclüssel und root Benutzer zurücksetzen."
},
"ssh": {
"title": "SSH Schlüssel",
@ -247,7 +244,6 @@
},
"modals": {
"unexpected_error": "Unerwarteter Fehler beim Platzieren von Seiten des Anbieters.",
"delete_server_volume": "Server und Speicher löschen?",
"dns_removal_error": "DNS-Einträge konnten nicht entfernt werden.",
"server_deletion_error": "Aktiver Server konnte nicht gelöscht werden.",
"server_validators_error": "Verfügbare Server konnten nicht abgerufen werden.",

View file

@ -46,8 +46,28 @@
},
"console_page": {
"title": "Console",
"waiting": "Waiting for initialization…",
"copy": "Copy"
"copy": "Copy",
"copy_raw": "Raw response",
"history_empty": "No data yet",
"error": "Error",
"rest_api_request": "REST API Request",
"rest_api_response": "REST API Response",
"graphql_request": "GraphQL Request",
"graphql_response": "GraphQL Response",
"logged_at": "Logged at",
"data": "Data",
"errors": "Errors",
"error_path": "Path",
"error_locations": "Locations",
"error_extensions": "Extensions",
"request_data": "Request data",
"headers": "Headers",
"response_data": "Response data",
"context": "Context",
"operation": "Operation",
"operation_type": "Operation type",
"operation_name": "Operation name",
"variables": "Variables"
},
"about_application_page": {
"title": "About & support",
@ -75,10 +95,12 @@
},
"application_settings": {
"title": "Application settings",
"system_dark_theme_title": "System default theme",
"system_dark_theme_description": "Use light or dark theme depending on system settings",
"system_theme_mode_title": "System default theme",
"system_theme_mode_description": "Use light or dark theme depending on system settings",
"dark_theme_title": "Dark theme",
"dark_theme_description": "Switch your application theme",
"change_application_theme": "Switch your application theme",
"language": "Language",
"click_to_change_locale": "Click to open language list",
"dangerous_settings": "Dangerous settings",
"reset_config_title": "Reset application config",
"reset_config_description": "Resets API keys and root user."
@ -565,6 +587,8 @@
"upgrade_success": "Server upgrade started",
"upgrade_failed": "Failed to upgrade server",
"upgrade_server": "Upgrade server",
"collect_nix_garbage": "Collect system garbage",
"collect_nix_garbage_failed": "Failed to collect system garbage",
"reboot_server": "Reboot server",
"create_ssh_key": "Create SSH key for {}",
"delete_ssh_key": "Delete SSH key for {}",
@ -606,5 +630,16 @@
"reset_onboarding": "Reset onboarding switch",
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
"cubit_statuses": "Cubit loading statuses"
},
"countries": {
"germany": "Germany",
"netherlands": "Netherlands",
"singapore": "Singapore",
"united_kingdom": "United Kingdom",
"canada": "Canada",
"india": "India",
"australia": "Australia",
"united_states": "United States",
"finland": "Finland"
}
}

View file

@ -0,0 +1 @@
{}

View file

@ -39,16 +39,14 @@
"test": "es-test",
"locale": "es",
"application_settings": {
"reset_config_title": "Restablecer la configuración de la aplicación",
"dark_theme_description": "Cambia el tema de tu aplicación",
"reset_config_description": "Restablecer claves API y usuario root.",
"delete_server_title": "Eliminar servidor",
"delete_server_description": "Esto elimina su servidor. Ya no será accesible.",
"title": "Ajustes de la aplicación",
"system_theme_mode_title": "Tema del sistema",
"system_theme_mode_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
"dark_theme_title": "Tema oscuro",
"system_dark_theme_title": "Tema del sistema",
"system_dark_theme_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
"dangerous_settings": "Configuraciones peligrosas"
"change_application_theme": "Cambia el tema de tu aplicación",
"dangerous_settings": "Configuraciones peligrosas",
"reset_config_title": "Restablecer la configuración de la aplicación",
"reset_config_description": "Restablecer claves API y usuario root."
},
"ssh": {
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
@ -84,7 +82,6 @@
},
"console_page": {
"title": "Consola",
"waiting": "Esperando la inicialización…",
"copy": "Copiar"
},
"server": {

View file

@ -1,15 +1,15 @@
{
"application_settings": {
"system_dark_theme_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest",
"delete_server_description": "See eemaldab teie serveri. Seda ei saa enam juurde pääseda.",
"title": "Rakenduse seaded",
"system_dark_theme_title": "Süsteemi vaiketeema",
"system_theme_mode_title": "Süsteemi vaiketeema",
"system_theme_mode_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest",
"dark_theme_title": "Tume teema",
"dark_theme_description": "Vaheta oma rakenduse teemat",
"change_application_theme": "Vaheta oma rakenduse teemat",
"dangerous_settings": "Ohtlikud seaded",
"reset_config_title": "Lähtesta rakenduse konfiguratsioon",
"reset_config_description": "Lähtestab API võtmed ja juurkasutaja.",
"delete_server_title": "Kustuta server"
"language": "Keel",
"click_to_change_locale": "Klõpsake keelte loendi avamiseks"
},
"server": {
"reboot_after_upgrade": "Taaskäivita pärast värskendust",
@ -116,8 +116,28 @@
},
"console_page": {
"title": "Konsool",
"waiting": "Ootab initsialiseerimist…",
"copy": "Kopeeri"
"copy": "Kopeeri",
"copy_raw": "Toores vastus",
"history_empty": "Andmeid veel pole",
"error": "Viga",
"rest_api_request": "REST API Taotlus",
"rest_api_response": "REST API Vastus",
"graphql_request": "GraphQL Taotlus",
"graphql_response": "GraphQL Vastus",
"logged_at": "Juhtus sisse",
"data": "Andmeid",
"errors": "Vead",
"error_path": "Faili tee",
"response_data": "Vastuse andmed",
"context": "Kontekst",
"operation": "Operatsioon",
"operation_type": "Operatsiooni tüüp",
"operation_name": "Operatsiooni nimi",
"variables": "Muutujad",
"error_locations": "Vigade asukohad",
"error_extensions": "Laiendused",
"request_data": "Päringuandmed",
"headers": "Päised"
},
"about_application_page": {
"title": "Teave ja klienditugi",
@ -566,7 +586,9 @@
"start_server_upgrade": "Serveri värskenduse käivitamine",
"change_auto_upgrade_settings": "Automaatse värskenduse seadete muutmine",
"change_server_timezone": "Serveri ajavööndi muutmine",
"change_ssh_settings": "Muuda SSH seadeid"
"change_ssh_settings": "Muuda SSH seadeid",
"collect_nix_garbage": "Koguge süsteemi prügi",
"collect_nix_garbage_failed": "Süsteemi prügi kogumine ebaõnnestus"
},
"validations": {
"required": "Nõutud",
@ -602,12 +624,22 @@
"are_you_sure": "Kas olete kindel?",
"purge_all_keys": "Kustutada kõik autentimisvõtmed?",
"purge_all_keys_confirm": "Jah, kustuta kõik minu võtmed",
"delete_server_volume": "Kustutada server ja maht?",
"reboot": "Taaskäivitage",
"yes": "Jah",
"no": "Ei"
},
"timer": {
"sec": "{} sek"
},
"countries": {
"germany": "Saksamaa",
"netherlands": "Madalmaad",
"singapore": "Singapur",
"united_kingdom": "Ühendkuningriik",
"canada": "Kanada",
"india": "India",
"australia": "Austraalia",
"united_states": "Ameerika Ühendriigid",
"finland": "Soome"
}
}

View file

@ -45,7 +45,6 @@
},
"console_page": {
"title": "Console",
"waiting": "En attente de l'initialisation…",
"copy": "Copier"
},
"about_application_page": {
@ -56,15 +55,13 @@
},
"application_settings": {
"title": "Paramètres de l'application",
"dark_theme_description": "Changer le thème de l'application",
"reset_config_title": "Réinitialiser la configuration de l'application",
"delete_server_title": "Supprimer le serveur",
"delete_server_description": "Cela va supprimer votre serveur. Celui-ci ne sera plus accessible.",
"system_theme_mode_title": "Thème par défaut du système",
"system_theme_mode_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
"dark_theme_title": "Thème sombre",
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root.",
"system_dark_theme_title": "Thème par défaut du système",
"system_dark_theme_description": "Affichage de jour ou de nuit en fonction du paramétrage système",
"dangerous_settings": "Paramètres dangereux"
"change_application_theme": "Changer le thème de l'application",
"dangerous_settings": "Paramètres dangereux",
"reset_config_title": "Réinitialiser la configuration de l'application",
"reset_config_description": "Réinitialiser les clés API et l'utilisateur root."
},
"ssh": {
"title": "Clés SSH",
@ -227,7 +224,6 @@
"no": "Non",
"yes": "Oui",
"reboot": "Redémarrer",
"delete_server_volume": "Supprimer le serveur et le volume?",
"purge_all_keys_confirm": "Oui, purger tous mes tokens",
"purge_all_keys": "Purger toutes les clés d'authentification?",
"are_you_sure": "Êtes-vous sûr?",

View file

@ -76,20 +76,17 @@
},
"console_page": {
"title": "מסוף",
"waiting": "בהמתנה לאתחול…",
"copy": "העתקה"
},
"application_settings": {
"title": "הגדרות יישום",
"system_dark_theme_title": "ערכת העיצוב כברירת המחדל של המערכת",
"system_dark_theme_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
"system_theme_mode_title": "ערכת העיצוב כברירת המחדל של המערכת",
"system_theme_mode_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך",
"dark_theme_title": "ערכת עיצוב כהה",
"dark_theme_description": "החלפת ערכת העיצוב של המערכת שלך",
"change_application_theme": "החלפת ערכת העיצוב של המערכת שלך",
"dangerous_settings": "הגדרות מסוכנות",
"reset_config_title": "איפוס הגדרות היישומון",
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל.",
"delete_server_title": "מחיקת שרת",
"delete_server_description": "מסיר את השרת שלך. הוא לא יהיה זמין עוד."
"reset_config_description": "איפוס מפתחות ה־API ומשתמש העל."
},
"backup": {
"create_new_select_heading": "לבחור מה לגבות",
@ -507,7 +504,6 @@
"are_you_sure": "להמשיך?",
"destroy_server": "להשמיד את השרת וליצור אחד חדש?",
"purge_all_keys_confirm": "כן, למחוק לחלוטין את כל האסימונים שלי",
"delete_server_volume": "למחוק את השרת והכרך?",
"reboot": "להפעיל מחדש",
"yes": "כן",
"dns_removal_error": "לא ניתן להסיר רשומת DNS.",

View file

@ -2,6 +2,605 @@
"test": "jp-test",
"locale": "jp",
"basis": {
"app_name": "SelfPrivacy"
"app_name": "SelfPrivacy",
"providers_title": "あなたのデータセンター",
"select": "選択",
"services": "サービス",
"services_title": "あなたのプライベートで、独立した、個人用のサービスです。",
"users": "ユーザー",
"more": "詳細",
"next": "次へ",
"got_it": "確認しました",
"settings": "設定",
"password": "パスワード",
"create": "追加",
"confirmation": "承認",
"cancel": "キャンセル",
"delete": "削除",
"close": "閉じる",
"connect": "接続",
"domain": "ドメイン",
"saving": "保存しています…",
"username": "ユーザー名",
"loading": "読み込んでいます…",
"later": "後で設定",
"connect_to_existing": "既にSelfPrivacyのサーバーを持っています",
"details": "詳細",
"no_data": "データがありません",
"wait": "お待ちください",
"remove": "削除",
"apply": "適用",
"done": "完了",
"continue": "続行",
"alert": "注意",
"reset": "リセット",
"providers": "サービス提供者",
"copied_to_clipboard": "クリップボードにコピーしました!",
"please_connect": "開始するにはサーバー、ドメイン、DNSのサービス提供者を接続してください",
"network_error": "ネットワークエラー"
},
"application_settings": {
"reset_config_description": "APIとルートユーザーをリセット。",
"title": "アプリケーションの設定",
"dark_theme_title": "ダークテーマ",
"dangerous_settings": "要注意の設定",
"reset_config_title": "アプリケーションの設定をリセット"
},
"console_page": {
"copy": "コピー",
"title": "コンソール"
},
"about_application_page": {
"title": "概要とサポート",
"api_version_text": "サーバーのAPIのバージョン",
"open_source_licenses": "オープンソースライセンス",
"versions": "バージョン",
"links": "リンク",
"website": "私たちのウェブサイト",
"documentation": "ドキュメンテーション",
"matrix_channel": "Matrixのチャンネル",
"telegram_channel": "Telegramのチャンネル",
"get_support": "サポート",
"matrix_support_chat": "Matrixのサポートチャット",
"telegram_support_chat": "Telegramのサポートチャット",
"email_support": "電子メールでのサポート",
"contribute": "貢献",
"source_code": "ソースコード",
"bug_report": "不具合を報告",
"bug_report_subtitle": "スパム攻撃のため、手動によるアカウント認証が必要です。サポートチャットで私たちに連絡してもらえれば、アカウントを有効化します。",
"help_translate": "翻訳を手伝う",
"matrix_contributors_chat": "Matrixでの貢献者用チャット",
"telegram_contributors_chat": "Telegramでの貢献者用チャット",
"privacy_policy": "プライバシーポリシー",
"application_version_text": "アプリケーションのバージョン"
},
"recovering": {
"fallback_select_provider_console": "サービス提供者のサービスコンソールへのアクセス。",
"generic_error": "操作に失敗しました。再度試してください。",
"domain_recovery_description": "アクセスしたいサーバーのドメインを入力してください。",
"method_select_description": "復元方法を選択してください。",
"method_select_other_device": "別の端末からアクセス",
"choose_server": "あなたのサーバーを選択",
"no_servers": "あなたのアカウントに利用可能なサーバーはありません。",
"domain_recover_error": "ドメイン名に対応するサーバーが見つかりませんでした",
"domain_recover_placeholder": "あなたのドメイン",
"choose_server_description": "どのサーバーに接続しようとしているか検知できませんでした。",
"recovery_main_header": "既存のサーバーに接続",
"fallback_select_provider_console_hint": "例Hetzner",
"provider_connected": "{}に接続",
"confirm_server": "サーバーを承認",
"method_select_recovery_key": "復元鍵を使用",
"confirm_server_accept": "はい、正しいです",
"confirm_server_decline": "別のサーバーを選択",
"modal_confirmation_title": "サーバーを確認してください",
"modal_confirmation_description": "正しくないサーバーに接続すると、全てのデータを失う可能性があります。",
"confirm_server_description": "サーバーが見つかりました!正しいサーバーであることを確認してください。",
"modal_confirmation_ip_valid": "IPアドレスはDNSレコードにあるものと同じです",
"method_device_button": "トークンを入手しました",
"domain_not_available_on_token": "選択したドメインはこのトークンでは利用できません。",
"modal_confirmation_ip_invalid": "IPアドレスはDNSレコードにあるものと同じではありません",
"method_device_description": "別の端末でアプリケーションを実行し、端末のページを開いてください。「端末を追加」を押して、トークンを入手してください。",
"method_device_input_description": "認証トークンを入力してください",
"provider_connected_description": "{}へのアクセス権限があるトークンを入力してください。",
"method_select_nothing": "どちらもありません",
"method_device_input_placeholder": "トークン",
"authorization_failed": "この鍵ではログインできませんでした",
"fallback_select_root_ssh": "ルート権限でのSSHによるサーバーへのアクセス。",
"fallback_select_token_copy": "他のバージョンのアプリケーションから認証トークンをコピー。",
"method_recovery_input_description": "復元鍵を入力してください",
"fallback_select_description": "具体的に何を持っていますか?最初に利用できるものを選択してください。",
"modal_confirmation_dns_invalid": "逆引きDNSが他のドメインを指しています",
"modal_confirmation_dns_valid": "逆引きDNSは有効です",
"provider_connected_placeholder": "{}のトークン"
},
"ssh": {
"root_subtitle": "これらの鍵の所有者は、サーバーへの完全なアクセスを行い、全く自由にサーバーを操作することができます。あなた自身の鍵だけをサーバーに追加してください。",
"ssh_disabled_warning": "SSHは無効になっています。サーバーの設定から有効にできます。",
"title": "SSH鍵",
"create": "SSH鍵を作成",
"delete": "SSH鍵を削除",
"delete_confirm_question": "SSH鍵を削除してよろしいですか",
"no_key_name": "無名の鍵",
"root_title": "これらはスーパーユーザーの鍵です",
"input_label": "Public ED25519、ECDSA、またはRSA鍵"
},
"onboarding": {
"page1_text": "あなた専用のサーバーで、電子メール、VPN、メッセンジャー、SNSなどを、あなた自身がコントロールすることができます。",
"page2_backup_provider_title": "バックアップのサービス提供者",
"page2_dns_provider_title": "DNSのサービス提供者",
"page2_title": "SelfPrivacyはクラウドサービスではなく、あなたの個人用データセンターです",
"page2_text": "SelfPrivacyは、あなたが選択するサービス提供者でのみ機能します。必要なアカウントを持っていない場合、私たちはアカウントの作成をお手伝いします。",
"page2_server_provider_title": "サーバーのプロバイダー",
"page1_title": "デジタル環境の自主独立を、私たち皆の手に",
"page2_server_provider_text": "サーバーのサービス提供者は、自ら所有するデータセンターで、あなたのサーバーを管理します。SelfPrivacyは、自動的にサービス提供者に接続して、必要な事項を設定します。",
"page2_backup_provider_text": "サーバーに何かあった場合を考えてみましょう。ハッカーによる攻撃、サービス拒否攻撃が行われた場合、または、不慮の事故によってデータを削除してしまった場合を想像してみましょう。あなたのデータは、バックアップのサービス提供者に安全に保存されます。バックアップは安全に暗号化され、それを使えばいつでもサーバーを復元することができます。",
"page2_dns_provider_text": "インターネット上で場所をもつためにはドメインが必要です。また、ドメインをサーバーに向けるために、信頼できるDNSのサービス提供者も必要となります。サポートするDNSのサービス提供者を選択して、ネットワークを自動的に設定することをお勧めします。"
},
"resource_chart": {
"month": "月",
"network_title": "ネットワークの使用状況",
"day": "日",
"hour": "時間",
"cpu_title": "CPUの使用状況",
"in": "受信済",
"out": "送信済"
},
"domain": {
"email_subtitle": "安全な電子メールの送受信に必要なレコードです。",
"email_title": "電子メール",
"update_list": "一覧を更新",
"card_title": "ドメイン",
"screen_title": "ドメインとDNS",
"services_title": "サービス",
"services_subtitle": "「A」タイプのレコードはそれぞれのサービスに必要となります。",
"ok": "レコードに問題はありません",
"refreshing": "状況を更新しています…",
"error": "問題が見つかりました",
"error_subtitle": "修正するには、ここをタップしてください。タップすると、カスタムのレコードも削除されます。",
"uninitialized": "データはまだ取得されていません"
},
"backup": {
"backups_encryption_key_show": "暗号化鍵を表示",
"backups_encryption_key": "暗号化鍵",
"backups_encryption_key_subtitle": "安全な場所に保管してください。",
"autobackup_set_period": "期間を設定",
"snapshots_title": "スナップショットの一覧",
"pending_jobs": "現在実行中のバックアップのジョブ",
"reupload_key": "鍵を強制的に再アップロード",
"card_subtitle": "バックアップを管理",
"initialize": "初期化",
"start": "バックアップを開始",
"latest_snapshots": "最近のスナップショット",
"create_new_select_heading": "バックアップするものを選択してください",
"service_busy": "別のバックアップが実行中です",
"latest_snapshots_subtitle": "最近の15個のスナップショットを表示しています",
"show_more": "さらに表示",
"card_title": "バックアップ",
"autobackup_period_every": "毎{period}",
"autobackup_period_disable": "自動バックアップを無効にする",
"snapshot_modal_inplace_option_title": "ダウンロードしつつ置換",
"snapshot_modal_download_verify_option_description": "比較的リスクは少ないですが、より多くの空き容量が必要となります。スナップショット全体を一時保管領域にダウンロードし、データを検証した後に、現在のデータと置き換えます。",
"quota_subtitles": {
"last": {
"many": "最新の{}個のバックアップを作成日時によらず保存",
"other": "最新の{}個のバックアップを作成日時によらず保存",
"two": "最新の{}個のバックアップを作成日時によらず保存",
"few": "最新の{}個のバックアップを作成日時によらず保存",
"one": "最新の{}個のバックアップを作成日時によらず保存",
"zero": "ルールは無効です"
},
"last_infinite": "全てのバックアップを保存",
"daily": {
"zero": "ルールは無効です",
"one": "直近{}個の毎日のバックアップを保存します",
"two": "直近{}個の毎日のバックアップを保存します",
"other": "直近{}個の毎日のバックアップを保存します",
"few": "直近{}個の毎日のバックアップを保存します",
"many": "直近{}個の毎日のバックアップを保存します"
},
"weekly": {
"zero": "ルールは無効です",
"one": "直近{}個の毎週のバックアップを保存します",
"other": "直近{}個の毎週のバックアップを保存します",
"few": "直近{}個の毎週のバックアップを保存します",
"two": "直近{}個の毎週のバックアップを保存します",
"many": "直近{}個の毎週のバックアップを保存します"
},
"monthly": {
"zero": "ルールは無効です",
"one": "直近{}個の毎月のバックアップを保存します",
"two": "直近{}個の毎月のバックアップを保存します",
"few": "直近{}個の毎月のバックアップを保存します",
"many": "直近{}個の毎月のバックアップを保存します",
"other": "直近{}個の毎月のバックアップを保存します"
},
"yearly": {
"zero": "ルールは無効です",
"two": "直近{}個の毎年のバックアップを保存します",
"few": "直近{}個の毎年のバックアップを保存します",
"many": "直近{}個の毎年のバックアップを保存します",
"other": "直近{}個の毎年のバックアップを保存します",
"one": "直近{}個の毎年のバックアップを保存します"
},
"weekly_infinite": "毎週のバックアップを全て保存します",
"no_effect": "他のルールにより多くのバックアップがあるため、このルールは無効です",
"daily_infinite": "毎日のバックアップを全て保存します",
"monthly_infinite": "毎月のバックアップを全て保存します",
"yearly_infinite": "毎年のバックアップを全て保存します"
},
"autobackup_period_title": "自動バックアップの期間",
"backups_encryption_key_copy": "暗号化鍵をコピー",
"restore": "バックアップから復元",
"no_backups": "バックアップがありません",
"create_new": "新規バックアップを作成",
"select_all": "全てをバックアップ",
"snapshot_reasons": {
"auto": "自動的に作成",
"explicit": "明示的なリクエストにより作成",
"pre_restore": "危険性のある復元の前に予備として作成",
"unknown": "不明"
},
"snapshot_reason_title": "作成理由",
"forget_snapshot": "スナップショットを削除",
"snapshot_id_title": "スナップショットのID",
"snapshot_modal_heading": "スナップショットの詳細",
"snapshot_service_title": "サービス",
"snapshot_creation_time_title": "作成日時",
"snapshot_modal_select_strategy": "復元方針を選択してください",
"snapshot_modal_download_verify_option_title": "ダウンロードし、検証後に置換",
"snapshot_modal_inplace_option_description": "必要な空き容量は比較的少ないですが、より多くのリスクがあります。ダウンロードを行いながら、現在のデータをスナップショットのデータと置き換えます。",
"forget_snapshot_alert": "このスナップショットを削除しようとしています。よろしいですか?これは取り消せません。",
"forget_snapshot_error": "スナップショットを削除できませんでした",
"backups_encryption_key_description": "この鍵はバックアップの暗号化に用いられます。紛失した場合は、バックアップを復元することができなくなります。手動でバックアップを復元する際に必要となるので、安全な場所に保管してください。",
"reuploaded_key": "鍵を再度アップロードしました",
"restore_alert": "{}に作成したバックアップから復元しようとしています。現在のデータは全て失われます。よろしいですか?",
"reupload_key_subtitle": "バックアップの保存領域の初期化を行うようサーバーに支持します。何か問題が発生した場合に使用してください。",
"autobackup_period_never": "自動バックアップは無効になっています",
"autobackup_period_subtitle": "毎{period}に作成されたバックアップ",
"description": "ハッカーによる攻撃やサーバーの削除といった事故があった場合に、サービスを復元することができます。",
"refetch_backups": "バックアップの一覧を再取得",
"refresh": "状況を更新",
"restore_started": "復元を開始しました。現在の状況に関してはジョブ一覧を確認してください",
"snapshot_modal_service_not_found": "これはあなたのサーバーで既に運営されていないサービスのスナップショットです。これは通常は起こらず、自動復元を行うことはできません。スナップショットをダウンロードして、手動で復元することもできます。手助けが必要な場合は、SelfPrivacyのサポートに連絡してください。",
"quotas_only_applied_to_autobackups": "この設定は自動バックアップにのみ適用されます。手動バックアップは削除されません。",
"refetching_list": "数分で一覧が更新されます",
"refetch_backups_subtitle": "キャッシュを破棄して、保管領域のサービス提供者からデータを再度取得します。追加料金がかかる可能性があります。",
"quota_titles": {
"last": "保存する直近のバックアップの個数",
"weekly": "保存する毎週のバックアップの数",
"monthly": "保存する毎月のバックアップの数",
"yearly": "保存する毎年のバックアップの数",
"daily": "保存する毎日のバックアップの数"
},
"rotation_quotas_title": "スナップショットのローテーションの設定",
"set_rotation_quotas": "新しいローテーションの割り当てを設定",
"backups_encryption_key_not_found": "暗号化の鍵がまだ見つかりません。後ほど改めて試してください。"
},
"service_page": {
"open_in_browser": "ブラウザーで開く",
"enable": "サービスを有効にする",
"snapshots": "バックアップのスナップショット",
"status": {
"off": "無効",
"reloading": "再起動中",
"activating": "有効に設定中",
"deactivating": "無効に設定中",
"inactive": "停止済",
"failed": "開始に失敗",
"active": "実行中"
},
"disable": "サービスを無効にする",
"restart": "サービスを再起動",
"nothing_here": "何もありません",
"uses": "{volume}で{usage}使用",
"move": "別のボリュームに移動"
},
"users": {
"could_not_fetch_users": "ユーザーの一覧を取得できませんでした",
"nobody_here": "ここにユーザーが表示されます",
"reset_password": "パスワードを再設定",
"refresh_users": "ユーザー一覧を再読み込み",
"could_not_fetch_description": "インターネット接続を確認して、再度試してください",
"could_not_create_user": "ユーザーを作成できませんでした",
"could_not_delete_user": "ユーザーを削除できませんでした",
"could_not_add_ssh_key": "SSH鍵を追加できませんでした",
"email_login": "電子メールによるログイン",
"user_already_exists": "同じユーザー名のユーザーが既に存在しています",
"delete_confirm_question": "よろしいですか?",
"new_user": "新規ユーザー",
"details_title": "ユーザーの詳細",
"add_new_user": "最初のユーザーを追加",
"delete_user": "ユーザーを削除",
"login": "ログイン",
"new_user_info_note": "新しいユーザーには、全てのサービスへのアクセス権限が与えられます",
"account": "アカウント",
"username_rule": "ユーザー名には小文字のラテンアルファベット、数字、アンダーバーのみを使用できます。数字を先頭に置くことは避けるべきです",
"no_ssh_notice": "このユーザーには電子メールとSSHのアカウントのみが作成されます。全てのサービスのシングルサインオンは近日中に実装されます。"
},
"devices": {
"add_new_device_screen": {
"tip": "鍵は10分間有効です。",
"expired": "鍵の有効期限が切れました。",
"get_new_key": "新しい鍵を入手",
"header": "新しい端末の認証",
"description": "認証したい端末に鍵を入力してください。",
"please_wait": "お待ちください"
},
"revoke_device_alert": {
"no": "キャンセル",
"header": "アクセス権を取り消しますか?",
"description": "端末 {} はサーバーにアクセスできなくなります。",
"yes": "取り消す"
},
"main_screen": {
"header": "端末",
"this_device": "この端末",
"other_devices": "他の端末",
"authorize_new_device": "新しい端末を認証",
"tip": "端末を押すとアクセス権を取り消します。",
"access_granted_on": "{}にアクセス権が付与されました",
"description": "これらの端末は、SelfPrivacyのアプリを使用してサーバーに完全にアクセスすることができます。"
}
},
"recovery_key": {
"key_main_description": "他の全ての認証済の端末が利用できない際、SelfPrivacyの認証に必要になります。",
"key_amount_field_title": "最大使用回数",
"key_duedate_toggle": "利用できる時間を制限",
"key_duedate_field_title": "期限切れとなる日時",
"key_valid": "鍵は有効です",
"key_invalid": "鍵は無効となりました",
"key_valid_until": "{}まで有効",
"key_replace_button": "新しい鍵を作成",
"key_receiving_description": "この鍵をメモして、安全な場所に保管してください。サーバーへの完全なアクセスを復元する際に使用されます。",
"generation_error": "復元鍵を作成できませんでした。{}",
"key_connection_error": "サーバーに接続できませんでした。",
"key_synchronizing": "同期しています…",
"key_main_header": "復元鍵",
"key_amount_toggle": "使用回数を制限",
"key_valid_for": "{}回有効",
"key_creation_date": "{}に作成済",
"key_receiving_done": "完了しました!",
"key_receive_button": "鍵を入手",
"key_receiving_info": "この鍵は二度と表示されませんが、別の鍵と置き換えることはできます。"
},
"jobs": {
"server_jobs": "サーバー上のジョブ",
"delete_ssh_key": "{}用のSSH鍵を削除",
"create_ssh_key": "{}用のSSH鍵を作成",
"change_server_timezone": "サーバーのタイムゾーンを変更",
"change_auto_upgrade_settings": "自動アップグレードの設定を変更",
"job_removed": "ジョブを削除しました",
"start": "開始",
"create_user": "ユーザーを作成",
"empty": "ジョブはありません",
"job_postponed": "ジョブを追加しました。現在のジョブの完了後に開始することができます",
"reboot_success": "サーバーを再起動しています",
"run_jobs": "ジョブを実行",
"reset_user_password": "ユーザーのパスワードをリセット",
"reboot_server": "サーバーを再起動",
"upgrade_server": "サーバーをアップグレード",
"generic_error": "サーバーに接続できませんでした!",
"rebuild_system": "システムの再構成",
"change_ssh_settings": "SSHの設定を変更",
"start_server_upgrade": "サーバーのアップグレードを開始",
"job_added": "ジョブを追加しました",
"upgrade_success": "サーバーのアップグレードを開始しました",
"upgrade_failed": "サーバーをアップグレードできませんでした",
"title": "ジョブの一覧",
"delete_user": "ユーザーを削除",
"reboot_failed": "サーバーを再起動できませんでした。アプリのログを確認してください。",
"service_turn_on": "有効にする",
"config_pull_failed": "設定のアップグレードを行えませんでした。ソフトウェアのアップグレードを開始しました。",
"service_turn_off": "無効にする"
},
"validations": {
"required": "必須",
"invalid_format": "不正なフォーマットです",
"invalid_format_password": "パスワードにスペースを含めることはできません",
"already_exist": "既に存在しています",
"invalid_format_ssh": "SSH鍵の形式に従う必要があります",
"root_name": "「root」は指定できません",
"length_not_equal": "長さは[]ですが、{}のはずです",
"length_longer": "長さは[]ですが、{}と等しいか、それよりも短いはずです"
},
"server": {
"card_title": "サーバー",
"general_information": "全般的な情報",
"settings": "サーバーの設定",
"core_count": {
"two": "{}個のコア",
"one": "{}個のコア",
"few": "{}個のコア",
"many": "{}個のコア",
"other": "{}個のコア"
},
"server_provider": "サーバーのサービス提供者",
"dns_provider": "DNSのサービス提供者",
"allow_autoupgrade_hint": "サーバーでパッケージの自動アップグレードを許可",
"enable_ssh_hint": "サーバーへのSSHアクセスを許可",
"allow_password_authentication": "SSHのパスワード認証を許可",
"server_timezone": "サーバーのタイムゾーン",
"enable_ssh": "SSHを許可",
"reboot_after_upgrade": "アップグレード後に再起動",
"allow_autoupgrade": "自動アップグレードを許可",
"reboot_after_upgrade_hint": "サーバーで変更を適用後、自動的に再起動",
"monthly_cost": "月コスト",
"location": "所在地",
"pricing_error": "サーバーのサービス提供者の価格を取得できませんでした",
"allow_password_authentication_hint": "ユーザーにパスワードでサーバーのシェルにログインすることを許可(ルートユーザーには適用されません)",
"cpu": "CPU",
"ram": "メモリー",
"resource_usage": "リソースの使用状況",
"server_id": "サーバーのID",
"status": "状況",
"select_timezone": "タイムゾーンを選択",
"timezone_search_bar": "タイムゾーンの名称または時差",
"description": "このコンピューター上で全てのサービスが実行されます",
"disk": "ローカルディスク"
},
"initializing": {
"select_provider_price_free": "無料",
"connect_to_server": "サーバーの選択から始めましょう。",
"select_provider_countries_text_hetzner": "ドイツ、フィンランド、アメリカ合衆国",
"select_provider_payment_title": "支払方法",
"select_provider_price_text_hetzner": "比較的小規模なサーバーと50GBのディスク容量で、月額8ユーロ",
"select_provider_price_text_do": "比較的小規模なサーバーと50GBのディスク容量で、月額17ユーロ",
"select_provider_payment_text_hetzner": "クレジットカード、国際送金SWIFT、SEPA、PayPal",
"select_provider_payment_text_do": "クレジットカード、Google Pay、PayPal",
"select_provider_email_notice": "新規のクライアントは電子メールのホスティングを利用できません。最初の支払いが完了次第、利用可能となります。",
"connect_to_server_provider": "にログイン ",
"how": "APIトークンを取得する方法について",
"provider_bad_key_error": "サービス提供者のAPI鍵が不正です",
"could_not_connect": "サービス提供者に接続できませんでした。",
"select_provider_site_button": "サイトを訪問",
"choose_location_type": "サーバーの所在地",
"backblaze_bad_key_error": "Backblazeのデータストレージに関する情報が不正です",
"use_this_domain": "このドメインを使用しますか?",
"connect_to_dns": "DNSのサービス提供者に接続",
"select_dns": "DNSのサービス提供者を選びましょう",
"server_rebooted": "サーバーを再起動しました。最後の認証を待機しています…",
"loading_domain_list": "ドメインの一覧を読み込んでいます",
"save_domain": "ドメインを保存",
"enter_username_and_password": "ユーザー名と強固なパスワードを入力してください",
"check": "確認",
"server_created": "サーバーを作成しました。DNSの確認とサーバーの起動を行っています…",
"steps": {
"dns_provider": "DNSのサービス提供者",
"backups_provider": "バックアップ",
"domain": "ドメイン",
"master_account": "マスターアカウント",
"server": "サーバー",
"hosting": "ホスティング",
"server_type": "サーバーの種類",
"dns_setup": "DNSの設定",
"nixos_installation": "NixOSのインストール",
"server_reboot": "サーバーの再起動",
"final_checks": "最終確認"
},
"select_provider_price_title": "平均価格",
"connect_to_server_provider_text": "APIトークンで、SelfPrivacyはコンピューターを借り、借りたコンピューター上でサーバーを設定することができます。",
"choose_server_type_ram": "{}GBのRAM",
"choose_server_type_storage": "{}GBのシステムデータ容量",
"dns_provider_bad_key_error": "API鍵が不正です",
"multiple_domains_found": "複数のドメインが見つかりました",
"server_started": "サーバーを開始しました。検証後、再起動します…",
"create_master_account": "マスターアカウントを作成",
"create_server": "サーバーを作成",
"one_more_restart": "最後にもう一度、セキュリティー証明書を適用するために再起動します。",
"domain_critical_error": "このドメインに到達できません!タップすると詳細を表示します…",
"select_provider_countries_text_do": "アメリカ合衆国、オランダ、シンガポール、イギリス、ドイツ、カナダ、インド、オーストラリア",
"select_provider_notice": "「比較的小規模」という表現で、私たちは2個のCPUのコアと、2GBのRAMを備えたコンピューターのことを指しています。",
"select_provider": "以下の一覧からサービス提供者を選択してください。どのサービス提供者もSelfPrivacyをサポートしています",
"select_provider_countries_title": "利用可能な国",
"choose_location_type_text": "所在地に応じて、サーバーの設定、値段、接続速度などが異なります。",
"back_to_locations": "別のものを選択してください",
"choose_server_type_payment_per_month": "月額{}",
"no_locations_found": "利用できる所在地が見つかりませんでした。アカウントにアクセスできることを確認してください",
"final": "最終段階",
"until_the_next_check": "次の確認まで: ",
"choose_server_type_payment_storage": "追加の保存領域に{}",
"choose_server_type_payment_server": "サーバー用に{}",
"locations_not_found": "あら!",
"choose_server_type_payment_ip": "公開のIPv4アドレスに{}",
"use_this_domain_text": "指定されたトークンで、以下のドメインにアクセスできます",
"no_connected_domains": "現在、接続しているドメインはありません",
"checks": "確認が完了しました\n{}件/{}件中",
"what": "その意味を解説",
"choose_server_type_notice": "確認すべき項目はCPUとRAMです。あなたのサービスのデータは、簡単に拡張でき、別個に使用料金が請求されるマウント済のボリューム上に保存されます。",
"choose_server_type": "必要な種類のサーバー",
"locations_not_found_text": "借りられるサーバーがありません",
"choose_server_type_text": "異なるサービスには異なる性能のサーバーが必要です。なお、サーバーはいつでも拡張できます",
"multiple_domains_found_text": "指定されたトークンで、以下のドメインにアクセスできます。使用したいドメインを選択してください。他のドメインのセキュリティーの観点から、SelfPrivacyで使用したいドメインにだけ、このトークンのアクセスを制限してください。"
},
"modals": {
"purge_all_keys_confirm": "はい、私の全てのトークンを消去してください",
"dns_removal_error": "DNSレコードを削除できませんでした。",
"volume_creation_error": "ボリュームを作成できませんでした。",
"server_validators_error": "利用可能なサーバーを取得できませんでした。",
"try_again": "再度試しますか?",
"reboot": "再起動",
"are_you_sure": "よろしいですか?",
"yes": "はい",
"no": "いいえ",
"purge_all_keys": "全ての認証鍵を消去しますか?",
"server_deletion_error": "動作中のサーバーを削除できませんでした。",
"already_exists": "そのサーバーは既に存在しています。",
"destroy_server": "サーバーを破棄して新しいサーバーを作成しますか?",
"unexpected_error": "プロバイダー側からの配置中に、予期しないエラーが発生しました。"
},
"storage": {
"size": "サイズ",
"data_migration_title": "データの移行",
"gb": "{}GB",
"mb": "{}MB",
"extending_volume_modal_description": "月毎に{}を{}で使用するプランにアップグレード。",
"extending_volume_price_info": "価格は内税表示で、サーバーのサービス提供者から提供された価格のデータを基に試算されたものです。サイズの変更後、サーバーは再起動します。",
"extending_volume_title": "ボリュームを拡張",
"extending_volume_error": "ボリュームの拡張を開始できませんでした。",
"extending_volume_started": "ボリュームの拡張を開始しました",
"extending_volume_provider_waiting": "サービス提供者のボリュームを拡張しました。10秒待機しています…",
"data_migration_notice": "データ移行中は全てのサービスが利用できなくなります。",
"start_migration_button": "移行を開始",
"extending_volume_server_waiting": "サービス提供者のボリュームを拡張しました。20秒待機しています…",
"extending_volume_rebooting": "サーバーを再起動しています…",
"extend_volume_button": "ボリュームを拡張",
"extending_volume_description": "ボリュームを拡張すると、サーバー自体を拡張することなく、より多くのデータをサーバーに保存できるようになります。一度増加した容量を減らすことはできません。",
"kb": "{}KB",
"price": "料金",
"status_ok": "使用状況に問題はありません",
"status_error": "空き領域が少なくなっています",
"bytes": "バイト",
"disk_total": "全{} · {}",
"card_title": "サーバーの保存領域",
"disk_usage": "{}使用しています"
},
"password_manager": {
"login_info": "ウェブサイトでアカウントを作成する必要があります。"
},
"mail": {
"login_info": "ユーザータブのユーザー名とパスワードを使用してください。IMAPのポート番号は143、SMTPのポート番号は587となります。両方ともSTARTTLSを指定してください。"
},
"video": {
"login_info": "アカウントは不要です。"
},
"cloud": {
"login_info": "ログイン名は「admin」で、パスワードは主のユーザーのものと同一です。Nextcloudのインターフェースで新しいアカウントを作成してください。"
},
"social_network": {
"login_info": "ウェブサイトでアカウントを作成する必要があります。"
},
"timer": {
"sec": "{}秒"
},
"developer_settings": {
"title": "開発者設定",
"server_setup": "サーバーの設定",
"add_root_ssh_key": "ルートSSH鍵を追加",
"allow_ssh_key_at_setup_description": "鍵を追加するためのボタンが確認画面に表示されます。",
"allow_ssh_key_at_setup": "設定中にルートSSH鍵の設定を許可",
"use_staging_acme": "ACMEテストサーバーを使用",
"reset_onboarding_description": "初期設定のスイッチをリセットして、初期設定画面を再度表示",
"cubit_statuses": "Cubitの読み込みの状況",
"routing": "アプリのルーティング",
"reset_onboarding": "初期設定のスイッチをリセット",
"subtitle": "これらの設定はデバッグ用です。分からない場合は変更しないでください。",
"use_staging_acme_description": "新しいサーバーを設定する際に適用されます。",
"ignore_tls": "TLS証明書を検証しない",
"root_ssh_key_added": "ルートSSH鍵を設定しました。適用します",
"ignore_tls_description": "サーバーに接続する際にアプリはTLS証明書を検証しません。"
},
"support": {
"title": "SelfPrivacyのサポート"
},
"not_ready_card": {
"in_menu": "サーバーはまだ設定されていません。セットアップウィザードを使用してセットアップを完了してください。"
},
"git": {
"login_info": "ウェブサイトでアカウントを作成する必要があります。最初のユーザーが管理者となります。"
},
"more_page": {
"configuration_wizard": "セットアップウィザード",
"create_ssh_key": "スーパーユーザーのSSH鍵",
"onboarding": "初期設定"
}
}

View file

@ -45,14 +45,15 @@
"configuration_wizard": "Конфигурация көмекшісі"
},
"ssh": {
"create": "Жасау",
"create": "SSH кілтін қосыңыз",
"title": "SSH",
"delete": "SSH кілтін жою",
"delete_confirm_question": "Жоюды растау сұрағы?",
"root_subtitle": "Root сипаттамасы.",
"no_key_name": "Кілт атауы жоқ",
"root_title": "Root атауы",
"input_label": "Енгізу жолағы"
"input_label": "Енгізу жолағы",
"ssh_disabled_warning": "SSH ажыратылады. Оны сервер параметрлерінде қосуға болады."
},
"onboarding": {
"page2_dns_provider_title": "2-бет DNS провайдер аталымы",
@ -91,16 +92,14 @@
"bug_report_subtitle": "Спамға байланысты есептік жазбаны қолмен растау қажет. Тіркелгіні белсендіру үшін Қолдау чатында бізге хабарласыңыз."
},
"application_settings": {
"title": "Қосымша параметрлері",
"system_theme_mode_title": "Системалық қараңғы тақырып",
"system_theme_mode_description": "Системалық қараңғы тақырып сипаттамасы",
"dark_theme_title": "Қараңғы тақырып",
"change_application_theme": "Қараңғы тақырып сипаттамасы",
"dangerous_settings": "Қауіпті параметрлер",
"reset_config_title": "Конфигурацияны қалпына келтіру",
"title": "Қосымша параметрлері",
"system_dark_theme_title": "Системалық қараңғы тақырып",
"system_dark_theme_description": "Системалық қараңғы тақырып сипаттамасы",
"dark_theme_title": "Қараңғы тақырып",
"dark_theme_description": "Қараңғы тақырып сипаттамасы",
"delete_server_title": "Серверді жою",
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы.",
"delete_server_description": "Серверді жою сипаттамасы."
"reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы."
},
"resource_chart": {
"month": "Ай",
@ -115,8 +114,8 @@
"card_title": "Сервер карточкасы",
"server_id": "Сервер идентификаторы",
"status": "Күй",
"cpu": "CPU",
"ram": "RAM",
"cpu": "Процессор",
"ram": "Жедел жады",
"description": "Сипаттама",
"general_information": "Жалпы ақпарат",
"allow_autoupgrade": "Автоматты жаңартуға рұқсат етілген",
@ -126,11 +125,496 @@
"reboot_after_upgrade_hint": "Жаңартудан кейін қайта жүктеу түсініктемесі",
"server_timezone": "Сервер уақыты белдеуі",
"select_timezone": "Уақыт белдеуін таңдау",
"timezone_search_bar": "Уақыт белдеуі іздеу жолағы"
"timezone_search_bar": "Уақыт белдеуі іздеу жолағы",
"settings": "Сервер параметрлері",
"server_provider": "Сервер провайдері",
"dns_provider": "DNS провайдері",
"core_count": {
"one": "{} ядро",
"two": "{} ядра",
"few": "{} ядра",
"many": "{} ядер",
"other": "{} ядер"
},
"pricing_error": "Провайдер бағасын алу мүмкін болмады",
"enable_ssh": "SSH қосу",
"enable_ssh_hint": "SSH арқылы серверге кіруге рұқсат беріңіз",
"disk": "Диск",
"allow_password_authentication": "Құпия сөзді пайдаланып SSH жүйесіне кіруге рұқсат беріңіз",
"allow_password_authentication_hint": "Пайдаланушыларға құпия сөзді пайдаланып SSH арқылы серверге қосылуға мүмкіндік береді (суперпайдаланушыға қолданылмайды)",
"monthly_cost": "Айлық құны",
"location": "Орналастыру"
},
"console_page": {
"title": "Консоль",
"waiting": "Күтуде…",
"copy": "Көшіру"
},
"developer_settings": {
"allow_ssh_key_at_setup": "Баптау кезінде SSH кілтіне рұқсат беру",
"server_setup": "Сервер баптауы",
"use_staging_acme": "Staging ACME пайдалану",
"use_staging_acme_description": "Staging ACME пайдалану сипаттамасы",
"ignore_tls": "TLS елемеу",
"ignore_tls_description": "TLS елемеу сипаттамасы",
"allow_ssh_key_at_setup_description": "Баптау кезінде SSH кілтіне рұқсат беру сипаттамасы",
"add_root_ssh_key": "Root SSH кілтін қосу",
"root_ssh_key_added": "Root SSH кілті қосылды",
"routing": "Маршруттау",
"title": "Әзірлеуші параметрлері",
"subtitle": "Әзірлеуші параметрлері субтитры",
"reset_onboarding": "Onboarding қалпына келтіру",
"reset_onboarding_description": "Onboarding қалпына келтіру сипаттамасы",
"cubit_statuses": "Cubit статустары"
},
"domain": {
"uninitialized": "Деректер әлі алынған жоқ",
"services_title": "Қызметтер",
"card_title": "Домен",
"update_list": "Тізімді жаңарту",
"services_subtitle": "Қызметтердің жұмыс істеуі үшін «А» түріндегі жазбалар қажет.",
"email_title": "Электрондық пошта",
"email_subtitle": "Қауіпсіз электрондық пошта алмасу үшін қажетті жазбалар.",
"screen_title": "Домен және DNS",
"ok": "Жазбалар қалыпты",
"error": "Мәселелер анықталды",
"error_subtitle": "Оны түзету үшін осы жерді басыңыз. Бұл сонымен қатар барлық үшінші тарап жазбаларын жояды.",
"refreshing": "Деректерді жаңарту…"
},
"backup": {
"initialize": "Баптау",
"restore": "Көшірмеден қалпына келтіру",
"create_new": "Жаңа көшірме жасаңыз",
"pending_jobs": "Күтілуде жұмыс",
"snapshots_title": "Суреттер атауы",
"forget_snapshot": "Суретті ұмыту",
"forget_snapshot_alert": "Бұл фотосуретті шынымен жойғыңыз келе ме? Бұл әрекетті әдетте кері қайтару мүмкін емес.",
"forget_snapshot_error": "Суретті ұмыту қатесі",
"snapshot_reasons": {
"auto": "Автоматты",
"explicit": "Айқын",
"pre_restore": "Қалпына келтіруден бұрын",
"unknown": "Белгісіз"
},
"rotation_quotas_title": "Айналдыру квоталары",
"set_rotation_quotas": "Айналдыру квоталарын орнату",
"quota_subtitles": {
"no_effect": "Әсері жоқ",
"last": {
"zero": "Нөл",
"one": "Бір",
"other": "Басқа",
"two": "Екі",
"many": "Көп",
"few": "Бірнеше"
},
"daily": {
"many": "Соңғы {} күнделікті сурет сақталады",
"other": "Соңғы {} күнделікті сурет сақталады",
"two": "Соңғы {} күнделікті сурет сақталады",
"zero": "Нөл",
"one": "Соңғы {} күнделікті сурет сақталады",
"few": "Соңғы {} күнделікті сурет сақталады"
},
"weekly": {
"zero": "Нөл",
"other": "Соңғы {} апталық сурет сақталады",
"one": "Соңғы {} апталық сурет сақталады",
"two": "Соңғы {} апталық сурет сақталады",
"few": "Соңғы {} апталық сурет сақталады",
"many": "Соңғы {} апталық сурет сақталады"
},
"daily_infinite": "Барлық күнделікті суреттер сақталады",
"monthly": {
"two": "Ең соңғы {} айлық сурет сақталады",
"few": "Ең соңғы {} айлық сурет сақталады",
"many": "Ең соңғы {} айлық сурет сақталады",
"other": "Ең соңғы {} айлық сурет сақталады",
"zero": "Нөл",
"one": "Соңғы {} айлық сурет сақталады"
},
"yearly": {
"other": "Басқа",
"zero": "Нөл",
"one": "Соңғы {} жылдық сурет сақталады",
"two": "Ең соңғы {} жылдық сурет сақталады",
"few": "Ең соңғы {} жылдық сурет сақталады",
"many": "Ең соңғы {} жылдық сурет сақталады"
},
"monthly_infinite": "Барлық айлық суреттер сақталады",
"weekly_infinite": "Барлық апталық суреттер сақталады",
"last_infinite": "Шексіз",
"yearly_infinite": "Шексіз"
},
"backups_encryption_key_description": "Бұл кілт сақтық көшірмелерді шифрлау үшін пайдаланылады. Егер сіз оны жоғалтсаңыз, сақтық көшірмеден деректерді қалпына келтіре алмайсыз. Оны қауіпсіз жерде сақтаңыз. Деректерді қолмен қалпына келтіру қажет болса, бұл пайдалы болуы мүмкін.",
"autobackup_set_period": "Кезеңді орнату",
"description": "Ол сізге кез келген жағдайда көмектеседі: хакерлер шабуылы, серверді жою және т.б.",
"reuploaded_key": "Сервердегі кілт жаңартылды",
"no_backups": "Сақтық көшірмелер әлі жоқ",
"restore_alert": "Сіз {} жасаған көшірмеден қалпына келтіресіз. Барлық ағымдағы деректер жоғалады. Сіз сенімдісіз бе?",
"autobackup_period_every": "Әрбір",
"autobackup_period_disable": "Өшіру",
"card_subtitle": "Сақтық көшірмелерді басқарыңыз",
"select_all": "Барлығын көшіріңіз",
"quota_titles": {
"last": "Соңғы",
"daily": "Күнделікті",
"weekly": "Апта сайынғы",
"monthly": "Ай сайынғы",
"yearly": "Жыл сайынғы"
},
"show_more": "Көбірек көрсету",
"autobackup_period_title": "Авто сақтық көшірме кезеңі",
"autobackup_period_subtitle": "Авто сақтық көшірме кезеңі субтитры",
"autobackup_period_never": "Ешқашан",
"refresh": "Күйді жаңарту",
"refetch_backups": "Көшірмелер тізімін жаңарту",
"quotas_only_applied_to_autobackups": "Бұл параметрлер автоматты түрде жасалған сақтық көшірмелерге ғана қолданылады. Қолмен жасалған сақтық көшірмелер осы ережелер бойынша жойылмайды.",
"backups_encryption_key_not_found": "Шифрлау кілті әлі табылмады, әрекетті кейінірек қайталаңыз.",
"card_title": "Сақтық көшірме",
"reupload_key": "Кілтті жаңартуды мәжбүрлеу",
"latest_snapshots": "Соңғы суреттер",
"latest_snapshots_subtitle": "Соңғы суреттер субтитры",
"reupload_key_subtitle": "Сақтық көшірме сақтау орнын тағы да инициализациялайды. Бірдеңе бұзылса, оны пайдаланыңыз.",
"refetch_backups_subtitle": "Кэшті қалпына келтіріп, провайдерден деректерді сұраңыз. Қосымша шығындар болуы мүмкін.",
"refetching_list": "Тізім бірнеше минуттан кейін жаңартылады",
"create_new_select_heading": "Көшіру қызметтерін таңдаңыз",
"start": "Көшірме жасауды бастаңыз",
"service_busy": "Басқа сақтық көшірмелер қазір жасалып жатыр",
"backups_encryption_key": "Шифрлау кілті",
"backups_encryption_key_copy": "Кілтті көшіру",
"backups_encryption_key_subtitle": "Оны қауіпсіз жерде сақтаңыз.",
"backups_encryption_key_show": "Кілтті көрсету",
"snapshot_modal_heading": "Сурет модалы атауы",
"snapshot_service_title": "Сурет қызметі атауы",
"snapshot_creation_time_title": "Сурет жасау уақыты",
"snapshot_id_title": "Сурет идентификаторы",
"snapshot_modal_select_strategy": "Стратегияны таңдау модалы",
"snapshot_modal_download_verify_option_title": "Жүктеу тексеру опциясы модалы",
"snapshot_modal_inplace_option_title": "Орындау опциясы модалы",
"snapshot_modal_download_verify_option_description": "Тәуекел аз, бірақ көбірек бос орын қажет. Бүкіл сақтық көшірмені уақытша жадқа жүктеу, көшірменің тұтастығын тексеру, содан кейін ағымдағы деректерді ауыстыру.",
"snapshot_modal_inplace_option_description": "Аз орын қажет, бірақ жоғары тәуекел. Сақтық көшірмеден деректерді жүктегенде, ағымдағы деректер дереу ауыстырылады.",
"restore_started": "Қалпына келтіру басталды",
"snapshot_reason_title": "Сурет себебі атауы",
"snapshot_modal_service_not_found": "Бұл серверде енді жоқ қызметтің суреті. Әдетте бұл болмауы керек және біз автоматты түрде қалпына келтіруді орындай алмаймыз. Суретті жүктеп алып, оны қолмен қалпына келтіруге болады. Көмек қажет болса, SelfPrivacy қолдау қызметіне хабарласыңыз."
},
"storage": {
"status_ok": "Статус: жақсы",
"status_error": "Статус: қате",
"disk_total": "Жалпы диск",
"mb": "МБ",
"extending_volume_title": "Көлемді ұлғайту атауы",
"extending_volume_description": "Көлемді ұлғайту сипаттамасы",
"data_migration_notice": "Деректерді көшіру туралы хабарлама",
"start_migration_button": "Көшіруді бастау",
"kb": "КБ",
"bytes": "Байт",
"extending_volume_started": "Көлемді ұлғайту басталды",
"extending_volume_provider_waiting": "Провайдерді күту",
"extending_volume_server_waiting": "Серверді күту",
"extending_volume_rebooting": "Қайта жүктеу",
"extending_volume_modal_description": "Көлемді ұлғайту модалы сипаттамасы",
"data_migration_title": "Деректерді көшіру атауы",
"card_title": "Сақтау карточкасы",
"extend_volume_button": "Көлемді ұлғайту",
"extending_volume_price_info": "Көлемді ұлғайту бағасы туралы ақпарат",
"extending_volume_error": "Көлемді ұлғайту қатесі",
"size": "Өлшемі",
"price": "Бағасы",
"disk_usage": "Диск қолдану",
"gb": "ГБ"
},
"service_page": {
"disable": "Өшіру",
"enable": "Қосу",
"move": "Жылжыту",
"uses": "Қолдану",
"snapshots": "Суреттер",
"status": {
"active": "Белсенді",
"activating": "Қосу",
"deactivating": "Өшіру",
"off": "Өшірулі",
"inactive": "Белсенді емес",
"failed": "Сәтсіз",
"reloading": "Қайта жүктеу"
},
"open_in_browser": "Браузерде ашу",
"nothing_here": "Мұнда ештеңе жоқ",
"restart": "Қайта қосу"
},
"git": {
"login_info": "Git кіру ақпараты"
},
"users": {
"details_title": "Пайдаланушылардың мәліметтері",
"delete_user": "Пайдаланушыны жою",
"nobody_here": "Мұнда ешкім жоқ",
"login": "Кіру",
"reset_password": "Құпия сөзді қалпына келтіру",
"account": "Есептік жазба",
"could_not_delete_user": "Пайдаланушыны жою мүмкін болмады",
"could_not_add_ssh_key": "SSH кілтін қосу мүмкін болмады",
"email_login": "Электрондық пошта арқылы кіру",
"add_new_user": "Жаңа пайдаланушы қосу",
"new_user": "Жаңа пайдаланушы",
"new_user_info_note": "Жаңа пайдаланушы туралы ескерту",
"delete_confirm_question": "Жоюды растау сұрағы?",
"could_not_fetch_users": "Пайдаланушыларды алу мүмкін болмады",
"could_not_fetch_description": "Сипаттаманы алу мүмкін болмады",
"refresh_users": "Пайдаланушыларды жаңарту",
"could_not_create_user": "Пайдаланушыны жасау мүмкін болмады",
"username_rule": "Пайдаланушы аты ережесі",
"no_ssh_notice": "SSH ескерту жоқ",
"user_already_exists": "Пайдаланушы қазірдің өзінде бар"
},
"initializing": {
"connect_to_server": "Серверге қосылу",
"select_provider_countries_text_do": "DigitalOcean елдері туралы мәтін",
"select_provider_price_title": "Провайдер бағасын таңдау атауы",
"select_provider_price_text_hetzner": "Hetzner бағасы туралы мәтін",
"select_provider_price_text_do": "DigitalOcean бағасы туралы мәтін",
"select_provider_payment_title": "Провайдер төлемін таңдау атауы",
"select_provider_payment_text_hetzner": "Hetzner төлемі туралы мәтін",
"select_provider_payment_text_do": "DigitalOcean төлемі туралы мәтін",
"connect_to_server_provider": "Сервер провайдеріне қосылу ",
"connect_to_server_provider_text": "Сервер провайдеріне қосылу мәтіні",
"choose_server_type_payment_per_month": "Айына төлем",
"choose_server_type_payment_storage": "Сақтау төлемі",
"select_dns": "DNS таңдау",
"multiple_domains_found": "Көптеген домендер табылды",
"loading_domain_list": "Домен тізімін жүктеу",
"save_domain": "Доменді сақтау",
"final": "Соңғы",
"create_server": "Сервер жасау",
"what": "Бұл нені білдіреді?",
"backblaze_bad_key_error": "Backblaze кілті қатесі",
"no_connected_domains": "Қосылған домендер жоқ",
"server_rebooted": "Сервер қайта жүктелді",
"check": "Тексеру",
"one_more_restart": "Тағы бір қайта қосу",
"create_master_account": "Бас есептік жазбаны жасау",
"enter_username_and_password": "Пайдаланушы аты мен құпия сөзді енгізіңіз",
"steps": {
"server_type": "Сервер түрі",
"master_account": "Бас есептік жазба",
"server": "Сервер",
"dns_setup": "DNS баптауы",
"nixos_installation": "NixOS орнату",
"server_reboot": "Серверді қайта жүктеу",
"final_checks": "Соңғы тексерулер",
"hosting": "Хостинг",
"dns_provider": "DNS провайдері",
"backups_provider": "Сақтық көшірме провайдері",
"domain": "Домен"
},
"select_provider": "Провайдерді таңдау",
"select_provider_countries_title": "Провайдер елдерін таңдау атауы",
"select_provider_email_notice": "Электрондық пошта туралы ескерту",
"select_provider_site_button": "Провайдер сайт батырмасы",
"how": "Қалай",
"provider_bad_key_error": "Провайдер кілті қатесі",
"could_not_connect": "Қосылу мүмкін болмады",
"choose_location_type": "Серверге қай жерде тапсырыс беруге болады?",
"back_to_locations": "Орналасуларға оралу",
"no_locations_found": "Орналасулар табылмады",
"choose_server_type_storage": "Сақтау орны",
"choose_server_type_payment_ip": "IP төлемі",
"dns_provider_bad_key_error": "DNS провайдер кілті қатесі",
"connect_to_dns": "DNS-ке қосылу",
"multiple_domains_found_text": "Көптеген домендер табылды мәтіні",
"server_started": "Сервер іске қосылды",
"server_created": "Сервер жасалды",
"until_the_next_check": "Келесі тексеріске дейін ",
"select_provider_notice": "Провайдерді таңдау туралы ескерту",
"select_provider_countries_text_hetzner": "Hetzner елдері туралы мәтін",
"choose_location_type_text": "Орналасу түрін таңдау мәтіні",
"locations_not_found": "Орналасулар табылмады",
"locations_not_found_text": "Орналасулар табылмады мәтіні",
"choose_server_type_text": "Сервер түрін таңдау мәтіні",
"choose_server_type_notice": "Сервер түрін таңдау туралы ескерту",
"choose_server_type_ram": "Жедел жады",
"choose_server_type": "Қай сервер түрін таңдауым керек?",
"select_provider_price_free": "Тегін",
"choose_server_type_payment_server": "Сервер төлемі",
"domain_critical_error": "Доменнің критикалық қатесі",
"checks": "Тексерулер",
"use_this_domain_text": "Осы доменді пайдалану мәтіні",
"use_this_domain": "Біз бұл доменді қолданамыз ба?"
},
"recovering": {
"provider_connected_placeholder": "Провайдер қосылу орны",
"confirm_server": "Серверді растау",
"confirm_server_description": "Сервер табылды! Ол екенін растаңыз:",
"fallback_select_provider_console": "Провайдер консолін таңдау әдісі",
"recovery_main_header": "Қалпына келтіру негізгі тақырыбы",
"method_select_other_device": "Басқа құрылғыны таңдау әдісі",
"method_select_recovery_key": "Қалпына келтіру кілтін таңдау әдісі",
"method_select_nothing": "Ешнәрсе таңдау әдісі",
"fallback_select_provider_console_hint": "Провайдер консолін таңдау ескерту",
"domain_not_available_on_token": "Домен токенде қолжетімді емес",
"modal_confirmation_ip_invalid": "IP жарамсыз",
"provider_connected_description": "Байланыс орнатылды. {} кіру рұқсаты бар таңбалауышыңызды енгізіңіз:",
"choose_server_description": "Серверді таңдау сипаттамасы",
"domain_recovery_description": "Серверге кіргіңіз келетін доменді енгізіңіз:",
"method_device_button": "Құрылғы батырмасы әдісі",
"method_device_input_description": "Құрылғы енгізу сипаттамасы әдісі",
"method_device_input_placeholder": "Құрылғы енгізу орны әдісі",
"fallback_select_token_copy": "Токен көшірмесін таңдау әдісі",
"confirm_server_accept": "Қабылдау",
"confirm_server_decline": "Қабылдамау",
"modal_confirmation_description": "Растау модалы сипаттамасы",
"modal_confirmation_dns_valid": "DNS жарамды",
"modal_confirmation_title": "Бұл шынымен сіздің серверіңіз бе?",
"generic_error": "Жалпы қате",
"domain_recover_placeholder": "Доменді қалпына келтіру орны",
"method_device_description": "Құрылғы сипаттамасы әдісі",
"fallback_select_root_ssh": "Root SSH таңдау әдісі",
"method_select_description": "Жүйеге кіру әдісін таңдаңыз:",
"authorization_failed": "Авторизация сәтсіз аяқталды",
"provider_connected": "Провайдер қосылды",
"choose_server": "Серверді таңдау",
"no_servers": "Серверлер жоқ",
"modal_confirmation_dns_invalid": "DNS жарамсыз",
"modal_confirmation_ip_valid": "IP жарамды",
"domain_recover_error": "Доменді қалпына келтіру қатесі",
"method_recovery_input_description": "Қалпына келтіру енгізу сипаттамасы әдісі",
"fallback_select_description": "Бұдан сізде не бар? Қолданылатын біріншісін таңдаңыз:"
},
"devices": {
"add_new_device_screen": {
"expired": "Мерзімі аяқталды",
"get_new_key": "Жаңа кілт алу",
"header": "Жаңа құрылғы экраны тақырыбы",
"please_wait": "Күте тұрыңыз",
"tip": "Кеңес",
"description": "Жаңа құрылғыда осы кілтті енгізіңіз:"
},
"revoke_device_alert": {
"description": "Құрылғыны қайтару ескерту сипаттамасы",
"header": "Кіру рұқсатын жою керек пе?",
"yes": "Иә",
"no": "Жоқ"
},
"main_screen": {
"this_device": "Бұл құрылғы",
"header": "Негізгі экран тақырыбы",
"description": "Негізгі экран сипаттамасы",
"other_devices": "Басқа құрылғылар",
"authorize_new_device": "Жаңа құрылғыны авторизациялау",
"access_granted_on": "Қолжетімділік берілді",
"tip": "Кеңес"
}
},
"recovery_key": {
"key_connection_error": "Кілт қосылым қатесі",
"key_main_header": "Кілт негізгі тақырыбы",
"key_main_description": "Кілт негізгі сипаттамасы",
"key_amount_field_title": "Кілт саны өріс атауы",
"key_duedate_toggle": "Кілт мерзімі ауыстыру",
"key_duedate_field_title": "Кілт мерзімі өріс атауы",
"key_receive_button": "Кілт қабылдау батырмасы",
"key_valid": "Кілт жарамды",
"key_creation_date": "Кілт жасау күні",
"key_synchronizing": "Кілт синхрондау",
"key_receiving_done": "Кілт қабылдау аяқталды",
"key_receiving_description": "Бұл кілтті қауіпсіз жерге жазыңыз. Ол сіздің серверіңізге толық қол жеткізуді қамтамасыз етеді:",
"key_amount_toggle": "Кілт саны ауыстыру",
"key_invalid": "Кілт жарамсыз",
"key_valid_until": "Кілт жарамдылығы",
"key_valid_for": "Кілт жарамдылығы үшін",
"key_replace_button": "Кілт ауыстыру батырмасы",
"key_receiving_info": "Кілт қабылдау туралы ақпарат",
"generation_error": "Кілт жасау қатесі"
},
"modals": {
"server_deletion_error": "Сервер жою қатесі",
"purge_all_keys_confirm": "Барлық кілттерді тазартуды растау",
"destroy_server": "Серверді жойып, жаңасын жасау керек пе?",
"already_exists": "Қазірдің өзінде бар",
"reboot": "Қайта жүктеу",
"try_again": "Тағы бір рет көріңіз бе?",
"volume_creation_error": "Көлем жасау қатесі",
"server_validators_error": "Сервер валидаторлары қатесі",
"dns_removal_error": "DNS жою қатесі",
"unexpected_error": "Күтпеген қате",
"are_you_sure": "Сіз сенімдісіз бе?",
"purge_all_keys": "Барлық авторизация кілттері өшірілсін бе?",
"yes": "Иә",
"no": "Жоқ"
},
"jobs": {
"title": "Жұмыстар",
"start": "Бастау",
"empty": "Бос",
"create_user": "Пайдаланушыны құру",
"delete_user": "Пайдаланушыны жою",
"service_turn_on": "Қызметті қосу",
"job_postponed": "Жұмыс кейінге қалдырылды",
"job_removed": "Жұмыс алынып тасталды",
"run_jobs": "Жұмыстарды орындау",
"reboot_failed": "Қайта жүктеу сәтсіз",
"config_pull_failed": "Конфигурацияны алу сәтсіз",
"rebuild_system": "Жүйені қайта құру",
"change_auto_upgrade_settings": "Авто жаңарту параметрлерін өзгерту",
"change_server_timezone": "Сервер уақыт белдеуін өзгерту",
"change_ssh_settings": "SSH параметрлерін өзгерту",
"service_turn_off": "Қызметті өшіру",
"upgrade_success": "Жаңарту сәтті",
"upgrade_failed": "Жаңарту сәтсіз",
"upgrade_server": "Серверді жаңарту",
"reboot_server": "Серверді қайта жүктеу",
"delete_ssh_key": "SSH кілтін жою",
"server_jobs": "Сервер жұмыстары",
"reset_user_password": "Пайдаланушы құпия сөзін қалпына келтіру",
"generic_error": "Жалпы қате",
"reboot_success": "Қайта жүктеу сәтті",
"start_server_upgrade": "Сервер жаңартуды бастау",
"create_ssh_key": "SSH кілтін жасау",
"job_added": "Жұмыс қосылды"
},
"validations": {
"already_exist": "Қазірдің өзінде бар",
"invalid_format": "Жарамсыз формат",
"invalid_format_password": "Құпия сөздің жарамсыз форматы",
"invalid_format_ssh": "SSH жарамсыз форматы",
"root_name": "Root атауы",
"required": "Міндетті",
"length_not_equal": "Ұзындығы тең емес",
"length_longer": "Ұзындығы ұзақ"
},
"countries": {
"germany": "Германия",
"netherlands": "Нидерланд",
"singapore": "Сингапур",
"united_kingdom": "Ұлыбритания",
"canada": "Канада",
"india": "Үндістан",
"australia": "Австралия",
"united_states": "Америка Құрама Штаттары",
"finland": "Финляндия"
},
"not_ready_card": {
"in_menu": "Мәзірде"
},
"password_manager": {
"login_info": "Құпия сөз менеджері кіру ақпараты"
},
"video": {
"login_info": "Бейне кіру ақпараты"
},
"social_network": {
"login_info": "Әлеуметтік желі кіру ақпараты"
},
"support": {
"title": "Қолдау"
},
"mail": {
"login_info": "Пошта кіру ақпараты"
},
"cloud": {
"login_info": "Бұлт кіру ақпараты"
},
"timer": {
"sec": "Секунд"
}
}

View file

@ -42,8 +42,7 @@
},
"console_page": {
"title": "Konsole",
"copy": "Kopēt",
"waiting": "Gaida inicializatīnu…"
"copy": "Kopēt"
},
"about_application_page": {
"title": "Par",
@ -52,16 +51,14 @@
"privacy_policy": "Privātuma politika"
},
"application_settings": {
"system_dark_theme_title": "Sistēmas noklusējuma dizains",
"dark_theme_title": "Tumšs dizains",
"title": "Aplikācijas iestatījumi",
"system_dark_theme_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem",
"dark_theme_description": "Lietojumprogrammas dizaina pārslēgšana",
"system_theme_mode_title": "Sistēmas noklusējuma dizains",
"system_theme_mode_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem",
"dark_theme_title": "Tumšs dizains",
"change_application_theme": "Lietojumprogrammas dizaina pārslēgšana",
"dangerous_settings": "Bīstamie iestatījumi",
"reset_config_title": "Atiestatīt lietojumprogrammas konfigurāciju",
"reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju.",
"delete_server_title": "Izdzēst serveri",
"delete_server_description": "Šis izdzēš jūsu serveri. Tas vairs nebūs pieejams."
"reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju."
},
"locale": "lv",
"ssh": {

View file

@ -33,7 +33,7 @@
"continue": "Kontynuować",
"alert": "Powiadomienie",
"app_name": "SelfPrivacy",
"please_connect": "Połącz się z serwerem, aby rozpocząć grę!",
"please_connect": "Połącz się z serwerem, dodaj domen i usługodawca DNS, aby rozpocząć!",
"copied_to_clipboard": "Skopiowane do schowka!"
},
"test": "pl-test",
@ -45,7 +45,6 @@
},
"console_page": {
"title": "Konsola",
"waiting": "Oczekiwanie na inicjalizację…",
"copy": "Kopia"
},
"about_application_page": {
@ -56,15 +55,13 @@
},
"application_settings": {
"title": "Ustawienia aplikacji",
"system_theme_mode_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu",
"system_theme_mode_title": "Domyślny motyw systemowy",
"dark_theme_title": "Ciemny motyw aplikacji",
"dark_theme_description": "Zmień kolor motywu aplikacji",
"change_application_theme": "Zmień kolor motywu aplikacji",
"dangerous_settings": "Niebezpieczne ustawienia",
"reset_config_title": "Resetowanie",
"reset_config_description": "Zresetuj klucze API i użytkownika root.",
"delete_server_title": "Usuń serwer",
"delete_server_description": "Ta czynność usunie serwer. Po tym będzie niedostępny.",
"system_dark_theme_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu",
"system_dark_theme_title": "Domyślny motyw systemowy",
"dangerous_settings": "Niebezpieczne ustawienia"
"reset_config_description": "Zresetuj klucze API i użytkownika root."
},
"ssh": {
"title": "klucze SSH",
@ -395,7 +392,6 @@
"destroy_server": "Zniszczyć serwer i stworzyć nowy?",
"try_again": "Mam spróbować jeszcze raz?",
"purge_all_keys_confirm": "Tak, wymazać wszystkie klucze",
"delete_server_volume": "Usunąć serwer i pamięć masową?",
"volume_creation_error": "Nie udało się utworzyć woluminu."
},
"recovery_key": {

View file

@ -36,7 +36,7 @@
"alert": "Уведомление",
"copied_to_clipboard": "Скопировано в буфер обмена!",
"app_name": "SelfPrivacy",
"please_connect": "Настройте ваш сервер и подключите домен для полного погружения!",
"please_connect": "Подключите ваш сервер, домен и провайдера DNS, чтобы начать!",
"network_error": "Ошибка сети"
},
"more_page": {
@ -46,8 +46,28 @@
},
"console_page": {
"title": "Консоль",
"waiting": "Ждём инициализации…",
"copy": "Копировать"
"copy": "Копировать",
"copy_raw": "Сырой ответ",
"history_empty": "Данных ещё нет",
"error": "Ошибка",
"rest_api_request": "Запрос REST API",
"rest_api_response": "Ответ REST API",
"graphql_request": "Запрос GraphQL",
"graphql_response": "Ответ GraphQL",
"logged_at": "Произошло в",
"data": "Данные",
"errors": "Ошибки",
"error_path": "Путь",
"error_locations": "Места ошибок",
"error_extensions": "Расширения",
"request_data": "Данные запроса",
"headers": "Заголовки",
"response_data": "Данные ответа",
"context": "Контекст",
"operation": "Операция",
"operation_type": "Тип операции",
"operation_name": "Название операции",
"variables": "Переменные"
},
"about_application_page": {
"title": "О приложении и поддержка",
@ -75,15 +95,15 @@
},
"application_settings": {
"title": "Настройки приложения",
"system_theme_mode_title": "Системная тема",
"system_theme_mode_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
"dark_theme_title": "Тёмная тема",
"dark_theme_description": "Сменить цветовую тему",
"change_application_theme": "Сменить цветовую тему",
"language": "Язык",
"click_to_change_locale": "Нажмите, чтобы открыть список языков",
"dangerous_settings": "Опасные настройки",
"reset_config_title": "Сброс настроек",
"reset_config_description": "Сбросить API ключи и root пользователя.",
"delete_server_title": "Удалить сервер",
"delete_server_description": "Действие приведёт к удалению сервера. После этого он будет недоступен.",
"system_dark_theme_title": "Системная тема",
"system_dark_theme_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек",
"dangerous_settings": "Опасные настройки"
"reset_config_description": "Сбросить API ключи и root пользователя."
},
"ssh": {
"title": "SSH ключи",
@ -541,7 +561,6 @@
"are_you_sure": "Вы уверены?",
"purge_all_keys": "Стереть все ключи авторизации?",
"purge_all_keys_confirm": "Да, стереть все ключи",
"delete_server_volume": "Удалить сервер и хранилище?",
"reboot": "Перезагрузить",
"yes": "Да",
"no": "Нет",
@ -578,7 +597,9 @@
"rebuild_system": "Пересобрать систему",
"start_server_upgrade": "Начать обновление сервера",
"change_server_timezone": "Изменить часовой пояс сервера",
"change_ssh_settings": "Изменить настройки SSH"
"change_ssh_settings": "Изменить настройки SSH",
"collect_nix_garbage_failed": "Не удалось начать сбор системного мусора",
"collect_nix_garbage": "Провести сбор системного мусора"
},
"validations": {
"required": "Обязательное поле",
@ -609,5 +630,16 @@
"root_ssh_key_added": "SSH ключ суперпользователя задан и будет применён",
"allow_ssh_key_at_setup": "Разрешить задавать SSH ключи суперпользователя во время установки",
"allow_ssh_key_at_setup_description": "Кнопка для добавления ключа появится на экране подтверждения."
},
"countries": {
"netherlands": "Нидерланды",
"germany": "Германия",
"singapore": "Сингапур",
"united_kingdom": "Великобритания",
"canada": "Канада",
"india": "Индия",
"australia": "Австралия",
"united_states": "США",
"finland": "Финляндия"
}
}

View file

@ -22,7 +22,6 @@
"try_again": "Skúsiť ešte raz?",
"purge_all_keys": "Vymazať všetky autorizačné kľúče?",
"purge_all_keys_confirm": "Áno, vyčistiť všetky moje tokeny",
"delete_server_volume": "Odstrániť server a úložisko?",
"reboot": "Reštartovať"
},
"jobs": {
@ -92,7 +91,6 @@
},
"console_page": {
"title": "Konzola",
"waiting": "Čakáme na inicializáciu…",
"copy": "Kopírovať"
},
"about_application_page": {
@ -103,15 +101,13 @@
},
"application_settings": {
"title": "Nastavenia aplikácie",
"system_theme_mode_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému",
"system_theme_mode_title": "Systémová predvolená téma",
"dark_theme_title": "Temná téma",
"dark_theme_description": "Zmeniť tému aplikácie",
"change_application_theme": "Zmeniť tému aplikácie",
"dangerous_settings": "Nebezpečné nastavenia",
"reset_config_title": "Resetovať nastavenia aplikácie",
"reset_config_description": "Resetovať kľúče API a užívateľa root.",
"delete_server_title": "Zmazať server",
"delete_server_description": "Tým sa odstráni váš server. Už nebude prístupným.",
"system_dark_theme_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému",
"system_dark_theme_title": "Systémová predvolená téma",
"dangerous_settings": "Nebezpečné nastavenia"
"reset_config_description": "Resetovať kľúče API a užívateľa root."
},
"ssh": {
"title": "Kľúče SSH",

View file

@ -43,7 +43,6 @@
},
"console_page": {
"title": "Konzole",
"waiting": "Čakanje na inicializacijo…",
"copy": "Kopiraj"
},
"about_application_page": {
@ -53,15 +52,13 @@
"application_version_text": "Različica aplikacije"
},
"application_settings": {
"dark_theme_title": "Temna tema",
"title": "Nastavitve aplikacije",
"system_dark_theme_title": "Privzeta tema sistema",
"system_dark_theme_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
"dark_theme_description": "Spreminjanje barvne teme",
"system_theme_mode_title": "Privzeta tema sistema",
"system_theme_mode_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve",
"dark_theme_title": "Temna tema",
"change_application_theme": "Spreminjanje barvne teme",
"dangerous_settings": "Nevarne nastavitve",
"reset_config_title": "Ponastavitev konfiguracije aplikacije",
"delete_server_title": "Brisanje strežnika",
"delete_server_description": "To dejanje povzroči izbris strežnika. Nato bo nedosegljiv."
"reset_config_title": "Ponastavitev konfiguracije aplikacije"
},
"onboarding": {
"page1_title": "Digitalna neodvisnost je na voljo vsem",

View file

@ -47,13 +47,11 @@
"privacy_policy": "นโยบายความเป็นส่วนตัว"
},
"application_settings": {
"dark_theme_description": "สลับธีมแอปพลิเคชั่นของคุณ",
"delete_server_description": "การกระทำนี้จะลบเซิฟเวอร์ของคุณทิ้งและคุณจะไม่สามารถเข้าถึงมันได้อีก",
"title": "การตั้งค่าแอปพลิเคชัน",
"dark_theme_title": "ธีมมืด",
"change_application_theme": "สลับธีมแอปพลิเคชั่นของคุณ",
"reset_config_title": "รีเซ็ตค่าดั้งเดิมการตั้งค่าของแอปพลิเคชั่น",
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root",
"delete_server_title": "ลบเซิฟเวอร์"
"reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root"
},
"ssh": {
"create": "สร้างกุญแจ SSH",
@ -66,8 +64,7 @@
"input_label": "กุญแจสาธารณะของ ED25519 หรือ RSA"
},
"console_page": {
"title": "คอนโซล",
"waiting": "กำลังรอการเริ่มตั้น…"
"title": "คอนโซล"
},
"domain": {
"services_subtitle": "ระเบียน A จำเป็นสำหรับแต่ละเซิร์ฟเวอร์",

View file

@ -41,15 +41,14 @@
"locale": "ua",
"application_settings": {
"title": "Налаштування додатка",
"reset_config_title": "Скинути налаштування",
"system_theme_mode_title": "Системна тема за замовчуванням",
"system_theme_mode_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
"dark_theme_title": "Темна тема",
"dark_theme_description": "Змінити тему додатка",
"reset_config_description": "Скинути API ключі та root користувача.",
"delete_server_title": "Видалити сервер",
"delete_server_description": "Це видалить ваш сервер. Він більше не буде доступний.",
"system_dark_theme_title": "Системна тема за замовчуванням",
"system_dark_theme_description": "Використовуйте світлу або темну теми залежно від системних налаштувань",
"dangerous_settings": "Небезпечні налаштування"
"change_application_theme": "Змінити тему додатка",
"language": "Мова",
"dangerous_settings": "Небезпечні налаштування",
"reset_config_title": "Скинути налаштування",
"reset_config_description": "Скинути API ключі та root користувача."
},
"ssh": {
"delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?",
@ -59,7 +58,8 @@
"delete": "Видалити SSH-ключ",
"no_key_name": "Безіменний ключ",
"root_title": "Це ключі суперкористувача",
"input_label": "Публічний ED25519, ECDSA або RSA ключ"
"input_label": "Публічний ED25519, ECDSA або RSA ключ",
"ssh_disabled_warning": "SSH вимкнено. Його можна ввімкнути в налаштуваннях сервера."
},
"more_page": {
"create_ssh_key": "SSH-ключі суперкористовача",
@ -68,14 +68,31 @@
},
"console_page": {
"title": "Консоль",
"waiting": "Очікування ініціалізації…",
"copy": "Copie"
},
"about_application_page": {
"application_version_text": "Версія додатку",
"application_version_text": "Версія програми",
"api_version_text": "Версія API сервера",
"privacy_policy": "Політика конфіденційності",
"title": "Про нас"
"title": "Про нас та підтримку",
"website": "Наш сайт",
"documentation": "Документація",
"matrix_channel": "Канал у Matrix",
"telegram_channel": "Канал у Telegram",
"get_support": "Отримати допомогу",
"versions": "Версії",
"open_source_licenses": "Ліцензії використовуваних бібліотек",
"links": "Посилання",
"matrix_support_chat": "Чат підтримки у Matrix",
"telegram_support_chat": "Чат підтримки у Telegram",
"email_support": "Підтримка по електронній пошті",
"contribute": "Внести внесок",
"source_code": "Вихідний код",
"bug_report": "Повідомити про неполадку",
"bug_report_subtitle": "Через спам потрібне ручне підтвердження акаунта. Зв'яжіться з нами в чаті підтримки для активації акаунта.",
"help_translate": "Допомогти з перекладом",
"matrix_contributors_chat": "Чат розробників у Matrix",
"telegram_contributors_chat": "Чат розробників у Telegram"
},
"onboarding": {
"page1_title": "Цифрова незалежність, доступна кожному",
@ -239,7 +256,12 @@
"location": "Місцезнаходження",
"server_provider": "Провайдер сервера",
"dns_provider": "Провайдер DNS",
"pricing_error": "Не вдалося отримати ціни постачальника"
"pricing_error": "Не вдалося отримати ціни постачальника",
"enable_ssh": "Увімкнути SSH",
"enable_ssh_hint": "Дозволити доступ до сервера через SSH",
"allow_password_authentication": "Дозволити вхід по SSH з використанням пароля",
"allow_password_authentication_hint": "Дозволяє користувачам підключатися до сервера по SSH використовуючи свій пароль (не застосовується до суперкористувача)",
"settings": "Налаштування сервера"
},
"domain": {
"card_title": "Домен",
@ -517,7 +539,6 @@
"are_you_sure": "Ви впевнені?",
"purge_all_keys": "Очистити всі ключі автентифікації?",
"purge_all_keys_confirm": "Так, очистити всі мої токени",
"delete_server_volume": "Видалити сервер і сховище?",
"reboot": "Перезавантажити",
"yes": "Так",
"no": "Ні",
@ -553,7 +574,8 @@
"change_server_timezone": "Змінити часовий пояс сервера",
"change_auto_upgrade_settings": "Зміна налаштувань автооновлення",
"job_postponed": "Завдання додано, але ви не зможете його запустити після завершення поточних завдань",
"rebuild_system": "Перезібрати систему"
"rebuild_system": "Перезібрати систему",
"change_ssh_settings": "Змінити налаштування SSH"
},
"validations": {
"required": "Потрібно",
@ -584,5 +606,16 @@
},
"support": {
"title": "Підтримка SelfPrivacy"
},
"countries": {
"germany": "Німеччина",
"united_kingdom": "Великобританія",
"netherlands": "Нідерланди",
"singapore": "Сінгапур",
"canada": "Канада",
"india": "Індія",
"australia": "Австралія",
"united_states": "США",
"finland": "Фінляндія"
}
}

View file

@ -344,7 +344,6 @@
"are_you_sure": "您确定吗?",
"purge_all_keys": "清除所有身份验证密钥?",
"purge_all_keys_confirm": "是的,清除我的所有令牌",
"delete_server_volume": "删除服务器和卷?",
"reboot": "重启",
"yes": "是",
"no": "否"
@ -447,8 +446,7 @@
},
"console_page": {
"title": "控制台",
"copy": "复制",
"waiting": "等待初始化…"
"copy": "复制"
},
"about_application_page": {
"title": "关于与支持",
@ -476,14 +474,12 @@
},
"application_settings": {
"title": "应用设置",
"system_dark_theme_title": "系统默认主题",
"system_theme_mode_title": "系统默认主题",
"system_theme_mode_description": "根据系统设置自动使用明亮或暗色主题",
"dark_theme_title": "暗色主题",
"system_dark_theme_description": "根据系统设置自动使用明亮或暗色主题",
"dark_theme_description": "切换应用主题",
"change_application_theme": "切换应用主题",
"dangerous_settings": "危险设置",
"reset_config_title": "重置应用配置",
"delete_server_title": "删除服务器",
"delete_server_description": "这将移除您的服务器。它将不再可以访问。",
"reset_config_description": "重置API密钥和root用户。"
},
"ssh": {

View file

@ -1,6 +1,6 @@
app-id: org.selfprivacy.app
runtime: org.freedesktop.Platform
runtime-version: '22.08'
runtime-version: '23.08'
sdk: org.freedesktop.Sdk
command: selfprivacy
finish-args:
@ -11,6 +11,7 @@ finish-args:
- "--share=network"
- "--own-name=org.selfprivacy.app"
- "--device=dri"
- "--talk-name=org.freedesktop.secrets"
modules:
- name: selfprivacy
buildsystem: simple
@ -35,7 +36,7 @@ modules:
sources:
- type: git
url: https://gitlab.gnome.org/GNOME/libsecret.git
tag: 0.20.5
tag: 0.21.4
- name: libjsoncpp
buildsystem: meson
config-opts:

View file

@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
import 'package:material_color_utilities/material_color_utilities.dart'
as color_utils;
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/config/localization.dart';
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
/// A class that many Widgets can interact with to read current app
/// configuration, update it, or listen to its changes.
///
/// AppController uses repo to change persistent data.
class AppController with ChangeNotifier {
AppController(this._repo);
/// repo encapsulates retrieval and storage of preferences
final PreferencesRepository _repo;
/// TODO: to be removed or changed
late final ApiConfigModel _apiConfigModel = getIt.get<ApiConfigModel>();
bool _loaded = false;
bool get loaded => _loaded;
// localization
late Locale _locale;
Locale get locale => _locale;
late List<Locale> _supportedLocales;
List<Locale> get supportedLocales => _supportedLocales;
// theme
late ThemeData _lightTheme;
ThemeData get lightTheme => _lightTheme;
late ThemeData _darkTheme;
ThemeData get darkTheme => _darkTheme;
late color_utils.CorePalette _corePalette;
color_utils.CorePalette get corePalette => _corePalette;
late bool _systemThemeModeActive;
bool get systemThemeModeActive => _systemThemeModeActive;
late bool _darkThemeModeActive;
bool get darkThemeModeActive => _darkThemeModeActive;
ThemeMode get themeMode => systemThemeModeActive
? ThemeMode.system
: darkThemeModeActive
? ThemeMode.dark
: ThemeMode.light;
late bool _shouldShowOnboarding;
bool get shouldShowOnboarding => _shouldShowOnboarding;
Future<void> init({
// required final AppPreferencesRepository repo,
required final ThemeData lightThemeData,
required final ThemeData darkThemeData,
required final color_utils.CorePalette colorPalette,
}) async {
// _repo = repo;
await Future.wait(<Future>[
// load locale
() async {
_supportedLocales = [
Localization.systemLocale,
...await _repo.getSupportedLocales(),
];
_locale = await _repo.getActiveLocale();
if (_locale != Localization.systemLocale) {
// preset value to other state holders
await _apiConfigModel.setLocaleCode(_locale.languageCode);
await _repo.setDelegateLocale(_locale);
}
}(),
// load theme mode && initialize theme
() async {
_lightTheme = lightThemeData;
_darkTheme = darkThemeData;
_corePalette = colorPalette;
_darkThemeModeActive = await _repo.getDarkThemeModeFlag();
_systemThemeModeActive = await _repo.getSystemThemeModeFlag();
}(),
// load onboarding flag
() async {
_shouldShowOnboarding = await _repo.getShouldShowOnboarding();
}(),
]);
_loaded = true;
// Important! Inform listeners a change has occurred.
notifyListeners();
}
// updateRepoReference
Future<void> setShouldShowOnboarding(final bool newValue) async {
// Do not perform any work if new and old flag values are identical
if (newValue == shouldShowOnboarding) {
return;
}
// Store the flag in memory
_shouldShowOnboarding = newValue;
notifyListeners();
// Persist the change
await _repo.setShouldShowOnboarding(newValue);
}
Future<void> setSystemThemeModeFlag(final bool newValue) async {
// Do not perform any work if new and old ThemeMode are identical
if (systemThemeModeActive == newValue) {
return;
}
// Store the new ThemeMode in memory
_systemThemeModeActive = newValue;
// Inform listeners a change has occurred.
notifyListeners();
// Persist the change
await _repo.setSystemModeFlag(newValue);
}
Future<void> setDarkThemeModeFlag(final bool newValue) async {
// Do not perform any work if new and old ThemeMode are identical
if (darkThemeModeActive == newValue) {
return;
}
// Store the new ThemeMode in memory
_darkThemeModeActive = newValue;
// Inform listeners a change has occurred.
notifyListeners();
// Persist the change
await _repo.setDarkThemeModeFlag(newValue);
}
Future<void> setLocale(final Locale newLocale) async {
// Do not perform any work if new and old Locales are identical
if (newLocale == _locale) {
return;
}
// Store the new Locale in memory
_locale = newLocale;
if (newLocale == Localization.systemLocale) {
return resetLocale();
}
/// update locale delegate, which in turn should update deps
await _repo.setDelegateLocale(newLocale);
// Persist the change
await _repo.setActiveLocale(newLocale);
// Update other locale holders
await _apiConfigModel.setLocaleCode(newLocale.languageCode);
}
Future<void> resetLocale() async {
/// update locale delegate, which in turn should update deps
await _repo.resetDelegateLocale();
// Persist the change
await _repo.resetActiveLocale();
// Update other locale holders
await _apiConfigModel.resetLocaleCode();
}
}

View file

@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:material_color_utilities/material_color_utilities.dart'
as color_utils;
import 'package:selfprivacy/config/app_controller/app_controller.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart';
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
class _AppControllerInjector extends InheritedNotifier<AppController> {
const _AppControllerInjector({
required super.child,
required super.notifier,
});
}
class InheritedAppController extends StatefulWidget {
const InheritedAppController({
required this.child,
super.key,
});
final Widget child;
@override
State<InheritedAppController> createState() => _InheritedAppControllerState();
static AppController of(final BuildContext context) => context
.dependOnInheritedWidgetOfExactType<_AppControllerInjector>()!
.notifier!;
}
class _InheritedAppControllerState extends State<InheritedAppController> {
// actual state provider
late AppController controller;
// hold local reference to active repo
late PreferencesRepository _repo;
bool initTriggerred = false;
@override
void didChangeDependencies() {
/// update reference on dependency change
_repo = InheritedPreferencesRepository.of(context)!;
if (!initTriggerred) {
/// hook controller repo to local reference
controller = AppController(_repo);
initialize();
initTriggerred = true;
}
super.didChangeDependencies();
}
Future<void> initialize() async {
late final ThemeData lightThemeData;
late final ThemeData darkThemeData;
late final color_utils.CorePalette colorPalette;
await Future.wait(
<Future<void>>[
() async {
lightThemeData = await AppThemeFactory.create(
isDark: false,
fallbackColor: BrandColors.primary,
);
}(),
() async {
darkThemeData = await AppThemeFactory.create(
isDark: true,
fallbackColor: BrandColors.primary,
);
}(),
() async {
colorPalette = (await AppThemeFactory.getCorePalette()) ??
color_utils.CorePalette.of(BrandColors.primary.value);
}(),
],
);
await controller.init(
colorPalette: colorPalette,
lightThemeData: lightThemeData,
darkThemeData: darkThemeData,
);
WidgetsBinding.instance.addPostFrameCallback((final _) {
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(final BuildContext context) => _AppControllerInjector(
notifier: controller,
child: widget.child,
);
}

View file

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart';
import 'package:selfprivacy/logic/bloc/connection_status_bloc.dart';
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
@ -56,58 +55,46 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
}
@override
Widget build(final BuildContext context) {
const isDark = false;
const isAutoDark = true;
return MultiProvider(
providers: [
BlocProvider(
create: (final _) => AppSettingsCubit(
isDarkModeOn: isDark,
isAutoDarkModeOn: isAutoDark,
isOnboardingShowing: true,
)..load(),
),
BlocProvider(
create: (final _) => supportSystemCubit,
),
BlocProvider(
create: (final _) => serverInstallationCubit,
lazy: false,
),
BlocProvider(
create: (final _) => usersBloc,
lazy: false,
),
BlocProvider(
create: (final _) => servicesBloc,
),
BlocProvider(
create: (final _) => backupsBloc,
),
BlocProvider(
create: (final _) => dnsRecordsCubit,
),
BlocProvider(
create: (final _) => recoveryKeyBloc,
),
BlocProvider(
create: (final _) => devicesBloc,
),
BlocProvider(
create: (final _) => serverJobsBloc,
),
BlocProvider(create: (final _) => connectionStatusBloc),
BlocProvider(
create: (final _) => serverDetailsCubit,
),
BlocProvider(create: (final _) => volumesBloc),
BlocProvider(
create: (final _) => JobsCubit(),
),
],
child: widget.child,
);
}
Widget build(final BuildContext context) => MultiProvider(
providers: [
BlocProvider(
create: (final _) => supportSystemCubit,
),
BlocProvider(
create: (final _) => serverInstallationCubit,
lazy: false,
),
BlocProvider(
create: (final _) => usersBloc,
lazy: false,
),
BlocProvider(
create: (final _) => servicesBloc,
),
BlocProvider(
create: (final _) => backupsBloc,
),
BlocProvider(
create: (final _) => dnsRecordsCubit,
),
BlocProvider(
create: (final _) => recoveryKeyBloc,
),
BlocProvider(
create: (final _) => devicesBloc,
),
BlocProvider(
create: (final _) => serverJobsBloc,
),
BlocProvider(create: (final _) => connectionStatusBloc),
BlocProvider(
create: (final _) => serverDetailsCubit,
),
BlocProvider(create: (final _) => volumesBloc),
BlocProvider(
create: (final _) => JobsCubit(),
),
],
child: widget.child,
);
}

18
lib/config/config.dart Normal file
View file

@ -0,0 +1,18 @@
import 'package:flutter/foundation.dart';
/// internal app configuration
const config = InternalConfig(
shouldDebugPrint: kDebugMode,
);
class InternalConfig {
const InternalConfig({
required this.shouldDebugPrint,
});
final bool shouldDebugPrint;
// example of other possible fields
// final String appVersion;
//
}

View file

@ -1,21 +1,25 @@
import 'package:get_it/get_it.dart';
import 'package:selfprivacy/logic/get_it/api_config.dart';
import 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/get_it/console_model.dart';
import 'package:selfprivacy/logic/get_it/navigation.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
export 'package:selfprivacy/logic/get_it/api_config.dart';
export 'package:selfprivacy/logic/get_it/api_connection_repository.dart';
export 'package:selfprivacy/logic/get_it/console.dart';
export 'package:selfprivacy/logic/get_it/console_model.dart';
export 'package:selfprivacy/logic/get_it/navigation.dart';
final GetIt getIt = GetIt.instance;
Future<void> getItSetup() async {
getIt.registerSingleton<NavigationService>(NavigationService());
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
getIt.registerSingleton<ResourcesModel>(ResourcesModel()..init());
getIt.registerSingleton<WizardDataModel>(WizardDataModel()..init());
final apiConfigModel = ApiConfigModel();
getIt.registerSingleton<ApiConfigModel>(apiConfigModel);
getIt.registerSingleton<ApiConnectionRepository>(
ApiConnectionRepository()..init(),

View file

@ -1,79 +1,248 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/dns_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/server.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
import 'package:selfprivacy/utils/app_logger.dart';
import 'package:selfprivacy/utils/platform_adapter.dart';
import 'package:selfprivacy/utils/secure_storage.dart';
class HiveConfig {
static final log = const AppLogger(name: 'hive_config').log;
/// bump on schema changes
static const version = 2;
static Future<void> init() async {
final String? storagePath = PlatformAdapter.storagePath;
print('HiveConfig: Custom storage path: $storagePath');
log('set custom storage path to: "$storagePath"');
await Hive.initFlutter(storagePath);
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(ServerHostingDetailsAdapter());
Hive.registerAdapter(ServerDomainAdapter());
Hive.registerAdapter(BackupsCredentialAdapter());
Hive.registerAdapter(BackblazeBucketAdapter());
Hive.registerAdapter(ServerProviderVolumeAdapter());
Hive.registerAdapter(UserTypeAdapter());
Hive.registerAdapter(DnsProviderTypeAdapter());
Hive.registerAdapter(ServerProviderTypeAdapter());
Hive.registerAdapter(BackupsProviderTypeAdapter());
await Hive.openBox(BNames.appSettingsBox);
final HiveAesCipher cipher = HiveAesCipher(
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
);
await Hive.openBox<User>(BNames.usersDeprecated);
await Hive.openBox<User>(BNames.usersBox, encryptionCipher: cipher);
final Box<User> deprecatedUsers = Hive.box<User>(BNames.usersDeprecated);
if (deprecatedUsers.isNotEmpty) {
final Box<User> users = Hive.box<User>(BNames.usersBox);
await users.addAll(deprecatedUsers.values.toList());
await deprecatedUsers.clear();
}
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
registerAdapters();
await decryptBoxes();
await performMigrations();
}
static Future<Uint8List> getEncryptedKey(final String encKey) async {
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
final bool hasEncryptionKey = await secureStorage.containsKey(key: encKey);
if (!hasEncryptionKey) {
final List<int> key = Hive.generateSecureKey();
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
static void registerAdapters() {
try {
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(ServerHostingDetailsAdapter());
Hive.registerAdapter(ServerDomainAdapter());
Hive.registerAdapter(BackupsCredentialAdapter());
Hive.registerAdapter(ServerProviderVolumeAdapter());
Hive.registerAdapter(BackblazeBucketAdapter());
Hive.registerAdapter(ServerProviderCredentialAdapter());
Hive.registerAdapter(DnsProviderCredentialAdapter());
Hive.registerAdapter(ServerAdapter());
Hive.registerAdapter(DnsProviderTypeAdapter());
Hive.registerAdapter(ServerProviderTypeAdapter());
Hive.registerAdapter(UserTypeAdapter());
Hive.registerAdapter(BackupsProviderTypeAdapter());
Hive.registerAdapter(ServerInstallationWizardDataAdapter());
log('successfully registered every adapter');
} catch (error, stackTrace) {
log(
'error registering adapters',
error: error,
stackTrace: stackTrace,
);
rethrow;
}
}
final String? string = await secureStorage.read(key: encKey);
return base64Url.decode(string!);
static Future<HiveAesCipher> getCipher() async {
List<int>? key = await SecureStorage.getKey();
if (key == null) {
key = Hive.generateSecureKey();
await SecureStorage.setKey(key);
}
return HiveAesCipher(key);
}
static Future<void> decryptBoxes() async {
try {
// load encrypted boxes into memory
final HiveAesCipher cipher = await getCipher();
await Hive.openBox(
BNames.serverInstallationBox,
encryptionCipher: cipher,
);
await Hive.openBox(
BNames.resourcesBox,
encryptionCipher: cipher,
);
await Hive.openBox(
BNames.wizardDataBox,
encryptionCipher: cipher,
);
log('successfully decrypted boxes');
} catch (error, stackTrace) {
log(
'error initializing encrypted boxes',
error: error,
stackTrace: stackTrace,
);
rethrow;
}
}
// migrations
static Future<void> performMigrations() async {
try {
// perform migration check
final localSettingsBox = await Hive.openBox(BNames.appSettingsBox);
// if it is an initial app launch, we do not need to perform any migrations
final savedVersion = localSettingsBox.isEmpty
? version
// if box was initialized, but database version was not introduced in
// it yet, it means that we have initial value
: await localSettingsBox.get(BNames.databaseVersion, defaultValue: 1);
/// launch migrations based on version
if (savedVersion < version) {
if (savedVersion < 2) {
await migrateFrom1To2();
}
/// add new migrations here, like:
/// if (version < 3) {...}, etc.
}
/// update saved version after successfull migrations
await localSettingsBox.put(BNames.databaseVersion, version);
} catch (error, stackTrace) {
log(
'error running db migrations',
error: error,
stackTrace: stackTrace,
);
rethrow;
}
}
/// introduce and populate resourcesBox
static Future<void> migrateFrom1To2() async {
final Box resourcesBox = Hive.box(BNames.resourcesBox);
if (resourcesBox.isEmpty) {
final Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
final ServerHostingDetails? serverDetails =
serverInstallationBox.get(BNames.serverDetails);
// move server provider config
final ServerProviderType? serverProvider =
serverInstallationBox.get(BNames.serverProvider) ??
serverDetails?.provider;
final String? serverProviderKey =
serverInstallationBox.get(BNames.hetznerKey);
if (serverProviderKey != null && serverProvider.isSpecified) {
final ServerProviderCredential serverProviderCredential =
ServerProviderCredential(
tokenId: null,
token: serverProviderKey,
provider: serverProvider!,
associatedServerIds: [if (serverDetails != null) serverDetails.id],
);
await resourcesBox.put(
BNames.serverProviderTokens,
[serverProviderCredential],
);
}
final String? serverLocation =
serverInstallationBox.get(BNames.serverLocation);
final String? serverType =
serverInstallationBox.get(BNames.serverTypeIdentifier);
final ServerDomain? serverDomain =
serverInstallationBox.get(BNames.serverDomain);
if (serverDetails != null && serverDomain != null) {
await resourcesBox.put(
BNames.servers,
[
Server(
domain: serverDomain,
hostingDetails: serverDetails.copyWith(
serverLocation: serverLocation,
serverType: serverType,
),
),
],
);
}
// move dns config
final String? dnsProviderKey =
serverInstallationBox.get(BNames.cloudFlareKey);
final DnsProviderType? dnsProvider =
serverInstallationBox.get(BNames.dnsProvider) ??
serverDomain?.provider;
if (dnsProviderKey != null && dnsProvider.isSpecified) {
final DnsProviderCredential dnsProviderCredential =
DnsProviderCredential(
tokenId: null,
token: dnsProviderKey,
provider: dnsProvider!,
associatedDomainNames: [
if (serverDomain != null) serverDomain.domainName,
],
);
await resourcesBox
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
}
// move backblaze (backups) config
final BackupsCredential? backblazeCredential =
serverInstallationBox.get(BNames.backblazeCredential);
final BackblazeBucket? backblazeBucket =
serverInstallationBox.get(BNames.backblazeBucket);
if (backblazeCredential != null) {
await resourcesBox
.put(BNames.backupsProviderTokens, [backblazeCredential]);
}
if (backblazeBucket != null) {
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
}
}
log('successfully migrated db from 1 to 2 version');
}
}
/// Mappings for the different boxes and their keys
class BNames {
/// App settings box. Contains app settings like [isDarkModeOn], [isOnboardingShowing]
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
static String appSettingsBox = 'appSettings';
/// A boolean field of [appSettingsBox] box.
static String isDarkModeOn = 'isDarkModeOn';
/// An integer with last saved version of the database
static String databaseVersion = 'databaseVersion';
/// A boolean field of [appSettingsBox] box.
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
static String darkThemeModeOn = 'isDarkModeOn';
/// A boolean field of [appSettingsBox] box.
static String isOnboardingShowing = 'isOnboardingShowing';
static String systemThemeModeOn = 'isAutoDarkModeOn';
/// Encryption key to decrypt [serverInstallationBox] and [usersBox] box.
static String serverInstallationEncryptionKey = 'key';
/// A boolean field of [appSettingsBox] box.
static String shouldShowOnboarding = 'isOnboardingShowing';
/// A string field
static String appLocale = 'appLocale';
/// Server installation box. Contains server details and provider tokens.
static String serverInstallationBox = 'appConfig';
@ -105,7 +274,7 @@ class BNames {
/// A String field of [serverInstallationBox] box.
static String cloudFlareKey = 'cloudFlareKey';
/// A String field of [serverTypeIdentifier] box.
/// A String field of [serverInstallationBox] box.
static String serverTypeIdentifier = 'serverTypeIdentifier';
/// A [User] field of [serverInstallationBox] box.
@ -132,9 +301,24 @@ class BNames {
/// A boolean field of [serverInstallationBox] box.
static String isRecoveringServer = 'isRecoveringServer';
/// Deprecated users box as it is unencrypted
static String usersDeprecated = 'users';
/// Resources and provider tokens box,
static String resourcesBox = 'resourcesBox';
/// Box with users
static String usersBox = 'usersEncrypted';
/// Server Provider Tokens of [resourcesBox] box.
static String serverProviderTokens = 'serverProviderTokens';
/// DNS Provider Tokens of [resourcesBox] box.
static String dnsProviderTokens = 'dnsProviderTokens';
/// Backups Provider Tokens of [resourcesBox] box.
static String backupsProviderTokens = 'backupsProviderTokens';
/// Servers of [resourcesBox] box.
static String servers = 'servers';
/// Wizard data box
static String wizardDataBox = 'wizardDataBox';
/// Server installation wizard data of [wizardDataBox] box.
static String serverInstallationWizardData = 'serverInstallationWizardData';
}

View file

@ -3,40 +3,78 @@ import 'package:flutter/material.dart';
class Localization extends StatelessWidget {
const Localization({
required this.child,
super.key,
this.child,
});
final Widget? child;
/// value for resetting locale in settings to system default
static const systemLocale = Locale('system');
// when adding new locale, add corresponding native language name to mapper
// below
static const supportedLocales = [
Locale('ar'),
Locale('az'),
Locale('be'),
Locale('cs'),
Locale('de'),
Locale('en'),
Locale('es'),
Locale('et'),
Locale('fr'),
Locale('he'),
Locale('ja'),
Locale('kk'),
Locale('lv'),
Locale('mk'),
Locale('pl'),
Locale('ru'),
Locale('sk'),
Locale('sl'),
Locale('th'),
Locale('uk'),
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
];
// https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags
static final _languageNames = {
systemLocale: 'System default',
const Locale('ar'): 'العربية',
const Locale('az'): 'Azərbaycan',
const Locale('be'): 'беларуская',
const Locale('cs'): 'čeština',
const Locale('de'): 'Deutsch',
const Locale('en'): 'English',
const Locale('es'): 'español',
const Locale('et'): 'eesti',
const Locale('fr'): 'français',
const Locale('he'): 'עברית',
const Locale('ja'): '日本語',
const Locale('kk'): 'Қазақша',
const Locale('lv'): 'latviešu',
const Locale('mk'): 'македонски јазик',
const Locale('pl'): 'polski',
const Locale('ru'): 'русский',
const Locale('sk'): 'slovenčina',
const Locale('sl'): 'slovenski',
const Locale('th'): 'ไทย',
const Locale('uk'): 'Українська',
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'): '中文',
};
static String getLanguageName(final Locale locale) =>
_languageNames[locale] ?? locale.languageCode;
final Widget child;
@override
Widget build(final BuildContext context) => EasyLocalization(
supportedLocales: const [
Locale('ar'),
Locale('az'),
Locale('be'),
Locale('cs'),
Locale('de'),
Locale('en'),
Locale('es'),
Locale('et'),
Locale('fr'),
Locale('he'),
Locale('kk'),
Locale('lv'),
Locale('mk'),
Locale('pl'),
Locale('ru'),
Locale('sk'),
Locale('sl'),
Locale('th'),
Locale('uk'),
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
],
supportedLocales: supportedLocales,
path: 'assets/translations',
fallbackLocale: const Locale('en'),
useFallbackTranslations: true,
saveLocale: false,
useOnlyLangCode: false,
child: child!,
child: child,
);
}

View file

@ -0,0 +1,33 @@
/// abstraction for manipulation of stored app preferences
abstract class PreferencesDataSource {
/// should onboarding be shown
Future<bool> getOnboardingFlag();
/// should onboarding be shown
Future<void> setOnboardingFlag(final bool newValue);
// TODO: should probably deprecate the following, instead add the
// getThemeMode and setThemeMode methods, which store one value instead of
// flags.
/// should system theme mode be enabled
Future<bool?> getSystemThemeModeFlag();
/// should system theme mode be enabled
Future<void> setSystemThemeModeFlag(final bool newValue);
/// should dark theme be enabled
Future<bool?> getDarkThemeModeFlag();
/// should dark theme be enabled
Future<void> setDarkThemeModeFlag(final bool newValue);
/// locale, as set by user
///
///
/// when null, app takes system locale
Future<String?> getLocale();
/// locale, as set by user
Future<void> setLocale(final String? newLocale);
}

View file

@ -0,0 +1,40 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
/// app preferences data source hive implementation
class PreferencesHiveDataSource implements PreferencesDataSource {
final Box _appSettingsBox = Hive.box(BNames.appSettingsBox);
@override
Future<bool> getOnboardingFlag() async =>
_appSettingsBox.get(BNames.shouldShowOnboarding, defaultValue: true);
@override
Future<void> setOnboardingFlag(final bool newValue) async =>
_appSettingsBox.put(BNames.shouldShowOnboarding, newValue);
@override
Future<bool?> getSystemThemeModeFlag() async =>
_appSettingsBox.get(BNames.systemThemeModeOn);
@override
Future<void> setSystemThemeModeFlag(final bool newValue) async =>
_appSettingsBox.put(BNames.systemThemeModeOn, newValue);
@override
Future<bool?> getDarkThemeModeFlag() async =>
_appSettingsBox.get(BNames.darkThemeModeOn);
@override
Future<void> setDarkThemeModeFlag(final bool newValue) async =>
_appSettingsBox.put(BNames.darkThemeModeOn, newValue);
@override
Future<String?> getLocale() async => _appSettingsBox.get(BNames.appLocale);
@override
Future<void> setLocale(final String? newLocale) async => newLocale == null
? _appSettingsBox.delete(BNames.appLocale)
: _appSettingsBox.put(BNames.appLocale, newLocale);
}

View file

@ -0,0 +1,64 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
class _PreferencesRepositoryInjector extends InheritedWidget {
const _PreferencesRepositoryInjector({
required this.settingsRepository,
required super.child,
});
final PreferencesRepository settingsRepository;
@override
bool updateShouldNotify(
covariant final _PreferencesRepositoryInjector oldWidget,
) =>
oldWidget.settingsRepository != settingsRepository;
}
/// Creates and injects app preferences repository inside widget tree.
class InheritedPreferencesRepository extends StatefulWidget {
const InheritedPreferencesRepository({
required this.child,
required this.dataSource,
super.key,
});
final PreferencesDataSource dataSource;
final Widget child;
@override
State<InheritedPreferencesRepository> createState() =>
_InheritedPreferencesRepositoryState();
static PreferencesRepository? of(final BuildContext context) => context
.dependOnInheritedWidgetOfExactType<_PreferencesRepositoryInjector>()
?.settingsRepository;
}
class _InheritedPreferencesRepositoryState
extends State<InheritedPreferencesRepository> {
late PreferencesRepository repo;
@override
void didChangeDependencies() {
super.didChangeDependencies();
/// recreate repo each time dependencies change
repo = PreferencesRepository(
dataSource: widget.dataSource,
setDelegateLocale: EasyLocalization.of(context)!.setLocale,
resetDelegateLocale: EasyLocalization.of(context)!.resetLocale,
getDelegateLocale: () => EasyLocalization.of(context)!.locale,
getSupportedLocales: () => EasyLocalization.of(context)!.supportedLocales,
);
}
@override
Widget build(final BuildContext context) => _PreferencesRepositoryInjector(
settingsRepository: repo,
child: widget.child,
);
}

View file

@ -0,0 +1,73 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/localization.dart';
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
class PreferencesRepository {
const PreferencesRepository({
required this.dataSource,
required this.getSupportedLocales,
required this.getDelegateLocale,
required this.setDelegateLocale,
required this.resetDelegateLocale,
});
final PreferencesDataSource dataSource;
/// easy localizations don't expose type of localization provider,
/// so it needs to be this crutchy (I could've created one more class-wrapper,
/// containing needed functions, but perceive it as boilerplate, because we
/// don't need additional encapsulation level here)
final FutureOr<void> Function(Locale) setDelegateLocale;
final FutureOr<void> Function() resetDelegateLocale;
final FutureOr<List<Locale>> Function() getSupportedLocales;
final FutureOr<Locale> Function() getDelegateLocale;
Future<bool> getSystemThemeModeFlag() async =>
(await dataSource.getSystemThemeModeFlag()) ?? true;
Future<void> setSystemThemeModeFlag(final bool newValue) async =>
dataSource.setSystemThemeModeFlag(newValue);
Future<bool> getDarkThemeModeFlag() async =>
(await dataSource.getDarkThemeModeFlag()) ?? false;
Future<void> setDarkThemeModeFlag(final bool newValue) async =>
dataSource.setDarkThemeModeFlag(newValue);
Future<void> setSystemModeFlag(final bool newValue) async =>
dataSource.setSystemThemeModeFlag(newValue);
Future<List<Locale>> supportedLocales() async => getSupportedLocales();
Future<Locale> getActiveLocale() async {
Locale? chosenLocale;
final String? storedLocaleCode = await dataSource.getLocale();
if (storedLocaleCode != null) {
chosenLocale = Locale(storedLocaleCode);
}
// when it's null fallback on delegate locale
chosenLocale ??= Localization.systemLocale;
return chosenLocale;
}
Future<void> setActiveLocale(final Locale newLocale) async {
await dataSource.setLocale(newLocale.toString());
}
Future<void> resetActiveLocale() async {
await dataSource.setLocale(null);
}
/// true when we need to show onboarding
Future<bool> getShouldShowOnboarding() async =>
dataSource.getOnboardingFlag();
/// true when we need to show onboarding
Future<void> setShouldShowOnboarding(final bool newValue) =>
dataSource.setOnboardingFlag(newValue);
}

View file

@ -1,18 +1,15 @@
import 'dart:convert';
import 'dart:io';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:http/io_client.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/models/message.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/console_log.dart';
void _logToAppConsole<T>(final T objectToLog) {
getIt.get<ConsoleModel>().addMessage(
Message(
text: objectToLog.toString(),
),
);
}
void _addConsoleLog(final ConsoleLog message) =>
getIt.get<ConsoleModel>().log(message);
class RequestLoggingLink extends Link {
@override
@ -20,13 +17,14 @@ class RequestLoggingLink extends Link {
final Request request, [
final NextLink? forward,
]) async* {
getIt.get<ConsoleModel>().addMessage(
GraphQlRequestMessage(
operation: request.operation,
variables: request.variables,
context: request.context,
),
);
_addConsoleLog(
GraphQlRequestConsoleLog(
// context: request.context,
operationType: request.type.name,
operation: request.operation,
variables: request.variables,
),
);
yield* forward!(request);
}
}
@ -35,20 +33,26 @@ class ResponseLoggingParser extends ResponseParser {
@override
Response parseResponse(final Map<String, dynamic> body) {
final response = super.parseResponse(body);
getIt.get<ConsoleModel>().addMessage(
GraphQlResponseMessage(
data: response.data,
errors: response.errors,
context: response.context,
),
);
_addConsoleLog(
GraphQlResponseConsoleLog(
// context: response.context,
data: response.data,
errors: response.errors,
rawResponse: jsonEncode(response.response),
),
);
return response;
}
@override
GraphQLError parseError(final Map<String, dynamic> error) {
final graphQlError = super.parseError(error);
_logToAppConsole(graphQlError);
_addConsoleLog(
ManualConsoleLog.warning(
customTitle: 'GraphQL Error',
content: graphQlError.toString(),
),
);
return graphQlError;
}
}
@ -96,8 +100,15 @@ abstract class GraphQLApiMap {
Future<GraphQLClient> getSubscriptionClient() async {
final WebSocketLink webSocketLink = WebSocketLink(
'ws://api.$rootAddress/graphql',
// Only [GraphQLProtocol.graphqlTransportWs] supports automatic pings, so we don't disconnect when nothing happens.
subProtocol: GraphQLProtocol.graphqlTransportWs,
config: SocketClientConfig(
autoReconnect: true,
initialPayload: _token.isEmpty
? null
: {
'Authorization': 'Bearer $_token',
},
headers: _token.isEmpty
? null
: {
@ -113,14 +124,15 @@ abstract class GraphQLApiMap {
);
}
String get _locale => getIt.get<ApiConfigModel>().localeCode ?? 'en';
String get _locale => getIt.get<ApiConfigModel>().localeCode;
String get _token {
String token = '';
final serverDetails = getIt<ApiConfigModel>().serverDetails;
final serverDetails = getIt<ResourcesModel>().serverDetails;
if (serverDetails != null) {
token = getIt<ApiConfigModel>().serverDetails!.apiToken;
token = serverDetails.apiToken;
}
return token;
}

View file

@ -288,6 +288,7 @@ type SSHSettingsMutationReturn implements MutationReturnInterface {
enum ServerProvider {
HETZNER
DIGITALOCEAN
OTHER
}
type Service {
@ -408,7 +409,8 @@ type StorageVolume {
}
type Subscription {
count(target: Int! = 100): Int!
jobUpdates: [ApiJob!]!
count: Int!
}
type System {
@ -443,6 +445,7 @@ type SystemMutations {
runSystemUpgrade: GenericJobMutationReturn!
rebootSystem: GenericMutationReturn!
pullRepositoryChanges: GenericMutationReturn!
nixCollectGarbage: GenericJobMutationReturn!
}
type SystemProviderInfo {

View file

@ -1625,7 +1625,18 @@ class _CopyWithStubImpl$Input$UserMutationInput<TRes>
_res;
}
enum Enum$BackupProvider { BACKBLAZE, NONE, MEMORY, FILE, $unknown }
enum Enum$BackupProvider {
BACKBLAZE,
NONE,
MEMORY,
FILE,
$unknown;
factory Enum$BackupProvider.fromJson(String value) =>
fromJson$Enum$BackupProvider(value);
String toJson() => toJson$Enum$BackupProvider(this);
}
String toJson$Enum$BackupProvider(Enum$BackupProvider e) {
switch (e) {
@ -1657,7 +1668,17 @@ Enum$BackupProvider fromJson$Enum$BackupProvider(String value) {
}
}
enum Enum$BackupReason { EXPLICIT, AUTO, PRE_RESTORE, $unknown }
enum Enum$BackupReason {
EXPLICIT,
AUTO,
PRE_RESTORE,
$unknown;
factory Enum$BackupReason.fromJson(String value) =>
fromJson$Enum$BackupReason(value);
String toJson() => toJson$Enum$BackupReason(this);
}
String toJson$Enum$BackupReason(Enum$BackupReason e) {
switch (e) {
@ -1685,7 +1706,17 @@ Enum$BackupReason fromJson$Enum$BackupReason(String value) {
}
}
enum Enum$DnsProvider { CLOUDFLARE, DIGITALOCEAN, DESEC, $unknown }
enum Enum$DnsProvider {
CLOUDFLARE,
DIGITALOCEAN,
DESEC,
$unknown;
factory Enum$DnsProvider.fromJson(String value) =>
fromJson$Enum$DnsProvider(value);
String toJson() => toJson$Enum$DnsProvider(this);
}
String toJson$Enum$DnsProvider(Enum$DnsProvider e) {
switch (e) {
@ -1713,7 +1744,16 @@ Enum$DnsProvider fromJson$Enum$DnsProvider(String value) {
}
}
enum Enum$RestoreStrategy { INPLACE, DOWNLOAD_VERIFY_OVERWRITE, $unknown }
enum Enum$RestoreStrategy {
INPLACE,
DOWNLOAD_VERIFY_OVERWRITE,
$unknown;
factory Enum$RestoreStrategy.fromJson(String value) =>
fromJson$Enum$RestoreStrategy(value);
String toJson() => toJson$Enum$RestoreStrategy(this);
}
String toJson$Enum$RestoreStrategy(Enum$RestoreStrategy e) {
switch (e) {
@ -1737,7 +1777,17 @@ Enum$RestoreStrategy fromJson$Enum$RestoreStrategy(String value) {
}
}
enum Enum$ServerProvider { HETZNER, DIGITALOCEAN, $unknown }
enum Enum$ServerProvider {
HETZNER,
DIGITALOCEAN,
OTHER,
$unknown;
factory Enum$ServerProvider.fromJson(String value) =>
fromJson$Enum$ServerProvider(value);
String toJson() => toJson$Enum$ServerProvider(this);
}
String toJson$Enum$ServerProvider(Enum$ServerProvider e) {
switch (e) {
@ -1745,6 +1795,8 @@ String toJson$Enum$ServerProvider(Enum$ServerProvider e) {
return r'HETZNER';
case Enum$ServerProvider.DIGITALOCEAN:
return r'DIGITALOCEAN';
case Enum$ServerProvider.OTHER:
return r'OTHER';
case Enum$ServerProvider.$unknown:
return r'$unknown';
}
@ -1756,6 +1808,8 @@ Enum$ServerProvider fromJson$Enum$ServerProvider(String value) {
return Enum$ServerProvider.HETZNER;
case r'DIGITALOCEAN':
return Enum$ServerProvider.DIGITALOCEAN;
case r'OTHER':
return Enum$ServerProvider.OTHER;
default:
return Enum$ServerProvider.$unknown;
}
@ -1769,7 +1823,12 @@ enum Enum$ServiceStatusEnum {
ACTIVATING,
DEACTIVATING,
OFF,
$unknown
$unknown;
factory Enum$ServiceStatusEnum.fromJson(String value) =>
fromJson$Enum$ServiceStatusEnum(value);
String toJson() => toJson$Enum$ServiceStatusEnum(this);
}
String toJson$Enum$ServiceStatusEnum(Enum$ServiceStatusEnum e) {
@ -1814,7 +1873,18 @@ Enum$ServiceStatusEnum fromJson$Enum$ServiceStatusEnum(String value) {
}
}
enum Enum$Severity { INFO, WARNING, ERROR, CRITICAL, SUCCESS, $unknown }
enum Enum$Severity {
INFO,
WARNING,
ERROR,
CRITICAL,
SUCCESS,
$unknown;
factory Enum$Severity.fromJson(String value) => fromJson$Enum$Severity(value);
String toJson() => toJson$Enum$Severity(this);
}
String toJson$Enum$Severity(Enum$Severity e) {
switch (e) {
@ -1850,7 +1920,16 @@ Enum$Severity fromJson$Enum$Severity(String value) {
}
}
enum Enum$UserType { NORMAL, PRIMARY, ROOT, $unknown }
enum Enum$UserType {
NORMAL,
PRIMARY,
ROOT,
$unknown;
factory Enum$UserType.fromJson(String value) => fromJson$Enum$UserType(value);
String toJson() => toJson$Enum$UserType(this);
}
String toJson$Enum$UserType(Enum$UserType e) {
switch (e) {
@ -1887,7 +1966,12 @@ enum Enum$__TypeKind {
INPUT_OBJECT,
LIST,
NON_NULL,
$unknown
$unknown;
factory Enum$__TypeKind.fromJson(String value) =>
fromJson$Enum$__TypeKind(value);
String toJson() => toJson$Enum$__TypeKind(this);
}
String toJson$Enum$__TypeKind(Enum$__TypeKind e) {
@ -1956,7 +2040,12 @@ enum Enum$__DirectiveLocation {
ENUM_VALUE,
INPUT_OBJECT,
INPUT_FIELD_DEFINITION,
$unknown
$unknown;
factory Enum$__DirectiveLocation.fromJson(String value) =>
fromJson$Enum$__DirectiveLocation(value);
String toJson() => toJson$Enum$__DirectiveLocation(this);
}
String toJson$Enum$__DirectiveLocation(Enum$__DirectiveLocation e) {

View file

@ -33,6 +33,12 @@ query GetApiJobs {
}
}
subscription JobUpdates {
jobUpdates {
...basicApiJobsFields
}
}
mutation RemoveJob($jobId: String!) {
jobs {
removeJob(jobId: $jobId) {
@ -79,6 +85,17 @@ mutation RunSystemUpgrade {
}
}
mutation NixCollectGarbage {
system {
nixCollectGarbage {
...basicMutationReturnFields
job {
...basicApiJobsFields
}
}
}
}
mutation RunSystemUpgradeFallback {
system {
runSystemUpgrade {

View file

@ -3831,6 +3831,273 @@ class _CopyWithStubImpl$Query$GetApiJobs$jobs<TRes>
getJobs(_fn) => _res;
}
class Subscription$JobUpdates {
Subscription$JobUpdates({
required this.jobUpdates,
this.$__typename = 'Subscription',
});
factory Subscription$JobUpdates.fromJson(Map<String, dynamic> json) {
final l$jobUpdates = json['jobUpdates'];
final l$$__typename = json['__typename'];
return Subscription$JobUpdates(
jobUpdates: (l$jobUpdates as List<dynamic>)
.map((e) =>
Fragment$basicApiJobsFields.fromJson((e as Map<String, dynamic>)))
.toList(),
$__typename: (l$$__typename as String),
);
}
final List<Fragment$basicApiJobsFields> jobUpdates;
final String $__typename;
Map<String, dynamic> toJson() {
final _resultData = <String, dynamic>{};
final l$jobUpdates = jobUpdates;
_resultData['jobUpdates'] = l$jobUpdates.map((e) => e.toJson()).toList();
final l$$__typename = $__typename;
_resultData['__typename'] = l$$__typename;
return _resultData;
}
@override
int get hashCode {
final l$jobUpdates = jobUpdates;
final l$$__typename = $__typename;
return Object.hashAll([
Object.hashAll(l$jobUpdates.map((v) => v)),
l$$__typename,
]);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (!(other is Subscription$JobUpdates) ||
runtimeType != other.runtimeType) {
return false;
}
final l$jobUpdates = jobUpdates;
final lOther$jobUpdates = other.jobUpdates;
if (l$jobUpdates.length != lOther$jobUpdates.length) {
return false;
}
for (int i = 0; i < l$jobUpdates.length; i++) {
final l$jobUpdates$entry = l$jobUpdates[i];
final lOther$jobUpdates$entry = lOther$jobUpdates[i];
if (l$jobUpdates$entry != lOther$jobUpdates$entry) {
return false;
}
}
final l$$__typename = $__typename;
final lOther$$__typename = other.$__typename;
if (l$$__typename != lOther$$__typename) {
return false;
}
return true;
}
}
extension UtilityExtension$Subscription$JobUpdates on Subscription$JobUpdates {
CopyWith$Subscription$JobUpdates<Subscription$JobUpdates> get copyWith =>
CopyWith$Subscription$JobUpdates(
this,
(i) => i,
);
}
abstract class CopyWith$Subscription$JobUpdates<TRes> {
factory CopyWith$Subscription$JobUpdates(
Subscription$JobUpdates instance,
TRes Function(Subscription$JobUpdates) then,
) = _CopyWithImpl$Subscription$JobUpdates;
factory CopyWith$Subscription$JobUpdates.stub(TRes res) =
_CopyWithStubImpl$Subscription$JobUpdates;
TRes call({
List<Fragment$basicApiJobsFields>? jobUpdates,
String? $__typename,
});
TRes jobUpdates(
Iterable<Fragment$basicApiJobsFields> Function(
Iterable<
CopyWith$Fragment$basicApiJobsFields<
Fragment$basicApiJobsFields>>)
_fn);
}
class _CopyWithImpl$Subscription$JobUpdates<TRes>
implements CopyWith$Subscription$JobUpdates<TRes> {
_CopyWithImpl$Subscription$JobUpdates(
this._instance,
this._then,
);
final Subscription$JobUpdates _instance;
final TRes Function(Subscription$JobUpdates) _then;
static const _undefined = <dynamic, dynamic>{};
TRes call({
Object? jobUpdates = _undefined,
Object? $__typename = _undefined,
}) =>
_then(Subscription$JobUpdates(
jobUpdates: jobUpdates == _undefined || jobUpdates == null
? _instance.jobUpdates
: (jobUpdates as List<Fragment$basicApiJobsFields>),
$__typename: $__typename == _undefined || $__typename == null
? _instance.$__typename
: ($__typename as String),
));
TRes jobUpdates(
Iterable<Fragment$basicApiJobsFields> Function(
Iterable<
CopyWith$Fragment$basicApiJobsFields<
Fragment$basicApiJobsFields>>)
_fn) =>
call(
jobUpdates: _fn(_instance.jobUpdates
.map((e) => CopyWith$Fragment$basicApiJobsFields(
e,
(i) => i,
))).toList());
}
class _CopyWithStubImpl$Subscription$JobUpdates<TRes>
implements CopyWith$Subscription$JobUpdates<TRes> {
_CopyWithStubImpl$Subscription$JobUpdates(this._res);
TRes _res;
call({
List<Fragment$basicApiJobsFields>? jobUpdates,
String? $__typename,
}) =>
_res;
jobUpdates(_fn) => _res;
}
const documentNodeSubscriptionJobUpdates = DocumentNode(definitions: [
OperationDefinitionNode(
type: OperationType.subscription,
name: NameNode(value: 'JobUpdates'),
variableDefinitions: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'jobUpdates'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FragmentSpreadNode(
name: NameNode(value: 'basicApiJobsFields'),
directives: [],
),
FieldNode(
name: NameNode(value: '__typename'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
]),
),
FieldNode(
name: NameNode(value: '__typename'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
]),
),
fragmentDefinitionbasicApiJobsFields,
]);
Subscription$JobUpdates _parserFn$Subscription$JobUpdates(
Map<String, dynamic> data) =>
Subscription$JobUpdates.fromJson(data);
class Options$Subscription$JobUpdates
extends graphql.SubscriptionOptions<Subscription$JobUpdates> {
Options$Subscription$JobUpdates({
String? operationName,
graphql.FetchPolicy? fetchPolicy,
graphql.ErrorPolicy? errorPolicy,
graphql.CacheRereadPolicy? cacheRereadPolicy,
Object? optimisticResult,
Subscription$JobUpdates? typedOptimisticResult,
graphql.Context? context,
}) : super(
operationName: operationName,
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
cacheRereadPolicy: cacheRereadPolicy,
optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
context: context,
document: documentNodeSubscriptionJobUpdates,
parserFn: _parserFn$Subscription$JobUpdates,
);
}
class WatchOptions$Subscription$JobUpdates
extends graphql.WatchQueryOptions<Subscription$JobUpdates> {
WatchOptions$Subscription$JobUpdates({
String? operationName,
graphql.FetchPolicy? fetchPolicy,
graphql.ErrorPolicy? errorPolicy,
graphql.CacheRereadPolicy? cacheRereadPolicy,
Object? optimisticResult,
Subscription$JobUpdates? typedOptimisticResult,
graphql.Context? context,
Duration? pollInterval,
bool? eagerlyFetchResults,
bool carryForwardDataOnException = true,
bool fetchResults = false,
}) : super(
operationName: operationName,
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
cacheRereadPolicy: cacheRereadPolicy,
optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
context: context,
document: documentNodeSubscriptionJobUpdates,
pollInterval: pollInterval,
eagerlyFetchResults: eagerlyFetchResults,
carryForwardDataOnException: carryForwardDataOnException,
fetchResults: fetchResults,
parserFn: _parserFn$Subscription$JobUpdates,
);
}
class FetchMoreOptions$Subscription$JobUpdates
extends graphql.FetchMoreOptions {
FetchMoreOptions$Subscription$JobUpdates(
{required graphql.UpdateQuery updateQuery})
: super(
updateQuery: updateQuery,
document: documentNodeSubscriptionJobUpdates,
);
}
extension ClientExtension$Subscription$JobUpdates on graphql.GraphQLClient {
Stream<graphql.QueryResult<Subscription$JobUpdates>> subscribe$JobUpdates(
[Options$Subscription$JobUpdates? options]) =>
this.subscribe(options ?? Options$Subscription$JobUpdates());
graphql.ObservableQuery<Subscription$JobUpdates> watchSubscription$JobUpdates(
[WatchOptions$Subscription$JobUpdates? options]) =>
this.watchQuery(options ?? WatchOptions$Subscription$JobUpdates());
}
class Variables$Mutation$RemoveJob {
factory Variables$Mutation$RemoveJob({required String jobId}) =>
Variables$Mutation$RemoveJob._({
@ -7043,6 +7310,663 @@ class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade<TRes>
CopyWith$Fragment$basicApiJobsFields.stub(_res);
}
class Mutation$NixCollectGarbage {
Mutation$NixCollectGarbage({
required this.system,
this.$__typename = 'Mutation',
});
factory Mutation$NixCollectGarbage.fromJson(Map<String, dynamic> json) {
final l$system = json['system'];
final l$$__typename = json['__typename'];
return Mutation$NixCollectGarbage(
system: Mutation$NixCollectGarbage$system.fromJson(
(l$system as Map<String, dynamic>)),
$__typename: (l$$__typename as String),
);
}
final Mutation$NixCollectGarbage$system system;
final String $__typename;
Map<String, dynamic> toJson() {
final _resultData = <String, dynamic>{};
final l$system = system;
_resultData['system'] = l$system.toJson();
final l$$__typename = $__typename;
_resultData['__typename'] = l$$__typename;
return _resultData;
}
@override
int get hashCode {
final l$system = system;
final l$$__typename = $__typename;
return Object.hashAll([
l$system,
l$$__typename,
]);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (!(other is Mutation$NixCollectGarbage) ||
runtimeType != other.runtimeType) {
return false;
}
final l$system = system;
final lOther$system = other.system;
if (l$system != lOther$system) {
return false;
}
final l$$__typename = $__typename;
final lOther$$__typename = other.$__typename;
if (l$$__typename != lOther$$__typename) {
return false;
}
return true;
}
}
extension UtilityExtension$Mutation$NixCollectGarbage
on Mutation$NixCollectGarbage {
CopyWith$Mutation$NixCollectGarbage<Mutation$NixCollectGarbage>
get copyWith => CopyWith$Mutation$NixCollectGarbage(
this,
(i) => i,
);
}
abstract class CopyWith$Mutation$NixCollectGarbage<TRes> {
factory CopyWith$Mutation$NixCollectGarbage(
Mutation$NixCollectGarbage instance,
TRes Function(Mutation$NixCollectGarbage) then,
) = _CopyWithImpl$Mutation$NixCollectGarbage;
factory CopyWith$Mutation$NixCollectGarbage.stub(TRes res) =
_CopyWithStubImpl$Mutation$NixCollectGarbage;
TRes call({
Mutation$NixCollectGarbage$system? system,
String? $__typename,
});
CopyWith$Mutation$NixCollectGarbage$system<TRes> get system;
}
class _CopyWithImpl$Mutation$NixCollectGarbage<TRes>
implements CopyWith$Mutation$NixCollectGarbage<TRes> {
_CopyWithImpl$Mutation$NixCollectGarbage(
this._instance,
this._then,
);
final Mutation$NixCollectGarbage _instance;
final TRes Function(Mutation$NixCollectGarbage) _then;
static const _undefined = <dynamic, dynamic>{};
TRes call({
Object? system = _undefined,
Object? $__typename = _undefined,
}) =>
_then(Mutation$NixCollectGarbage(
system: system == _undefined || system == null
? _instance.system
: (system as Mutation$NixCollectGarbage$system),
$__typename: $__typename == _undefined || $__typename == null
? _instance.$__typename
: ($__typename as String),
));
CopyWith$Mutation$NixCollectGarbage$system<TRes> get system {
final local$system = _instance.system;
return CopyWith$Mutation$NixCollectGarbage$system(
local$system, (e) => call(system: e));
}
}
class _CopyWithStubImpl$Mutation$NixCollectGarbage<TRes>
implements CopyWith$Mutation$NixCollectGarbage<TRes> {
_CopyWithStubImpl$Mutation$NixCollectGarbage(this._res);
TRes _res;
call({
Mutation$NixCollectGarbage$system? system,
String? $__typename,
}) =>
_res;
CopyWith$Mutation$NixCollectGarbage$system<TRes> get system =>
CopyWith$Mutation$NixCollectGarbage$system.stub(_res);
}
const documentNodeMutationNixCollectGarbage = DocumentNode(definitions: [
OperationDefinitionNode(
type: OperationType.mutation,
name: NameNode(value: 'NixCollectGarbage'),
variableDefinitions: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'system'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'nixCollectGarbage'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FragmentSpreadNode(
name: NameNode(value: 'basicMutationReturnFields'),
directives: [],
),
FieldNode(
name: NameNode(value: 'job'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FragmentSpreadNode(
name: NameNode(value: 'basicApiJobsFields'),
directives: [],
),
FieldNode(
name: NameNode(value: '__typename'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
]),
),
FieldNode(
name: NameNode(value: '__typename'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
]),
),
FieldNode(
name: NameNode(value: '__typename'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
]),
),
FieldNode(
name: NameNode(value: '__typename'),
alias: null,
arguments: [],
directives: [],
selectionSet: null,
),
]),
),
fragmentDefinitionbasicMutationReturnFields,
fragmentDefinitionbasicApiJobsFields,
]);
Mutation$NixCollectGarbage _parserFn$Mutation$NixCollectGarbage(
Map<String, dynamic> data) =>
Mutation$NixCollectGarbage.fromJson(data);
typedef OnMutationCompleted$Mutation$NixCollectGarbage = FutureOr<void>
Function(
Map<String, dynamic>?,
Mutation$NixCollectGarbage?,
);
class Options$Mutation$NixCollectGarbage
extends graphql.MutationOptions<Mutation$NixCollectGarbage> {
Options$Mutation$NixCollectGarbage({
String? operationName,
graphql.FetchPolicy? fetchPolicy,
graphql.ErrorPolicy? errorPolicy,
graphql.CacheRereadPolicy? cacheRereadPolicy,
Object? optimisticResult,
Mutation$NixCollectGarbage? typedOptimisticResult,
graphql.Context? context,
OnMutationCompleted$Mutation$NixCollectGarbage? onCompleted,
graphql.OnMutationUpdate<Mutation$NixCollectGarbage>? update,
graphql.OnError? onError,
}) : onCompletedWithParsed = onCompleted,
super(
operationName: operationName,
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
cacheRereadPolicy: cacheRereadPolicy,
optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
context: context,
onCompleted: onCompleted == null
? null
: (data) => onCompleted(
data,
data == null
? null
: _parserFn$Mutation$NixCollectGarbage(data),
),
update: update,
onError: onError,
document: documentNodeMutationNixCollectGarbage,
parserFn: _parserFn$Mutation$NixCollectGarbage,
);
final OnMutationCompleted$Mutation$NixCollectGarbage? onCompletedWithParsed;
@override
List<Object?> get properties => [
...super.onCompleted == null
? super.properties
: super.properties.where((property) => property != onCompleted),
onCompletedWithParsed,
];
}
class WatchOptions$Mutation$NixCollectGarbage
extends graphql.WatchQueryOptions<Mutation$NixCollectGarbage> {
WatchOptions$Mutation$NixCollectGarbage({
String? operationName,
graphql.FetchPolicy? fetchPolicy,
graphql.ErrorPolicy? errorPolicy,
graphql.CacheRereadPolicy? cacheRereadPolicy,
Object? optimisticResult,
Mutation$NixCollectGarbage? typedOptimisticResult,
graphql.Context? context,
Duration? pollInterval,
bool? eagerlyFetchResults,
bool carryForwardDataOnException = true,
bool fetchResults = false,
}) : super(
operationName: operationName,
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
cacheRereadPolicy: cacheRereadPolicy,
optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
context: context,
document: documentNodeMutationNixCollectGarbage,
pollInterval: pollInterval,
eagerlyFetchResults: eagerlyFetchResults,
carryForwardDataOnException: carryForwardDataOnException,
fetchResults: fetchResults,
parserFn: _parserFn$Mutation$NixCollectGarbage,
);
}
extension ClientExtension$Mutation$NixCollectGarbage on graphql.GraphQLClient {
Future<graphql.QueryResult<Mutation$NixCollectGarbage>>
mutate$NixCollectGarbage(
[Options$Mutation$NixCollectGarbage? options]) async =>
await this.mutate(options ?? Options$Mutation$NixCollectGarbage());
graphql.ObservableQuery<
Mutation$NixCollectGarbage> watchMutation$NixCollectGarbage(
[WatchOptions$Mutation$NixCollectGarbage? options]) =>
this.watchMutation(options ?? WatchOptions$Mutation$NixCollectGarbage());
}
class Mutation$NixCollectGarbage$system {
Mutation$NixCollectGarbage$system({
required this.nixCollectGarbage,
this.$__typename = 'SystemMutations',
});
factory Mutation$NixCollectGarbage$system.fromJson(
Map<String, dynamic> json) {
final l$nixCollectGarbage = json['nixCollectGarbage'];
final l$$__typename = json['__typename'];
return Mutation$NixCollectGarbage$system(
nixCollectGarbage:
Mutation$NixCollectGarbage$system$nixCollectGarbage.fromJson(
(l$nixCollectGarbage as Map<String, dynamic>)),
$__typename: (l$$__typename as String),
);
}
final Mutation$NixCollectGarbage$system$nixCollectGarbage nixCollectGarbage;
final String $__typename;
Map<String, dynamic> toJson() {
final _resultData = <String, dynamic>{};
final l$nixCollectGarbage = nixCollectGarbage;
_resultData['nixCollectGarbage'] = l$nixCollectGarbage.toJson();
final l$$__typename = $__typename;
_resultData['__typename'] = l$$__typename;
return _resultData;
}
@override
int get hashCode {
final l$nixCollectGarbage = nixCollectGarbage;
final l$$__typename = $__typename;
return Object.hashAll([
l$nixCollectGarbage,
l$$__typename,
]);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (!(other is Mutation$NixCollectGarbage$system) ||
runtimeType != other.runtimeType) {
return false;
}
final l$nixCollectGarbage = nixCollectGarbage;
final lOther$nixCollectGarbage = other.nixCollectGarbage;
if (l$nixCollectGarbage != lOther$nixCollectGarbage) {
return false;
}
final l$$__typename = $__typename;
final lOther$$__typename = other.$__typename;
if (l$$__typename != lOther$$__typename) {
return false;
}
return true;
}
}
extension UtilityExtension$Mutation$NixCollectGarbage$system
on Mutation$NixCollectGarbage$system {
CopyWith$Mutation$NixCollectGarbage$system<Mutation$NixCollectGarbage$system>
get copyWith => CopyWith$Mutation$NixCollectGarbage$system(
this,
(i) => i,
);
}
abstract class CopyWith$Mutation$NixCollectGarbage$system<TRes> {
factory CopyWith$Mutation$NixCollectGarbage$system(
Mutation$NixCollectGarbage$system instance,
TRes Function(Mutation$NixCollectGarbage$system) then,
) = _CopyWithImpl$Mutation$NixCollectGarbage$system;
factory CopyWith$Mutation$NixCollectGarbage$system.stub(TRes res) =
_CopyWithStubImpl$Mutation$NixCollectGarbage$system;
TRes call({
Mutation$NixCollectGarbage$system$nixCollectGarbage? nixCollectGarbage,
String? $__typename,
});
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
get nixCollectGarbage;
}
class _CopyWithImpl$Mutation$NixCollectGarbage$system<TRes>
implements CopyWith$Mutation$NixCollectGarbage$system<TRes> {
_CopyWithImpl$Mutation$NixCollectGarbage$system(
this._instance,
this._then,
);
final Mutation$NixCollectGarbage$system _instance;
final TRes Function(Mutation$NixCollectGarbage$system) _then;
static const _undefined = <dynamic, dynamic>{};
TRes call({
Object? nixCollectGarbage = _undefined,
Object? $__typename = _undefined,
}) =>
_then(Mutation$NixCollectGarbage$system(
nixCollectGarbage:
nixCollectGarbage == _undefined || nixCollectGarbage == null
? _instance.nixCollectGarbage
: (nixCollectGarbage
as Mutation$NixCollectGarbage$system$nixCollectGarbage),
$__typename: $__typename == _undefined || $__typename == null
? _instance.$__typename
: ($__typename as String),
));
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
get nixCollectGarbage {
final local$nixCollectGarbage = _instance.nixCollectGarbage;
return CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage(
local$nixCollectGarbage, (e) => call(nixCollectGarbage: e));
}
}
class _CopyWithStubImpl$Mutation$NixCollectGarbage$system<TRes>
implements CopyWith$Mutation$NixCollectGarbage$system<TRes> {
_CopyWithStubImpl$Mutation$NixCollectGarbage$system(this._res);
TRes _res;
call({
Mutation$NixCollectGarbage$system$nixCollectGarbage? nixCollectGarbage,
String? $__typename,
}) =>
_res;
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
get nixCollectGarbage =>
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage.stub(
_res);
}
class Mutation$NixCollectGarbage$system$nixCollectGarbage
implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn {
Mutation$NixCollectGarbage$system$nixCollectGarbage({
required this.code,
required this.message,
required this.success,
this.$__typename = 'GenericJobMutationReturn',
this.job,
});
factory Mutation$NixCollectGarbage$system$nixCollectGarbage.fromJson(
Map<String, dynamic> json) {
final l$code = json['code'];
final l$message = json['message'];
final l$success = json['success'];
final l$$__typename = json['__typename'];
final l$job = json['job'];
return Mutation$NixCollectGarbage$system$nixCollectGarbage(
code: (l$code as int),
message: (l$message as String),
success: (l$success as bool),
$__typename: (l$$__typename as String),
job: l$job == null
? null
: Fragment$basicApiJobsFields.fromJson(
(l$job as Map<String, dynamic>)),
);
}
final int code;
final String message;
final bool success;
final String $__typename;
final Fragment$basicApiJobsFields? job;
Map<String, dynamic> toJson() {
final _resultData = <String, dynamic>{};
final l$code = code;
_resultData['code'] = l$code;
final l$message = message;
_resultData['message'] = l$message;
final l$success = success;
_resultData['success'] = l$success;
final l$$__typename = $__typename;
_resultData['__typename'] = l$$__typename;
final l$job = job;
_resultData['job'] = l$job?.toJson();
return _resultData;
}
@override
int get hashCode {
final l$code = code;
final l$message = message;
final l$success = success;
final l$$__typename = $__typename;
final l$job = job;
return Object.hashAll([
l$code,
l$message,
l$success,
l$$__typename,
l$job,
]);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (!(other is Mutation$NixCollectGarbage$system$nixCollectGarbage) ||
runtimeType != other.runtimeType) {
return false;
}
final l$code = code;
final lOther$code = other.code;
if (l$code != lOther$code) {
return false;
}
final l$message = message;
final lOther$message = other.message;
if (l$message != lOther$message) {
return false;
}
final l$success = success;
final lOther$success = other.success;
if (l$success != lOther$success) {
return false;
}
final l$$__typename = $__typename;
final lOther$$__typename = other.$__typename;
if (l$$__typename != lOther$$__typename) {
return false;
}
final l$job = job;
final lOther$job = other.job;
if (l$job != lOther$job) {
return false;
}
return true;
}
}
extension UtilityExtension$Mutation$NixCollectGarbage$system$nixCollectGarbage
on Mutation$NixCollectGarbage$system$nixCollectGarbage {
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<
Mutation$NixCollectGarbage$system$nixCollectGarbage>
get copyWith =>
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage(
this,
(i) => i,
);
}
abstract class CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<
TRes> {
factory CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage(
Mutation$NixCollectGarbage$system$nixCollectGarbage instance,
TRes Function(Mutation$NixCollectGarbage$system$nixCollectGarbage) then,
) = _CopyWithImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage;
factory CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage.stub(
TRes res) =
_CopyWithStubImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage;
TRes call({
int? code,
String? message,
bool? success,
String? $__typename,
Fragment$basicApiJobsFields? job,
});
CopyWith$Fragment$basicApiJobsFields<TRes> get job;
}
class _CopyWithImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes>
implements
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes> {
_CopyWithImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage(
this._instance,
this._then,
);
final Mutation$NixCollectGarbage$system$nixCollectGarbage _instance;
final TRes Function(Mutation$NixCollectGarbage$system$nixCollectGarbage)
_then;
static const _undefined = <dynamic, dynamic>{};
TRes call({
Object? code = _undefined,
Object? message = _undefined,
Object? success = _undefined,
Object? $__typename = _undefined,
Object? job = _undefined,
}) =>
_then(Mutation$NixCollectGarbage$system$nixCollectGarbage(
code:
code == _undefined || code == null ? _instance.code : (code as int),
message: message == _undefined || message == null
? _instance.message
: (message as String),
success: success == _undefined || success == null
? _instance.success
: (success as bool),
$__typename: $__typename == _undefined || $__typename == null
? _instance.$__typename
: ($__typename as String),
job: job == _undefined
? _instance.job
: (job as Fragment$basicApiJobsFields?),
));
CopyWith$Fragment$basicApiJobsFields<TRes> get job {
final local$job = _instance.job;
return local$job == null
? CopyWith$Fragment$basicApiJobsFields.stub(_then(_instance))
: CopyWith$Fragment$basicApiJobsFields(local$job, (e) => call(job: e));
}
}
class _CopyWithStubImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage<
TRes>
implements
CopyWith$Mutation$NixCollectGarbage$system$nixCollectGarbage<TRes> {
_CopyWithStubImpl$Mutation$NixCollectGarbage$system$nixCollectGarbage(
this._res);
TRes _res;
call({
int? code,
String? message,
bool? success,
String? $__typename,
Fragment$basicApiJobsFields? job,
}) =>
_res;
CopyWith$Fragment$basicApiJobsFields<TRes> get job =>
CopyWith$Fragment$basicApiJobsFields.stub(_res);
}
class Mutation$RunSystemUpgradeFallback {
Mutation$RunSystemUpgradeFallback({
required this.system,

View file

@ -22,6 +22,18 @@ mixin JobsApi on GraphQLApiMap {
return jobsList;
}
Stream<List<ServerJob>> getServerJobsStream() async* {
final GraphQLClient client = await getSubscriptionClient();
final subscription = client.subscribe$JobUpdates();
await for (final response in subscription) {
final jobsList = response.parsedData?.jobUpdates
.map<ServerJob>((final job) => ServerJob.fromGraphQL(job))
.toList() ??
[];
yield jobsList;
}
}
Future<GenericResult<bool>> removeApiJob(final String uid) async {
try {
final GraphQLClient client = await getClient();

View file

@ -144,4 +144,38 @@ mixin ServerActionsApi on GraphQLApiMap {
);
}
}
Future<GenericResult<ServerJob?>> collectNixGarbage() async {
try {
final GraphQLClient client = await getClient();
final result = await client.mutate$NixCollectGarbage();
if (result.hasException) {
return GenericResult(
success: false,
data: null,
);
} else if (result.parsedData!.system.nixCollectGarbage.success &&
result.parsedData!.system.nixCollectGarbage.job != null) {
return GenericResult(
success: true,
data: ServerJob.fromGraphQL(
result.parsedData!.system.nixCollectGarbage.job!,
),
message: result.parsedData!.system.nixCollectGarbage.message,
);
} else {
return GenericResult(
success: false,
message: result.parsedData!.system.nixCollectGarbage.message,
data: null,
);
}
} catch (e) {
return GenericResult(
success: false,
message: e.toString(),
data: null,
);
}
}
}

View file

@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphq
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/users.graphql.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -57,7 +58,7 @@ class ServerApi extends GraphQLApiMap
String customToken;
@override
String? get rootAddress =>
overrideDomain ?? getIt<ApiConfigModel>().serverDomain?.domainName;
overrideDomain ?? getIt<ResourcesModel>().serverDomain?.domainName;
String? overrideDomain;
Future<String?> getApiVersion() async {

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
@ -39,7 +40,7 @@ class BackblazeApi extends RestApiMap {
);
if (isWithToken) {
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
getIt<ResourcesModel>().backblazeCredential;
final String token = backblazeCredential!.applicationKey;
options.headers = {'Authorization': 'Basic $token'};
}
@ -59,7 +60,7 @@ class BackblazeApi extends RestApiMap {
Future<BackblazeApiAuth> getAuthorizationToken() async {
final Dio client = await getClient();
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
getIt<ResourcesModel>().backblazeCredential;
if (backblazeCredential == null) {
throw Exception('Backblaze credential is null');
}
@ -124,7 +125,7 @@ class BackblazeApi extends RestApiMap {
Future<String> createBucket(final String bucketName) async {
final BackblazeApiAuth auth = await getAuthorizationToken();
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
getIt<ResourcesModel>().backblazeCredential;
final Dio client = await getClient();
client.options.baseUrl = auth.apiUrl;
final Response response = await client.post(
@ -161,7 +162,7 @@ class BackblazeApi extends RestApiMap {
final Response response = await client.post(
'$apiPrefix/b2_create_key',
data: {
'accountId': getIt<ApiConfigModel>().backblazeCredential!.keyId,
'accountId': getIt<ResourcesModel>().backblazeCredential!.keyId,
'bucketId': bucketId,
'capabilities': ['listBuckets', 'listFiles', 'readFiles', 'writeFiles'],
'keyName': 'selfprivacy-restricted-server-key',
@ -192,7 +193,7 @@ class BackblazeApi extends RestApiMap {
final Response response = await client.get(
'$apiPrefix/b2_list_buckets',
queryParameters: {
'accountId': getIt<ApiConfigModel>().backblazeCredential!.keyId,
'accountId': getIt<ResourcesModel>().backblazeCredential!.keyId,
},
options: Options(
headers: {'Authorization': auth.authorizationToken},

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart';
class CloudflareApi extends RestApiMap {
@ -27,7 +28,7 @@ class CloudflareApi extends RestApiMap {
responseType: ResponseType.json,
);
if (isWithToken) {
final String? token = getIt<ApiConfigModel>().dnsProviderKey;
final String? token = getIt<ResourcesModel>().dnsProviderKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/json/dns_providers/desec_dns_info.dart';
class DesecApi extends RestApiMap {
@ -27,7 +28,7 @@ class DesecApi extends RestApiMap {
responseType: ResponseType.json,
);
if (isWithToken) {
final String? token = getIt<ApiConfigModel>().dnsProviderKey;
final String? token = getIt<ResourcesModel>().dnsProviderKey;
assert(token != null);
options.headers = {'Authorization': 'Token $token'};
}

View file

@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_dns_info.dart';
class DigitalOceanDnsApi extends RestApiMap {
@ -27,7 +28,7 @@ class DigitalOceanDnsApi extends RestApiMap {
responseType: ResponseType.json,
);
if (isWithToken) {
final String? token = getIt<ApiConfigModel>().dnsProviderKey;
final String? token = getIt<ResourcesModel>().dnsProviderKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}

View file

@ -1,18 +1,25 @@
import 'dart:async';
import 'dart:developer';
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:selfprivacy/config/config.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/models/message.dart';
import 'package:selfprivacy/logic/models/console_log.dart';
import 'package:selfprivacy/utils/app_logger.dart';
abstract class RestApiMap {
static final log = const AppLogger(name: 'rest_api_map').log;
Future<Dio> getClient({final BaseOptions? customOptions}) async {
final Dio dio = Dio(customOptions ?? (await options));
if (hasLogger) {
dio.interceptors.add(PrettyDioLogger());
if (hasLogger && config.shouldDebugPrint) {
dio.interceptors.add(
PrettyDioLogger(logPrint: (final object) => debugPrint('$object')),
);
}
dio.interceptors.add(ConsoleInterceptor());
dio.httpClientAdapter = IOHttpClientAdapter(
@ -27,14 +34,13 @@ abstract class RestApiMap {
dio.interceptors.add(
InterceptorsWrapper(
onError: (final DioException e, final ErrorInterceptorHandler handler) {
print(e.requestOptions.path);
print(e.requestOptions.data);
onError: (
final DioException exception,
final ErrorInterceptorHandler handler,
) {
log('got dio exception:', error: exception);
print(e.message);
print(e.response);
return handler.next(e);
return handler.next(exception);
},
),
);
@ -57,8 +63,8 @@ abstract class RestApiMap {
}
class ConsoleInterceptor extends InterceptorsWrapper {
void addMessage(final Message message) {
getIt.get<ConsoleModel>().addMessage(message);
void addConsoleLog(final ConsoleLog message) {
getIt.get<ConsoleModel>().log(message);
}
@override
@ -66,12 +72,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
final RequestOptions options,
final RequestInterceptorHandler handler,
) async {
addMessage(
RestApiRequestMessage(
method: options.method,
data: options.data.toString(),
headers: options.headers,
addConsoleLog(
RestApiRequestConsoleLog(
uri: options.uri,
method: options.method,
headers: options.headers,
data: jsonEncode(options.data),
),
);
return super.onRequest(options, handler);
@ -82,12 +88,12 @@ class ConsoleInterceptor extends InterceptorsWrapper {
final Response response,
final ResponseInterceptorHandler handler,
) async {
addMessage(
RestApiResponseMessage(
addConsoleLog(
RestApiResponseConsoleLog(
uri: response.realUri,
method: response.requestOptions.method,
statusCode: response.statusCode,
data: response.data.toString(),
uri: response.realUri,
data: jsonEncode(response.data),
),
);
return super.onResponse(
@ -102,11 +108,13 @@ class ConsoleInterceptor extends InterceptorsWrapper {
final ErrorInterceptorHandler handler,
) async {
final Response? response = err.response;
log(err.toString());
addMessage(
Message.warn(
text:
'response-uri: ${response?.realUri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n',
addConsoleLog(
ManualConsoleLog.warning(
customTitle: 'RestAPI error',
content: '"uri": "${response?.realUri}",\n'
'"status_code": ${response?.statusCode},\n'
'"response": ${jsonEncode(response)}',
),
);
return super.onError(err, handler);

View file

@ -5,6 +5,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/json/digital_ocean_server_info.dart';
import 'package:selfprivacy/utils/password_generator.dart';
@ -30,7 +31,7 @@ class DigitalOceanApi extends RestApiMap {
responseType: ResponseType.json,
);
if (isWithToken) {
final String? token = getIt<ApiConfigModel>().serverProviderKey;
final String? token = getIt<ResourcesModel>().serverProviderKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}

View file

@ -5,6 +5,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/disk_size.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
@ -31,7 +32,7 @@ class HetznerApi extends RestApiMap {
responseType: ResponseType.json,
);
if (isWithToken) {
final String? token = getIt<ApiConfigModel>().serverProviderKey;
final String? token = getIt<ResourcesModel>().serverProviderKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}

View file

@ -6,6 +6,7 @@ import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
@ -108,7 +109,7 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
final BackupsServerLoaded event,
final Emitter<BackupsState> emit,
) async {
BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
BackblazeBucket? bucket = getIt<ResourcesModel>().backblazeBucket;
final backups = getIt<ApiConnectionRepository>().apiData.backups;
final backupConfig = getIt<ApiConnectionRepository>().apiData.backupConfig;
if (backupConfig.data == null || backups.data == null) {
@ -227,7 +228,7 @@ class BackupsBloc extends Bloc<BackupsEvent, BackupsState> {
emit(BackupsUnititialized());
return;
}
final BackblazeBucket? bucket = getIt<ApiConfigModel>().backblazeBucket;
final BackblazeBucket? bucket = getIt<ResourcesModel>().backblazeBucket;
emit(
BackupsInitialized(
backblazeBucket: bucket,

View file

@ -1,39 +0,0 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/get_it_config.dart';
part 'connection_status_event.dart';
part 'connection_status_state.dart';
class ConnectionStatusBloc
extends Bloc<ConnectionStatusEvent, ConnectionStatusState> {
ConnectionStatusBloc()
: super(
const ConnectionStatusState(
connectionStatus: ConnectionStatus.nonexistent,
),
) {
on<ConnectionStatusChanged>((final event, final emit) {
emit(ConnectionStatusState(connectionStatus: event.connectionStatus));
});
final apiConnectionRepository = getIt<ApiConnectionRepository>();
_apiConnectionStatusSubscription =
apiConnectionRepository.connectionStatusStream.listen(
(final ConnectionStatus connectionStatus) {
add(
ConnectionStatusChanged(connectionStatus),
);
},
);
}
StreamSubscription? _apiConnectionStatusSubscription;
@override
Future<void> close() {
_apiConnectionStatusSubscription?.cancel();
return super.close();
}
}

View file

@ -1,14 +0,0 @@
part of 'connection_status_bloc.dart';
sealed class ConnectionStatusEvent extends Equatable {
const ConnectionStatusEvent();
}
class ConnectionStatusChanged extends ConnectionStatusEvent {
const ConnectionStatusChanged(this.connectionStatus);
final ConnectionStatus connectionStatus;
@override
List<Object?> get props => [connectionStatus];
}

View file

@ -1,12 +0,0 @@
part of 'connection_status_bloc.dart';
class ConnectionStatusState extends Equatable {
const ConnectionStatusState({
required this.connectionStatus,
});
final ConnectionStatus connectionStatus;
@override
List<Object> get props => [connectionStatus];
}

View file

@ -0,0 +1,27 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/get_it_config.dart';
/// basically, a bus for other blocs to listen to server status updates
class ConnectionStatusBloc extends Bloc<ConnectionStatus, ConnectionStatus> {
ConnectionStatusBloc() : super(ConnectionStatus.nonexistent) {
on<ConnectionStatus>(
(final newStatus, final emit) => emit(newStatus),
);
final apiConnectionRepository = getIt<ApiConnectionRepository>();
_apiConnectionStatusSubscription =
apiConnectionRepository.connectionStatusStream.listen(
(final ConnectionStatus newStatus) => add(newStatus),
);
}
StreamSubscription? _apiConnectionStatusSubscription;
@override
Future<void> close() {
_apiConnectionStatusSubscription?.cancel();
return super.close();
}
}

View file

@ -1,66 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import 'package:material_color_utilities/material_color_utilities.dart'
as color_utils;
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
export 'package:provider/provider.dart';
part 'app_settings_state.dart';
class AppSettingsCubit extends Cubit<AppSettingsState> {
AppSettingsCubit({
required final bool isDarkModeOn,
required final bool isAutoDarkModeOn,
required final bool isOnboardingShowing,
}) : super(
AppSettingsState(
isDarkModeOn: isDarkModeOn,
isAutoDarkModeOn: isAutoDarkModeOn,
isOnboardingShowing: isOnboardingShowing,
),
);
Box box = Hive.box(BNames.appSettingsBox);
void load() async {
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
final bool? isAutoDarkModeOn = box.get(BNames.isAutoDarkModeOn);
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
emit(
state.copyWith(
isDarkModeOn: isDarkModeOn,
isAutoDarkModeOn: isAutoDarkModeOn,
isOnboardingShowing: isOnboardingShowing,
),
);
WidgetsFlutterBinding.ensureInitialized();
final color_utils.CorePalette? colorPalette =
await AppThemeFactory.getCorePalette();
emit(
state.copyWith(
corePalette: colorPalette,
),
);
}
void updateDarkMode({required final bool isDarkModeOn}) {
box.put(BNames.isDarkModeOn, isDarkModeOn);
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
}
void updateAutoDarkMode({required final bool isAutoDarkModeOn}) {
box.put(BNames.isAutoDarkModeOn, isAutoDarkModeOn);
emit(state.copyWith(isAutoDarkModeOn: isAutoDarkModeOn));
}
void turnOffOnboarding({final bool isOnboardingShowing = false}) {
box.put(BNames.isOnboardingShowing, isOnboardingShowing);
emit(state.copyWith(isOnboardingShowing: isOnboardingShowing));
}
}

View file

@ -1,35 +0,0 @@
part of 'app_settings_cubit.dart';
class AppSettingsState extends Equatable {
const AppSettingsState({
required this.isDarkModeOn,
required this.isAutoDarkModeOn,
required this.isOnboardingShowing,
this.corePalette,
});
final bool isDarkModeOn;
final bool isAutoDarkModeOn;
final bool isOnboardingShowing;
final color_utils.CorePalette? corePalette;
AppSettingsState copyWith({
final bool? isDarkModeOn,
final bool? isAutoDarkModeOn,
final bool? isOnboardingShowing,
final color_utils.CorePalette? corePalette,
}) =>
AppSettingsState(
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
isAutoDarkModeOn: isAutoDarkModeOn ?? this.isAutoDarkModeOn,
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
corePalette: corePalette ?? this.corePalette,
);
color_utils.CorePalette get corePaletteOrDefault =>
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
@override
List<dynamic> get props =>
[isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette];
}

View file

@ -178,6 +178,45 @@ class JobsCubit extends Cubit<JobsState> {
}
}
Future<void> collectNixGarbage() async {
if (state is JobsStateEmpty) {
emit(
JobsStateLoading(
[CollectNixGarbageJob(status: JobStatusEnum.running)],
null,
const [],
),
);
final result =
await getIt<ApiConnectionRepository>().api.collectNixGarbage();
if (result.success && result.data != null) {
emit(
JobsStateLoading(
[CollectNixGarbageJob(status: JobStatusEnum.finished)],
result.data!.uid,
const [],
),
);
} else if (result.success) {
emit(
JobsStateFinished(
[CollectNixGarbageJob(status: JobStatusEnum.finished)],
null,
const [],
),
);
} else {
emit(
JobsStateFinished(
[CollectNixGarbageJob(status: JobStatusEnum.error)],
null,
const [],
),
);
}
}
}
Future<void> acknowledgeFinished() async {
if (state is! JobsStateFinished) {
return;

View file

@ -2,6 +2,7 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/providers/providers_controller.dart';
class MetricsLoadException implements Exception {
@ -30,7 +31,7 @@ class MetricsRepository {
break;
}
final serverId = getIt<ApiConfigModel>().serverDetails!.id;
final serverId = getIt<ResourcesModel>().serverDetails!.id;
final result = await ProvidersController.currentServerProvider!.getMetrics(
serverId,
start,

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/server_metadata.dart';
import 'package:selfprivacy/logic/models/ssh_settings.dart';
@ -43,7 +44,7 @@ class ServerDetailsCubit
final serverProviderApi = ProvidersController.currentServerProvider;
final dnsProviderApi = ProvidersController.currentDnsProvider;
if (serverProviderApi != null && dnsProviderApi != null) {
final serverId = getIt<ApiConfigModel>().serverDetails?.id ?? 0;
final serverId = getIt<ResourcesModel>().serverDetails?.id ?? 0;
final metadataResult = await serverProviderApi.getMetadata(serverId);
metadataResult.data.add(
ServerMetadataEntity(
@ -60,7 +61,7 @@ class ServerDetailsCubit
}
void check() async {
final bool isReadyToCheck = getIt<ApiConfigModel>().serverDetails != null;
final bool isReadyToCheck = getIt<ResourcesModel>().serverDetails != null;
try {
if (isReadyToCheck) {
emit(const ServerDetailsLoading());

View file

@ -15,6 +15,7 @@ import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
import 'package:selfprivacy/logic/models/launch_installation_data.dart';
import 'package:selfprivacy/logic/models/price.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart';
@ -222,12 +223,14 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
provider: BackupsProviderType.backblaze,
);
final BackblazeBucket? bucket;
await repository.saveBackblazeKey(backblazeCredential);
await repository.saveBackupsCredential(backblazeCredential);
if (state is ServerInstallationRecovery) {
final configuration = await ServerApi(
customToken:
(state as ServerInstallationRecovery).serverDetails!.apiToken,
isWithToken: true,
overrideDomain:
(state as ServerInstallationRecovery).serverDomain!.domainName,
).getBackupsConfiguration();
if (configuration != null) {
try {
@ -401,7 +404,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
);
timer = Timer(pauseDuration, () async {
final ServerHostingDetails serverDetails = await repository.restart();
await repository.saveIsServerResetedFirstTime(true);
await repository.saveIsServerRebootedFirstTime(true);
await repository.saveServerDetails(serverDetails);
final ServerInstallationNotFinished newState = dataState.copyWith(
@ -442,7 +445,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
);
timer = Timer(pauseDuration, () async {
final ServerHostingDetails serverDetails = await repository.restart();
await repository.saveIsServerResetedSecondTime(true);
await repository.saveIsServerRebootedSecondTime(true);
await repository.saveServerDetails(serverDetails);
final ServerInstallationNotFinished newState = dataState.copyWith(
@ -577,10 +580,12 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final ServerProviderType serverProvider = await ServerApi(
customToken: serverDetails.apiToken,
isWithToken: true,
overrideDomain: serverDomain.domainName,
).getServerProviderType();
final dnsProvider = await ServerApi(
customToken: serverDetails.apiToken,
isWithToken: true,
overrideDomain: serverDomain.domainName,
).getDnsProviderType();
if (serverProvider == ServerProviderType.unknown ||
dnsProvider == DnsProviderType.unknown) {
@ -762,6 +767,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final dnsProviderType = await ServerApi(
customToken: dataState.serverDetails!.apiToken,
isWithToken: true,
overrideDomain: serverDomain.domainName,
).getDnsProviderType();
await repository.saveDomain(
ServerDomain(
@ -769,6 +775,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
provider: dnsProviderType,
),
);
await repository.setDnsApiToken(token);
emit(
dataState.copyWith(
serverDomain: ServerDomain(
@ -785,21 +792,18 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final BackupsCredential backblazeCredential,
) async {
await repository.saveIsServerStarted(true);
await repository.saveIsServerResetedFirstTime(true);
await repository.saveIsServerResetedSecondTime(true);
await repository.saveHasFinalChecked(true);
await repository.saveIsServerRebootedFirstTime(true);
await repository.saveIsServerRebootedSecondTime(true);
await repository.saveIsRecoveringServer(false);
final serverType = await ProvidersController.currentServerProvider!
.getServerType(state.serverDetails!.id);
await repository.saveServerType(serverType.data!);
await ProvidersController.currentServerProvider!
.trySetServerLocation(serverType.data!.location.identifier);
final User mainUser = await repository.getMainUser();
await repository.saveRootUser(mainUser);
await repository.saveHasFinalChecked(true);
final ServerInstallationRecovery updatedState =
(state as ServerInstallationRecovery).copyWith(
backblazeCredential: backblazeCredential,
rootUser: mainUser,
serverTypeIdentificator: serverType.data!.identifier,
);
emit(updatedState.finish());

View file

@ -9,10 +9,15 @@ import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/dns_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/server.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
import 'package:selfprivacy/logic/models/json/device_token.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart';
@ -34,22 +39,25 @@ class ServerAuthorizationException implements Exception {
class ServerInstallationRepository {
Box box = Hive.box(BNames.serverInstallationBox);
Box<User> usersBox = Hive.box(BNames.usersBox);
Future<ServerInstallationState> load() async {
final String? providerApiToken = getIt<ApiConfigModel>().serverProviderKey;
final String? location = getIt<ApiConfigModel>().serverLocation;
final String? dnsApiToken = getIt<ApiConfigModel>().dnsProviderKey;
final String? serverTypeIdentificator = getIt<ApiConfigModel>().serverType;
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
final DnsProviderType? dnsProvider = getIt<ApiConfigModel>().dnsProvider;
final ServerInstallationWizardData? wizardData =
getIt<WizardDataModel>().serverInstallation;
final List<Server> servers = getIt<ResourcesModel>().servers;
final String? providerApiToken = getIt<ResourcesModel>().serverProviderKey;
final String? location = getIt<ResourcesModel>().serverLocation;
final String? dnsApiToken = getIt<ResourcesModel>().dnsProviderKey;
final String? serverTypeIdentificator = getIt<ResourcesModel>().serverType;
final ServerDomain? serverDomain = getIt<ResourcesModel>().serverDomain;
final DnsProviderType? dnsProvider = getIt<ResourcesModel>().dnsProvider;
final ServerProviderType? serverProvider =
getIt<ApiConfigModel>().serverProvider;
getIt<ResourcesModel>().serverProvider;
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
getIt<ResourcesModel>().backblazeCredential;
final ServerHostingDetails? serverDetails =
getIt<ApiConfigModel>().serverDetails;
getIt<ResourcesModel>().serverDetails;
// TODO: Init server providers in another place
if (serverProvider != null ||
(serverDetails != null &&
serverDetails.provider != ServerProviderType.unknown)) {
@ -73,85 +81,48 @@ class ServerInstallationRepository {
);
}
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
TlsOptions.verifyCertificate = true;
if (serverTypeIdentificator == null && serverDetails != null) {
final finalServerType = await ProvidersController.currentServerProvider!
.getServerType(serverDetails.id);
await saveServerType(finalServerType.data!);
await ProvidersController.currentServerProvider!
.trySetServerLocation(finalServerType.data!.location.identifier);
return ServerInstallationFinished(
installationDialoguePopUp: null,
providerApiToken: providerApiToken!,
serverTypeIdentificator: finalServerType.data!.identifier,
dnsApiToken: dnsApiToken!,
serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!,
serverDetails: serverDetails,
rootUser: box.get(BNames.rootUser),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isServerResetedFirstTime:
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
isServerResetedSecondTime:
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
// If we don't have any wizard data, we either have a server set up, or we are starting from scratch
// This behaviour shall change when we introduce multitenancy
if (wizardData == null) {
if (servers.isEmpty) {
// We don't have anything set up, so we start from scratch
return ServerInstallationNotFinished.fromWizardData(
ServerInstallationWizardData.empty(),
);
} else {
// We have a server set up, so we load it
TlsOptions.verifyCertificate = true;
return ServerInstallationFinished(
installationDialoguePopUp: null,
providerApiToken: providerApiToken!,
serverTypeIdentificator: serverTypeIdentificator!,
dnsApiToken: dnsApiToken!,
serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!,
serverDetails: serverDetails!,
rootUser: box.get(BNames.rootUser),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isServerResetedFirstTime:
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
isServerResetedSecondTime:
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
);
}
}
if (box.get(BNames.isRecoveringServer, defaultValue: false) &&
serverDomain != null) {
if (wizardData.isRecoveringServer && wizardData.serverDomain != null) {
return ServerInstallationRecovery(
providerApiToken: providerApiToken,
dnsApiToken: dnsApiToken,
serverDomain: serverDomain,
serverTypeIdentificator: serverTypeIdentificator,
backblazeCredential: backblazeCredential,
serverDetails: serverDetails,
rootUser: box.get(BNames.rootUser),
providerApiToken: wizardData.serverProviderKey,
dnsApiToken: wizardData.dnsProviderKey,
serverDomain: wizardData.serverDomain,
serverTypeIdentificator: wizardData.serverTypeIdentifier,
backblazeCredential: wizardData.backupsCredential,
serverDetails: wizardData.serverDetails,
currentStep: _getCurrentRecoveryStep(
providerApiToken,
dnsApiToken,
serverDomain,
serverDetails,
wizardData.serverProviderKey,
wizardData.dnsProviderKey,
wizardData.serverDomain!,
wizardData.serverDetails,
),
recoveryCapabilities: await getRecoveryCapabilities(serverDomain),
recoveryCapabilities:
await getRecoveryCapabilities(wizardData.serverDomain!),
);
}
return ServerInstallationNotFinished(
providerApiToken: providerApiToken,
dnsApiToken: dnsApiToken,
serverDomain: serverDomain,
serverTypeIdentificator: serverTypeIdentificator,
backblazeCredential: backblazeCredential,
serverDetails: serverDetails,
rootUser: box.get(BNames.rootUser),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isServerResetedFirstTime:
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
isServerResetedSecondTime:
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
isLoading: box.get(BNames.isLoading, defaultValue: false),
dnsMatches: null,
customSshKey: null,
);
return ServerInstallationNotFinished.fromWizardData(wizardData);
}
RecoveryStep _getCurrentRecoveryStep(
@ -177,7 +148,9 @@ class ServerInstallationRepository {
void clearAppConfig() {
box.clear();
usersBox.clear();
getIt<ResourcesModel>().clear();
getIt<WizardDataModel>().clear();
getIt<ApiConnectionRepository>().clear();
}
Future<ServerHostingDetails> startServer(
@ -209,6 +182,9 @@ class ServerInstallationRepository {
if (!domainResult.success || domainResult.data.isEmpty) {
return false;
}
await getIt<ResourcesModel>().removeDnsProviderToken(
getIt<ResourcesModel>().dnsProviderCredentials.first,
);
return domainResult.data.any(
(final serverDomain) => serverDomain.domainName == domain,
@ -216,7 +192,12 @@ class ServerInstallationRepository {
}
Future<void> createDkimRecord(final ServerDomain domain) async {
final ServerApi api = ServerApi();
final ServerApi api = ServerApi(
overrideDomain: domain.domainName,
customToken:
getIt<WizardDataModel>().serverInstallation!.serverDetails!.apiToken,
isWithToken: true,
);
late DnsRecord record;
try {
@ -233,14 +214,26 @@ class ServerInstallationRepository {
}
Future<bool> isHttpServerWorking() async {
final ServerApi api = ServerApi();
final ServerApi api = ServerApi(
overrideDomain:
getIt<WizardDataModel>().serverInstallation!.serverDomain!.domainName,
customToken:
getIt<WizardDataModel>().serverInstallation!.serverDetails!.apiToken,
isWithToken: true,
);
return api.isHttpServerWorking();
}
Future<ServerHostingDetails> restart() async {
final server = getIt<ApiConfigModel>().serverDetails!;
final server = getIt<WizardDataModel>().serverInstallation!.serverDetails!;
final result = await ServerApi().reboot();
final result = await ServerApi(
overrideDomain:
getIt<WizardDataModel>().serverInstallation!.serverDomain!.domainName,
customToken:
getIt<WizardDataModel>().serverInstallation!.serverDetails!.apiToken,
isWithToken: true,
).reboot();
if (result.success && result.data != null) {
server.copyWith(startTime: result.data);
@ -252,7 +245,7 @@ class ServerInstallationRepository {
}
Future<ServerHostingDetails> powerOn() async {
final server = getIt<ApiConfigModel>().serverDetails!;
final server = getIt<ResourcesModel>().serverDetails!;
return startServer(server);
}
@ -436,174 +429,119 @@ class ServerInstallationRepository {
);
}
Future<User> getMainUser() async {
final ServerApi serverApi = ServerApi();
const User fallbackUser = User(
isFoundOnServer: false,
type: UserType.primary,
note: "Couldn't find main user on server, API is outdated",
login: 'UNKNOWN',
sshKeys: [],
);
final String? serverApiVersion = await serverApi.getApiVersion();
final users = await serverApi.getAllUsers();
if (serverApiVersion == null || users.isEmpty) {
return fallbackUser;
}
try {
final Version parsedVersion = Version.parse(serverApiVersion);
if (!VersionConstraint.parse('>=1.2.5').allows(parsedVersion)) {
return fallbackUser;
}
return users.firstWhere(
(final User user) => user.type == UserType.primary,
);
} on FormatException {
return fallbackUser;
}
}
Future<List<ServerBasicInfo>> getServersOnProviderAccount() async =>
(await ProvidersController.currentServerProvider!.getServers()).data;
Future<void> saveServerDetails(
final ServerHostingDetails serverDetails,
) async {
await getIt<ApiConfigModel>().setServerDetails(serverDetails);
await getIt<WizardDataModel>().setServerDetails(serverDetails);
}
Future<void> deleteServerDetails() async {
await box.delete(BNames.serverDetails);
getIt<ApiConfigModel>().init();
await getIt<WizardDataModel>().deleteServerDetails();
}
Future<void> saveServerProviderType(final ServerProviderType type) async {
await getIt<ApiConfigModel>().storeServerProviderType(type);
await getIt<WizardDataModel>().setServerProviderType(type);
}
Future<void> saveDnsProviderType(final DnsProviderType type) async {
await getIt<ApiConfigModel>().setDnsProviderType(type);
await getIt<WizardDataModel>().setDnsProviderType(type);
}
Future<void> saveServerProviderKey(final String key) async {
await getIt<ApiConfigModel>().setServerProviderKey(key);
await getIt<WizardDataModel>().setServerProviderKey(key);
await getIt<ResourcesModel>().addServerProviderToken(
ServerProviderCredential(
tokenId: null,
token: key,
provider:
getIt<WizardDataModel>().serverInstallation!.serverProviderType!,
associatedServerIds: [],
),
);
}
Future<void> saveServerType(final ServerType serverType) async {
await getIt<ApiConfigModel>().setServerTypeIdentifier(
await getIt<WizardDataModel>().setServerTypeIdentifier(
serverType.identifier,
);
await getIt<ApiConfigModel>().setServerLocation(
await getIt<WizardDataModel>().setServerLocation(
serverType.location.identifier,
);
}
Future<void> deleteServerProviderKey() async {
await box.delete(BNames.hetznerKey);
getIt<ApiConfigModel>().init();
}
Future<void> saveBackblazeKey(
final BackupsCredential backblazeCredential,
Future<void> saveBackupsCredential(
final BackupsCredential backupsCredential,
) async {
await getIt<ApiConfigModel>().setBackblazeCredential(backblazeCredential);
}
Future<void> deleteBackblazeKey() async {
await box.delete(BNames.backblazeCredential);
getIt<ApiConfigModel>().init();
await getIt<WizardDataModel>().setBackupsCredential(backupsCredential);
}
Future<void> setDnsApiToken(final String key) async {
await getIt<ApiConfigModel>().setDnsProviderKey(key);
}
Future<void> deleteDnsProviderKey() async {
await box.delete(BNames.cloudFlareKey);
getIt<ApiConfigModel>().init();
await getIt<WizardDataModel>().setDnsProviderKey(key);
await getIt<ResourcesModel>().addDnsProviderToken(
DnsProviderCredential(
tokenId: null,
token: key,
provider: getIt<WizardDataModel>().serverInstallation!.dnsProviderType!,
associatedDomainNames: [],
),
);
}
Future<void> saveDomain(final ServerDomain serverDomain) async {
await getIt<ApiConfigModel>().setServerDomain(serverDomain);
await getIt<WizardDataModel>().setServerDomain(serverDomain);
}
Future<void> deleteDomain() async {
await box.delete(BNames.serverDomain);
getIt<ApiConfigModel>().init();
await getIt<WizardDataModel>().deleteServerDomain();
}
Future<void> saveIsServerStarted(final bool value) async {
await box.put(BNames.isServerStarted, value);
await getIt<WizardDataModel>().setIsServerStarted(value);
}
Future<void> saveIsServerResetedFirstTime(final bool value) async {
await box.put(BNames.isServerResetedFirstTime, value);
Future<void> saveIsServerRebootedFirstTime(final bool value) async {
await getIt<WizardDataModel>().setIsServerRebootedFirstTime(value);
}
Future<void> saveIsServerResetedSecondTime(final bool value) async {
await box.put(BNames.isServerResetedSecondTime, value);
Future<void> saveIsServerRebootedSecondTime(final bool value) async {
await getIt<WizardDataModel>().setIsServerRebootedSecondTime(value);
}
Future<void> saveRootUser(final User rootUser) async {
await box.put(BNames.rootUser, rootUser);
await getIt<WizardDataModel>().setRootUser(rootUser);
}
Future<void> saveIsRecoveringServer(final bool value) async {
await box.put(BNames.isRecoveringServer, value);
await getIt<WizardDataModel>().setIsRecoveringServer(value);
}
Future<void> saveHasFinalChecked(final bool value) async {
await box.put(BNames.hasFinalChecked, value);
}
Future<bool> deleteServer(final ServerDomain serverDomain) async {
final ServerApi api = ServerApi();
final dnsRecords = await api.getDnsRecords();
final GenericResult<void> removalResult =
await ProvidersController.currentDnsProvider!.removeDomainRecords(
domain: serverDomain,
records: dnsRecords,
// We are finished here. Time to save the state and finish the wizard
// TODO: A lot of null checks are skipped here. Implication that every value exists might become false in the future.
// TODO: We would actually want to handle token creation elsewhere.
await getIt<WizardDataModel>().moveServerTypeToServerDetails();
final ServerInstallationWizardData wizardData =
getIt<WizardDataModel>().serverInstallation!;
await getIt<ResourcesModel>().addServer(
Server(
hostingDetails: wizardData.serverDetails!,
domain: wizardData.serverDomain!,
),
);
if (!removalResult.success) {
getIt<NavigationService>().showSnackBar(
'modals.dns_removal_error'.tr(),
);
return false;
}
final deletionResult =
await ProvidersController.currentServerProvider!.deleteServer(
serverDomain.domainName,
await getIt<ResourcesModel>().associateServerWithToken(
wizardData.serverDetails!.id,
wizardData.serverProviderKey!,
);
if (!deletionResult.success) {
getIt<NavigationService>().showSnackBar(
'modals.server_validators_error'.tr(),
);
return false;
}
await box.put(BNames.hasFinalChecked, false);
await box.put(BNames.isServerStarted, false);
await box.put(BNames.isServerResetedFirstTime, false);
await box.put(BNames.isServerResetedSecondTime, false);
await box.put(BNames.isLoading, false);
await box.put(BNames.serverDetails, null);
return true;
}
Future<void> deleteServerRelatedRecords() async {
await box.deleteAll([
BNames.serverDetails,
BNames.isServerStarted,
BNames.isServerResetedFirstTime,
BNames.isServerResetedSecondTime,
BNames.hasFinalChecked,
BNames.isLoading,
]);
getIt<ApiConfigModel>().init();
await getIt<ResourcesModel>().associateDomainWithToken(
wizardData.serverDomain!.domainName,
wizardData.dnsProviderKey!,
);
await getIt<ResourcesModel>().addBackupsCredential(
wizardData.backupsCredential!,
);
await getIt<WizardDataModel>().clearServerInstallation();
}
}

View file

@ -49,11 +49,12 @@ abstract class ServerInstallationState extends Equatable {
bool get isPrimaryUserFilled => rootUser != null;
bool get isServerCreated => serverDetails != null;
bool get isFullyInitilized => _fulfilementList.every((final el) => el!);
bool get isFullyInitialized =>
_fulfillmentList.every((final el) => el ?? false);
ServerSetupProgress get progress => ServerSetupProgress
.values[_fulfilementList.where((final el) => el!).length];
.values[_fulfillmentList.where((final el) => el!).length];
int get porgressBar {
int get progressBar {
if (progress.index < 6) {
return progress.index;
} else if (progress.index < 10) {
@ -63,7 +64,7 @@ abstract class ServerInstallationState extends Equatable {
}
}
List<bool?> get _fulfilementList {
List<bool?> get _fulfillmentList {
final List<bool> res = [
isServerProviderApiKeyFilled,
isServerTypeFilled,
@ -118,7 +119,7 @@ class TimerState extends ServerInstallationNotFinished {
enum ServerSetupProgress {
nothingYet,
serverProviderFilled,
servertTypeFilled,
serverTypeFilled,
dnsProviderFilled,
backblazeFilled,
domainFilled,
@ -146,6 +147,26 @@ class ServerInstallationNotFinished extends ServerInstallationState {
super.serverDetails,
super.installationDialoguePopUp,
});
ServerInstallationNotFinished.fromWizardData(
final ServerInstallationWizardData data,
) : this(
providerApiToken: data.serverProviderKey,
dnsApiToken: data.dnsProviderKey,
serverDomain: data.serverDomain,
serverTypeIdentificator: data.serverTypeIdentifier,
backblazeCredential: data.backupsCredential,
serverDetails: data.serverDetails,
rootUser: data.rootUser,
isServerStarted: data.isServerStarted,
isServerResetedFirstTime: data.isServerResetedFirstTime,
isServerResetedSecondTime: data.isServerResetedSecondTime,
isLoading: data.isLoading,
dnsMatches: null,
customSshKey: null,
installationDialoguePopUp: null,
);
final bool isLoading;
final Map<String, DnsRecordStatus>? dnsMatches;
final String? customSshKey;
@ -210,12 +231,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
dnsApiToken: dnsApiToken!,
backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!,
rootUser: rootUser!,
serverDetails: serverDetails!,
isServerStarted: isServerStarted,
isServerResetedFirstTime: isServerResetedFirstTime,
isServerResetedSecondTime: isServerResetedSecondTime,
installationDialoguePopUp: installationDialoguePopUp,
);
}
@ -246,13 +262,14 @@ class ServerInstallationFinished extends ServerInstallationState {
required String super.dnsApiToken,
required BackupsCredential super.backblazeCredential,
required ServerDomain super.serverDomain,
required User super.rootUser,
required ServerHostingDetails super.serverDetails,
required super.isServerStarted,
required super.isServerResetedFirstTime,
required super.isServerResetedSecondTime,
required super.installationDialoguePopUp,
});
}) : super(
rootUser: null,
isServerStarted: true,
isServerResetedFirstTime: true,
isServerResetedSecondTime: true,
installationDialoguePopUp: null,
);
@override
List<Object?> get props => [
@ -301,9 +318,9 @@ class ServerInstallationRecovery extends ServerInstallationState {
super.dnsApiToken,
super.backblazeCredential,
super.serverDomain,
super.rootUser,
super.serverDetails,
}) : super(
rootUser: null,
isServerStarted: true,
isServerResetedFirstTime: true,
isServerResetedSecondTime: true,
@ -333,7 +350,6 @@ class ServerInstallationRecovery extends ServerInstallationState {
final String? dnsApiToken,
final BackupsCredential? backblazeCredential,
final ServerDomain? serverDomain,
final User? rootUser,
final ServerHostingDetails? serverDetails,
final RecoveryStep? currentStep,
final ServerRecoveryCapabilities? recoveryCapabilities,
@ -345,7 +361,6 @@ class ServerInstallationRecovery extends ServerInstallationState {
dnsApiToken: dnsApiToken ?? this.dnsApiToken,
backblazeCredential: backblazeCredential ?? this.backblazeCredential,
serverDomain: serverDomain ?? this.serverDomain,
rootUser: rootUser ?? this.rootUser,
serverDetails: serverDetails ?? this.serverDetails,
currentStep: currentStep ?? this.currentStep,
recoveryCapabilities: recoveryCapabilities ?? this.recoveryCapabilities,
@ -357,11 +372,6 @@ class ServerInstallationRecovery extends ServerInstallationState {
dnsApiToken: dnsApiToken!,
backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!,
rootUser: rootUser!,
serverDetails: serverDetails!,
isServerStarted: true,
isServerResetedFirstTime: true,
isServerResetedSecondTime: true,
installationDialoguePopUp: null,
);
}

View file

@ -1,117 +1,18 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
class ApiConfigModel {
final Box _box = Hive.box(BNames.serverInstallationBox);
ServerHostingDetails? get serverDetails => _serverDetails;
String? get localeCode => _localeCode;
String? get serverProviderKey => _serverProviderKey;
String? get serverLocation => _serverLocation;
String? get serverType => _serverType;
String? get dnsProviderKey => _dnsProviderKey;
ServerProviderType? get serverProvider => _serverProvider;
DnsProviderType? get dnsProvider => _dnsProvider;
BackupsCredential? get backblazeCredential => _backblazeCredential;
ServerDomain? get serverDomain => _serverDomain;
BackblazeBucket? get backblazeBucket => _backblazeBucket;
static const localeCodeFallback = 'en';
String? _localeCode;
String? _serverProviderKey;
String? _serverLocation;
String? _dnsProviderKey;
String? _serverType;
ServerProviderType? _serverProvider;
DnsProviderType? _dnsProvider;
ServerHostingDetails? _serverDetails;
BackupsCredential? _backblazeCredential;
ServerDomain? _serverDomain;
BackblazeBucket? _backblazeBucket;
Future<void> setLocaleCode(final String value) async {
_localeCode = value;
}
Future<void> storeServerProviderType(final ServerProviderType value) async {
await _box.put(BNames.serverProvider, value);
_serverProvider = value;
}
Future<void> setDnsProviderType(final DnsProviderType value) async {
await _box.put(BNames.dnsProvider, value);
_dnsProvider = value;
}
Future<void> setServerProviderKey(final String value) async {
await _box.put(BNames.hetznerKey, value);
_serverProviderKey = value;
}
Future<void> setDnsProviderKey(final String value) async {
await _box.put(BNames.cloudFlareKey, value);
_dnsProviderKey = value;
}
Future<void> setServerTypeIdentifier(final String typeIdentifier) async {
await _box.put(BNames.serverTypeIdentifier, typeIdentifier);
_serverType = typeIdentifier;
}
Future<void> setServerLocation(final String serverLocation) async {
await _box.put(BNames.serverLocation, serverLocation);
_serverLocation = serverLocation;
}
Future<void> setBackblazeCredential(final BackupsCredential value) async {
await _box.put(BNames.backblazeCredential, value);
_backblazeCredential = value;
}
Future<void> setServerDomain(final ServerDomain value) async {
await _box.put(BNames.serverDomain, value);
_serverDomain = value;
}
Future<void> setServerDetails(final ServerHostingDetails value) async {
await _box.put(BNames.serverDetails, value);
_serverDetails = value;
}
String get localeCode => _localeCode ?? localeCodeFallback;
Future<void> setLocaleCode(final String value) async => _localeCode = value;
Future<void> resetLocaleCode() async => _localeCode = null;
Future<void> setBackblazeBucket(final BackblazeBucket value) async {
await _box.put(BNames.backblazeBucket, value);
_backblazeBucket = value;
}
void clear() {
_localeCode = null;
_serverProviderKey = null;
_dnsProvider = null;
_serverLocation = null;
_dnsProviderKey = null;
_backblazeCredential = null;
_serverDomain = null;
_serverDetails = null;
_backblazeBucket = null;
_serverType = null;
_serverProvider = null;
}
void init() {
_localeCode = 'en';
_serverProviderKey = _box.get(BNames.hetznerKey);
_serverLocation = _box.get(BNames.serverLocation);
_dnsProviderKey = _box.get(BNames.cloudFlareKey);
_backblazeCredential = _box.get(BNames.backblazeCredential);
_serverDomain = _box.get(BNames.serverDomain);
_serverDetails = _box.get(BNames.serverDetails);
_backblazeBucket = _box.get(BNames.backblazeBucket);
_serverType = _box.get(BNames.serverTypeIdentifier);
_serverProvider = _box.get(BNames.serverProvider);
_dnsProvider = _box.get(BNames.dnsProvider);
}
}

View file

@ -6,6 +6,7 @@ import 'package:pub_semver/pub_semver.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
import 'package:selfprivacy/logic/get_it/resources_model.dart';
import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
@ -43,6 +44,8 @@ class ApiConnectionRepository {
Timer? _timer;
StreamSubscription<List<ServerJob>>? _serverJobsStreamSubscription;
Future<void> removeServerJob(final String uid) async {
await api.removeApiJob(uid);
_apiData.serverJobs.data
@ -246,14 +249,12 @@ class ApiConnectionRepository {
}
ServerHostingDetails? get serverDetails =>
getIt<ApiConfigModel>().serverDetails;
ServerDomain? get serverDomain => getIt<ApiConfigModel>().serverDomain;
getIt<ResourcesModel>().serverDetails;
ServerDomain? get serverDomain => getIt<ResourcesModel>().serverDomain;
void init() async {
final serverDetails = getIt<ApiConfigModel>().serverDetails;
final hasFinalChecked =
box.get(BNames.hasFinalChecked, defaultValue: false);
if (serverDetails == null || !hasFinalChecked) {
final serverDetails = getIt<ResourcesModel>().serverDetails;
if (serverDetails == null) {
return;
}
connectionStatus = ConnectionStatus.reconnecting;
@ -274,6 +275,15 @@ class ApiConnectionRepository {
connectionStatus = ConnectionStatus.connected;
_connectionStatusStream.add(connectionStatus);
if (VersionConstraint.parse(wsJobsUpdatesSupportedVersion)
.allows(Version.parse(apiVersion))) {
_serverJobsStreamSubscription =
api.getServerJobsStream().listen((final List<ServerJob> jobs) {
_apiData.serverJobs.data = jobs;
_dataStream.add(_apiData);
});
}
// Use timer to periodically check for new jobs
_timer = Timer.periodic(
const Duration(seconds: 10),
@ -281,9 +291,21 @@ class ApiConnectionRepository {
);
}
void clear() async {
connectionStatus = ConnectionStatus.nonexistent;
_connectionStatusStream.add(connectionStatus);
_timer?.cancel();
await _serverJobsStreamSubscription?.cancel();
}
static const String wsJobsUpdatesSupportedVersion = '>=3.3.0';
Future<void> _refetchEverything(final Version version) async {
await _apiData.serverJobs
.refetchData(version, () => _dataStream.add(_apiData));
if (_serverJobsStreamSubscription == null ||
_apiData.serverJobs.data == null) {
await _apiData.serverJobs
.refetchData(version, () => _dataStream.add(_apiData));
}
await _apiData.backups
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.backupConfig
@ -302,7 +324,7 @@ class ApiConnectionRepository {
}
Future<void> reload(final Timer? timer) async {
final serverDetails = getIt<ApiConfigModel>().serverDetails;
final serverDetails = getIt<ResourcesModel>().serverDetails;
if (serverDetails == null) {
return;
}

View file

@ -1,17 +0,0 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/models/message.dart';
class ConsoleModel extends ChangeNotifier {
final List<Message> _messages = [];
List<Message> get messages => _messages;
void addMessage(final Message message) {
messages.add(message);
notifyListeners();
// Make sure we don't have too many messages
if (messages.length > 500) {
messages.removeAt(0);
}
}
}

View file

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/models/console_log.dart';
class ConsoleModel extends ChangeNotifier {
/// limit for history, so logs won't affect memory and overflow
static const logBufferLimit = 500;
/// differs from log buffer limit so as to not rearrange memory each time
/// we add incoming log
static const incomingBufferBreakpoint = 750;
final List<ConsoleLog> _logs = [];
final List<ConsoleLog> _incomingQueue = [];
bool _paused = false;
bool get paused => _paused;
List<ConsoleLog> get logs => _logs;
void log(final ConsoleLog newLog) {
if (paused) {
_incomingQueue.add(newLog);
if (_incomingQueue.length > incomingBufferBreakpoint) {
logs.removeRange(0, _incomingQueue.length - logBufferLimit);
}
} else {
logs.add(newLog);
_updateQueue();
}
}
void play() {
_logs.addAll(_incomingQueue);
_paused = false;
_updateQueue();
_incomingQueue.clear();
}
void pause() {
_paused = true;
notifyListeners();
}
/// drop logs over the limit and
void _updateQueue() {
// Make sure we don't have too many
if (logs.length > logBufferLimit) {
logs.removeRange(0, logs.length - logBufferLimit);
}
notifyListeners();
}
}

View file

@ -0,0 +1,342 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/models/hive/dns_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/server.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
class ResourcesModel {
final Box _box = Hive.box(BNames.resourcesBox);
List<ServerProviderCredential> get serverProviderCredentials =>
_serverProviderTokens;
List<DnsProviderCredential> get dnsProviderCredentials => _dnsProviderTokens;
List<BackupsCredential> get backupsCredentials => _backupsCredentials;
List<Server> get servers => _servers;
BackblazeBucket? get backblazeBucket => _backblazeBucket;
List<ServerProviderCredential> _serverProviderTokens = [];
List<DnsProviderCredential> _dnsProviderTokens = [];
List<BackupsCredential> _backupsCredentials = [];
List<Server> _servers = [];
// TODO: As we will add support for other backup storages, we should
// refactor this.
BackblazeBucket? _backblazeBucket;
@Deprecated('Compatibility getter')
ServerHostingDetails? get serverDetails =>
_servers.firstOrNull?.hostingDetails;
@Deprecated('Compatibility getter')
String? get serverProviderKey => _serverProviderTokens.firstOrNull?.token;
@Deprecated('Compatibility getter')
String? get serverLocation =>
_servers.firstOrNull?.hostingDetails.serverLocation;
@Deprecated('Compatibility getter')
String? get serverType => _servers.firstOrNull?.hostingDetails.serverType;
@Deprecated('Compatibility getter')
String? get dnsProviderKey => _dnsProviderTokens.firstOrNull?.token;
@Deprecated('Compatibility getter')
ServerProviderType? get serverProvider =>
_serverProviderTokens.firstOrNull?.provider;
@Deprecated('Compatibility getter')
DnsProviderType? get dnsProvider => _dnsProviderTokens.firstOrNull?.provider;
@Deprecated('Compatibility getter')
BackupsCredential? get backblazeCredential => _backupsCredentials.firstOrNull;
@Deprecated('Compatibility getter')
ServerDomain? get serverDomain => _servers.firstOrNull?.domain;
Future<void> addServerProviderToken(
final ServerProviderCredential token,
) async {
_serverProviderTokens.add(token);
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
}
Future<void> associateServerWithToken(
final int serverId,
final String token,
) async {
_serverProviderTokens
.firstWhere(
(final credential) => credential.token == token,
)
.associatedServerIds
.add(serverId);
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
}
Future<void> removeServerProviderToken(
final ServerProviderCredential token,
) async {
_serverProviderTokens.remove(token);
await _box.put(BNames.serverProviderTokens, _serverProviderTokens);
}
Future<void> addDnsProviderToken(final DnsProviderCredential token) async {
// Check if this token already exists
if (_dnsProviderTokens
.any((final credential) => credential.token == token.token)) {
throw Exception('Token already exists');
}
_dnsProviderTokens.add(token);
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
}
Future<void> associateDomainWithToken(
final String domain,
final String token,
) async {
_dnsProviderTokens
.firstWhere(
(final credential) => credential.token == token,
)
.associatedDomainNames
.add(domain);
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
}
Future<void> removeDnsProviderToken(final DnsProviderCredential token) async {
_dnsProviderTokens.remove(token);
await _box.put(BNames.dnsProviderTokens, _dnsProviderTokens);
}
Future<void> addBackupsCredential(final BackupsCredential credential) async {
_backupsCredentials.add(credential);
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
}
Future<void> removeBackupsCredential(
final BackupsCredential credential,
) async {
_backupsCredentials.remove(credential);
await _box.put(BNames.backupsProviderTokens, _backupsCredentials);
}
Future<void> addServer(final Server server) async {
_servers.add(server);
await _box.put(BNames.servers, _servers);
}
Future<void> removeServer(final Server server) async {
_servers.remove(server);
await _box.put(BNames.servers, _servers);
}
Future<void> setBackblazeBucket(final BackblazeBucket bucket) async {
_backblazeBucket = bucket;
await _box.put(BNames.backblazeBucket, _backblazeBucket);
}
Future<void> removeBackblazeBucket() async {
_backblazeBucket = null;
await _box.delete(BNames.backblazeBucket);
}
void clear() {
_servers.clear();
_serverProviderTokens.clear();
_dnsProviderTokens.clear();
_backupsCredentials.clear();
_backblazeBucket = null;
_box.clear();
_box.compact();
}
void init() {
_serverProviderTokens = _box
.get(
BNames.serverProviderTokens,
defaultValue: <ServerProviderCredential>[],
)
.map<ServerProviderCredential>(
(final e) => e as ServerProviderCredential,
)
.toList();
_dnsProviderTokens = _box
.get(
BNames.dnsProviderTokens,
defaultValue: <DnsProviderCredential>[],
)
.map<DnsProviderCredential>((final e) => e as DnsProviderCredential)
.toList();
_backupsCredentials = _box
.get(
BNames.backupsProviderTokens,
defaultValue: <BackupsCredential>[],
)
.map<BackupsCredential>((final e) => e as BackupsCredential)
.toList();
_servers = _box
.get(
BNames.servers,
defaultValue: <Server>[],
)
.map<Server>((final e) => e as Server)
.toList();
_backblazeBucket = _box.get(BNames.backblazeBucket);
}
}
class WizardDataModel {
final Box _box = Hive.box(BNames.wizardDataBox);
ServerInstallationWizardData? get serverInstallation => _serverInstallation;
ServerInstallationWizardData? _serverInstallation;
Future<void> setServerProviderType(final ServerProviderType provider) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverProviderType: provider);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerProviderKey(final String key) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverProviderKey: key);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setDnsProviderType(final DnsProviderType provider) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(dnsProviderType: provider);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setDnsProviderKey(final String key) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(dnsProviderKey: key);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerTypeIdentifier(final String identifier) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverTypeIdentifier: identifier);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerLocation(final String location) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverLocation: location);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> moveServerTypeToServerDetails() async {
final details = _serverInstallation?.serverDetails;
if (details != null) {
if (_serverInstallation?.serverTypeIdentifier != null &&
_serverInstallation?.serverLocation != null) {
_serverInstallation = _serverInstallation?.copyWith(
serverDetails: () => details.copyWith(
serverType: _serverInstallation?.serverTypeIdentifier,
serverLocation: _serverInstallation?.serverLocation,
),
);
await _box.put(
BNames.serverInstallationWizardData,
_serverInstallation,
);
}
}
}
Future<void> setServerDetails(final ServerHostingDetails details) async {
final detailsWithServerType = details.copyWith(
serverLocation: _serverInstallation?.serverLocation,
serverType: _serverInstallation?.serverTypeIdentifier,
);
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDetails: () => detailsWithServerType);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> deleteServerDetails() async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDetails: () => null);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setBackupsCredential(final BackupsCredential credential) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(backupsCredential: credential);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setServerDomain(final ServerDomain domain) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDomain: () => domain);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> deleteServerDomain() async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(serverDomain: () => null);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsServerStarted(final bool isStarted) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isServerStarted: isStarted);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsServerRebootedFirstTime(final bool isRebooted) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isServerResetedFirstTime: isRebooted);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsServerRebootedSecondTime(final bool isRebooted) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isServerResetedSecondTime: isRebooted);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setRootUser(final User user) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(rootUser: user);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> setIsRecoveringServer(final bool isRecovering) async {
_serverInstallation =
(_serverInstallation ?? ServerInstallationWizardData.empty())
.copyWith(isRecoveringServer: isRecovering);
await _box.put(BNames.serverInstallationWizardData, _serverInstallation);
}
Future<void> clearServerInstallation() async {
_serverInstallation = null;
await _box.delete(BNames.serverInstallationWizardData);
}
Future<void> clear() async {
await _box.clear();
await _box.compact();
}
void init() {
_serverInstallation =
_box.get(BNames.serverInstallationWizardData, defaultValue: null);
}
}

View file

@ -0,0 +1,184 @@
import 'dart:convert';
import 'package:gql/language.dart' as gql;
import 'package:graphql/client.dart' as gql_client;
import 'package:intl/intl.dart';
enum ConsoleLogSeverity {
normal,
warning,
}
/// Base entity for console logs.
sealed class ConsoleLog {
ConsoleLog({
final String? customTitle,
this.severity = ConsoleLogSeverity.normal,
}) : title = customTitle ??
(severity == ConsoleLogSeverity.warning ? 'Error' : 'Log'),
time = DateTime.now();
final DateTime time;
final ConsoleLogSeverity severity;
bool get isError => severity == ConsoleLogSeverity.warning;
/// title for both in listing and in dialog
final String title;
/// formatted data to be shown in listing
String get content;
/// data available for copy in dialog
String? get shareableData => '{"title": "$title",\n'
'"timestamp": "$fullUTCString",\n'
'"data":{\n$content\n}'
'\n}';
static final DateFormat _formatter = DateFormat('hh:mm:ss');
String get timeString => _formatter.format(time);
String get fullUTCString => time.toUtc().toIso8601String();
}
abstract class LogWithRawResponse {
String get rawResponse;
}
/// entity for manually created logs, as opposed to automated ones coming
/// from requests / responses
class ManualConsoleLog extends ConsoleLog {
ManualConsoleLog({
required this.content,
super.customTitle,
super.severity,
});
ManualConsoleLog.warning({
required this.content,
super.customTitle,
}) : super(severity: ConsoleLogSeverity.warning);
@override
String content;
}
class RestApiRequestConsoleLog extends ConsoleLog {
RestApiRequestConsoleLog({
this.method,
this.uri,
this.headers,
this.data,
super.severity,
});
/// headers thath should not be included into clipboard buffer, as opposed to
/// `[[ConsoleLogItemDialog]]` `_KeyValueRow.hideList` which filters values,
/// that should be accessible from UI, but hidden in screenshots
static const blacklistedHeaders = ['Authorization'];
final String? method;
final Uri? uri;
final Map<String, dynamic>? headers;
final String? data;
@override
String get title => 'Rest API Request';
Map<String, dynamic> get filteredHeaders => Map.fromEntries(
headers?.entries.where(
(final entry) => !blacklistedHeaders.contains(entry.key),
) ??
const [],
);
@override
String get content => '"method": "$method",\n'
'"uri": "$uri",\n'
'"headers": ${jsonEncode(filteredHeaders)},\n' // censor header to not expose API keys
'"data": $data';
}
class RestApiResponseConsoleLog extends ConsoleLog {
RestApiResponseConsoleLog({
this.method,
this.uri,
this.statusCode,
this.data,
super.severity,
});
final String? method;
final Uri? uri;
final int? statusCode;
final String? data;
@override
String get title => 'Rest API Response';
@override
String get content => '"method": "$method",\n'
'"status_code": $statusCode,\n'
'"uri": "$uri",\n'
'"data": $data';
}
/// there is no actual getter for context fields outside of its class
/// one can extract unique entries by their type, which implements
/// `ContextEntry` class, I'll leave the code here if in the future
/// some entries will actually be needed.
// extension ContextEncoder on gql_client.Context {
// String get encode {
// return '""';
// }
// }
class GraphQlRequestConsoleLog extends ConsoleLog {
GraphQlRequestConsoleLog({
required this.operationType,
required this.operation,
required this.variables,
// this.context,
super.severity,
});
// final gql_client.Context? context;
final String operationType;
final gql_client.Operation? operation;
String get operationDocument =>
operation != null ? gql.printNode(operation!.document) : 'null';
final Map<String, dynamic>? variables;
@override
String get title => 'GraphQL Request';
@override
String get content =>
// '"context": ${context?.encode},\n'
'"variables": ${jsonEncode(variables)},\n'
'"type": "$operationType",\n'
'"name": "${operation?.operationName}",\n'
'"document": ${jsonEncode(operationDocument)}';
}
class GraphQlResponseConsoleLog extends ConsoleLog
implements LogWithRawResponse {
GraphQlResponseConsoleLog({
required this.rawResponse,
// this.context,
this.data,
this.errors,
super.severity,
});
@override
final String rawResponse;
// final gql_client.Context? context;
final Map<String, dynamic>? data;
final List<gql_client.GraphQLError>? errors;
@override
String get title => 'GraphQL Response';
@override
String get content =>
// '"context": ${context?.encode},\n'
'"data": ${jsonEncode(data)},\n'
'"errors": $errors';
}

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