@fleetbase/registry-bridge-engine 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/addon/components/extension-card.js +8 -2
  2. package/addon/components/modals/extension-details.hbs +7 -4
  3. package/addon/controllers/installed.js +2 -0
  4. package/addon/engine.js +1 -1
  5. package/addon/routes/application.js +11 -0
  6. package/addon/routes/developers/analytics.js +11 -0
  7. package/addon/routes/developers/credentials.js +11 -0
  8. package/addon/routes/developers/extensions/edit.js +11 -0
  9. package/addon/routes/developers/extensions/index.js +11 -0
  10. package/addon/routes/developers/extensions/new.js +14 -1
  11. package/addon/routes/developers/extensions.js +14 -1
  12. package/addon/routes/developers/payments/index.js +11 -0
  13. package/addon/routes/developers/payments/onboard.js +8 -1
  14. package/addon/routes/developers/payments.js +14 -1
  15. package/addon/routes/explore/category.js +11 -0
  16. package/addon/routes/explore/index.js +11 -0
  17. package/addon/routes/installed.js +11 -0
  18. package/addon/routes/purchased.js +11 -0
  19. package/addon/styles/registry-bridge-engine.css +106 -0
  20. package/addon/templates/application.hbs +9 -15
  21. package/addon/templates/developers/analytics.hbs +2 -0
  22. package/addon/templates/developers/credentials.hbs +1 -1
  23. package/addon/templates/developers/extensions/edit.hbs +2 -1
  24. package/addon/templates/developers/extensions/index.hbs +1 -1
  25. package/addon/templates/developers/extensions/new.hbs +1 -1
  26. package/addon/templates/developers/payments/index.hbs +1 -1
  27. package/composer.json +2 -2
  28. package/config/environment.js +7 -0
  29. package/extension.json +1 -1
  30. package/package.json +5 -4
  31. package/server/src/Auth/Schemas/RegistryBridge.php +215 -0
  32. package/translations/ar-ae.yml +132 -0
  33. package/translations/en-us.yaml +1 -0
  34. package/translations/vi-vn.yaml +131 -0
@@ -26,6 +26,7 @@ export default class ExtensionCardComponent extends Component {
26
26
  @service fetch;
27
27
  @service stripe;
28
28
  @service urlSearchParams;
29
+ @service abilities;
29
30
  @tracked extension;
30
31
 
31
32
  constructor(owner, { extension }) {
@@ -45,6 +46,11 @@ export default class ExtensionCardComponent extends Component {
45
46
  const isAlreadyPurchased = this.extension.is_purchased === true;
46
47
  const isAlreadyInstalled = this.extension.is_installed === true;
47
48
  const isPaymentRequired = !isAuthor && this.extension.payment_required === true && isAlreadyPurchased === false;
49
+ const userCannotPurchase = isPaymentRequired && this.abilities.cannot('registry-bridge purchase extension');
50
+ let acceptButtonText = isPaymentRequired ? `Purchase for ${formatCurrency(this.extension.price, this.extension.currency)}` : isAlreadyInstalled ? 'Installed' : 'Install';
51
+ if (this.abilities.cannot('registry-bridge install extension')) {
52
+ acceptButtonText = 'Unauthorized to Install';
53
+ }
48
54
  const goBack = async (modal) => {
49
55
  await modal.done();
50
56
  later(
@@ -65,9 +71,9 @@ export default class ExtensionCardComponent extends Component {
65
71
  titleComponent: 'extension-modal-title',
66
72
  modalClass: 'flb--extension-modal modal-lg',
67
73
  modalHeaderClass: 'flb--extension-modal-header',
68
- acceptButtonText: isPaymentRequired ? `Purchase for ${formatCurrency(this.extension.price, this.extension.currency)}` : isAlreadyInstalled ? 'Installed' : 'Install',
74
+ acceptButtonText,
69
75
  acceptButtonIcon: isPaymentRequired ? 'credit-card' : isAlreadyInstalled ? 'check' : 'download',
70
- acceptButtonDisabled: isAlreadyInstalled,
76
+ acceptButtonDisabled: this.abilities.cannot('registry-bridge install extension') || isAlreadyInstalled || userCannotPurchase,
71
77
  acceptButtonScheme: isPaymentRequired ? 'success' : 'primary',
72
78
  declineButtonText: 'Done',
73
79
  process: null,
@@ -27,11 +27,14 @@
27
27
  </EmberWormhole>
28
28
  {{/if}}
29
29
  {{/if}}
30
- <div>
31
- <h3 class="dark:text-white font-semibold mb-1">{{t "registry-bridge.component.extension-details-modal.overview"}}</h3>
32
- <div class="space-y-1">
30
+ <div class="space-y-3">
31
+ <div>
32
+ <h3 class="dark:text-white font-semibold mb-1">{{t "registry-bridge.component.extension-details-modal.description"}}</h3>
33
33
  <p class="dark:text-gray-200 text-sm">{{this.extension.description}}</p>
34
- <p class="dark:text-gray-200 text-sm">{{html-safe this.extension.promotional_text}}</p>
34
+ </div>
35
+ <div>
36
+ <h3 class="dark:text-white font-semibold mb-1">{{t "registry-bridge.component.extension-details-modal.overview"}}</h3>
37
+ <div class="extension-details-promo-content dark:text-gray-200 text-sm">{{html-safe this.extension.promotional_text}}</div>
35
38
  </div>
36
39
  </div>
37
40
  <div>
@@ -9,6 +9,7 @@ export default class InstalledController extends Controller {
9
9
  @service notifications;
10
10
  @service socket;
11
11
  @service hostRouter;
12
+ @service abilities;
12
13
 
13
14
  @action about(extension) {
14
15
  this.modalsManager.show('modals/extension-details', {
@@ -31,6 +32,7 @@ export default class InstalledController extends Controller {
31
32
  acceptButtonText: 'Uninstall',
32
33
  acceptButtonIcon: 'trash',
33
34
  acceptButtonScheme: 'danger',
35
+ acceptButtonDisabled: this.abilities.cannot('registry-bridge uninstall extension'),
34
36
  process: null,
35
37
  step: null,
36
38
  stepDescription: 'Awaiting uninstall to begin...',
package/addon/engine.js CHANGED
@@ -19,7 +19,7 @@ export default class RegistryBridgeEngine extends Engine {
19
19
  };
20
20
  setupExtension = function (app, engine, universe) {
21
21
  // Register menu item in header
22
- universe.registerHeaderMenuItem('Extensions', 'console.extensions', { icon: 'shapes', priority: 99 });
22
+ universe.registerHeaderMenuItem('Extensions', 'console.extensions', { icon: 'shapes', priority: 99, slug: 'registry-bridge' });
23
23
  // Register admin controls
24
24
  universe.registerAdminMenuPanel(
25
25
  'Extensions Registry',
@@ -3,6 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class ApplicationRoute extends Route {
5
5
  @service fetch;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
10
+
11
+ beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge see extension')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+ }
6
17
 
7
18
  async setupController(controller) {
8
19
  super.setupController(...arguments);
@@ -3,6 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class DevelopersAnalyticsRoute extends Route {
5
5
  @service store;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
10
+
11
+ beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge list extension-analytic')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+ }
6
17
 
7
18
  model() {
8
19
  return this.store.query('registry-extension', { is_author: 1 });
@@ -3,6 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class DevelopersCredentialsRoute extends Route {
5
5
  @service fetch;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
10
+
11
+ beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge list registry-token')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+ }
6
17
 
7
18
  model() {
8
19
  return this.fetch.get('auth/registry-tokens', {}, { namespace: '~registry/v1' });
@@ -3,6 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class DevelopersExtensionsEditRoute extends Route {
5
5
  @service store;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
10
+
11
+ beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge update extension-bundle')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+ }
6
17
 
7
18
  model(params) {
8
19
  return this.store.queryRecord('registry-extension', { public_id: params.public_id, single: true });
@@ -3,6 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class DevelopersExtensionsIndexRoute extends Route {
5
5
  @service store;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
10
+
11
+ beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge list extension-bundle')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+ }
6
17
 
7
18
  model() {
8
19
  return this.store.query('registry-extension', { is_author: 1 });
@@ -1,3 +1,16 @@
1
1
  import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
2
3
 
3
- export default class DevelopersExtensionsNewRoute extends Route {}
4
+ export default class DevelopersExtensionsNewRoute extends Route {
5
+ @service notifications;
6
+ @service hostRouter;
7
+ @service abilities;
8
+ @service intl;
9
+
10
+ beforeModel() {
11
+ if (this.abilities.cannot('registry-bridge create extension-bundle')) {
12
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
13
+ return this.hostRouter.transitionTo('console');
14
+ }
15
+ }
16
+ }
@@ -1,3 +1,16 @@
1
1
  import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
2
3
 
3
- export default class DevelopersExtensionsRoute extends Route {}
4
+ export default class DevelopersExtensionsRoute extends Route {
5
+ @service notifications;
6
+ @service hostRouter;
7
+ @service abilities;
8
+ @service intl;
9
+
10
+ beforeModel() {
11
+ if (this.abilities.cannot('registry-bridge list extension-bundle')) {
12
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
13
+ return this.hostRouter.transitionTo('console');
14
+ }
15
+ }
16
+ }
@@ -3,6 +3,10 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class DevelopersPaymentsIndexRoute extends Route {
5
5
  @service fetch;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
6
10
 
7
11
  queryParams = {
8
12
  page: { refreshModel: true },
@@ -11,6 +15,13 @@ export default class DevelopersPaymentsIndexRoute extends Route {
11
15
  query: { refreshModel: true },
12
16
  };
13
17
 
18
+ beforeModel() {
19
+ if (this.abilities.cannot('registry-bridge list extension-payment')) {
20
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
21
+ return this.hostRouter.transitionTo('console');
22
+ }
23
+ }
24
+
14
25
  model() {
15
26
  return this.fetch.get('payments/author-received', {}, { namespace: '~registry/v1' });
16
27
  }
@@ -3,10 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class DevelopersPaymentsOnboardRoute extends Route {
5
5
  @service fetch;
6
- @service hostRouter;
7
6
  @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
8
10
 
9
11
  async beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge onboard extension-payment')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+
10
17
  try {
11
18
  const { hasStripeConnectAccount } = await this.fetch.get('payments/has-stripe-connect-account', {}, { namespace: '~registry/v1' });
12
19
  if (hasStripeConnectAccount) {
@@ -1,3 +1,16 @@
1
1
  import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
2
3
 
3
- export default class DevelopersPaymentsRoute extends Route {}
4
+ export default class DevelopersPaymentsRoute extends Route {
5
+ @service notifications;
6
+ @service hostRouter;
7
+ @service abilities;
8
+ @service intl;
9
+
10
+ beforeModel() {
11
+ if (this.abilities.cannot('registry-bridge list extension-payment')) {
12
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
13
+ return this.hostRouter.transitionTo('console');
14
+ }
15
+ }
16
+ }
@@ -3,6 +3,10 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class ExploreCategoryRoute extends Route {
5
5
  @service store;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
6
10
 
7
11
  queryParams = {
8
12
  query: {
@@ -10,6 +14,13 @@ export default class ExploreCategoryRoute extends Route {
10
14
  },
11
15
  };
12
16
 
17
+ beforeModel() {
18
+ if (this.abilities.cannot('registry-bridge list extension')) {
19
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
20
+ return this.hostRouter.transitionTo('console');
21
+ }
22
+ }
23
+
13
24
  model({ slug }) {
14
25
  return this.store.queryRecord('category', { slug, for: 'extension_category', core_category: 1, single: 1 });
15
26
  }
@@ -3,6 +3,10 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class ExploreIndexRoute extends Route {
5
5
  @service store;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
6
10
 
7
11
  queryParams = {
8
12
  query: {
@@ -10,6 +14,13 @@ export default class ExploreIndexRoute extends Route {
10
14
  },
11
15
  };
12
16
 
17
+ beforeModel() {
18
+ if (this.abilities.cannot('registry-bridge list extension')) {
19
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
20
+ return this.hostRouter.transitionTo('console');
21
+ }
22
+ }
23
+
13
24
  model(params) {
14
25
  const { query } = params;
15
26
  return this.store.query('registry-extension', { explore: 1, query });
@@ -3,6 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class InstalledRoute extends Route {
5
5
  @service fetch;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
10
+
11
+ beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge list extension-install')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+ }
6
17
 
7
18
  model(params = {}) {
8
19
  return this.fetch.get('registry-extensions/installed', params, { namespace: '~registry/v1', normalizeToEmberData: true, modelType: 'registry-extension' });
@@ -3,6 +3,17 @@ import { inject as service } from '@ember/service';
3
3
 
4
4
  export default class PurchasedRoute extends Route {
5
5
  @service fetch;
6
+ @service notifications;
7
+ @service hostRouter;
8
+ @service abilities;
9
+ @service intl;
10
+
11
+ beforeModel() {
12
+ if (this.abilities.cannot('registry-bridge list extension-purchase')) {
13
+ this.notifications.warning(this.intl.t('common.unauthorized-access'));
14
+ return this.hostRouter.transitionTo('console');
15
+ }
16
+ }
6
17
 
7
18
  model(params = {}) {
8
19
  return this.fetch.get('registry-extensions/purchased', params, { namespace: '~registry/v1', normalizeToEmberData: true, modelType: 'registry-extension' });
@@ -171,3 +171,109 @@ body[data-theme='dark'] .self-managed-install-instructions {
171
171
  font-size: 0.75rem;
172
172
  line-height: 1rem;
173
173
  }
174
+
175
+ .extension-details-promo-content {
176
+ padding: 0.5rem;
177
+ background-color: #f9fafb;
178
+ color: #000;
179
+ }
180
+
181
+ body[data-theme='dark'] .extension-details-promo-content {
182
+ padding: 0.5rem;
183
+ background-color: #111827;
184
+ color: #f9fafb;
185
+ }
186
+
187
+ .extension-details-promo-content hr {
188
+ border: none;
189
+ border-top: 1px solid #e5e7eb;
190
+ margin: 0.5rem 0;
191
+ }
192
+
193
+ body[data-theme='dark'] .extension-details-promo-content hr {
194
+ border-top: 1px solid #374151;
195
+ }
196
+
197
+ .extension-details-promo-content ol,
198
+ .extension-details-promo-content ul {
199
+ padding-left: 25px;
200
+ }
201
+
202
+ .extension-details-promo-content ol {
203
+ list-style: decimal;
204
+ }
205
+
206
+ .extension-details-promo-content ul {
207
+ list-style: disc;
208
+ }
209
+
210
+ .extension-details-promo-content blockquote {
211
+ border-left: 3px #e2e8f0 solid;
212
+ padding-left: 0.5rem;
213
+ margin-left: 0.25rem;
214
+ font-style: italic;
215
+ }
216
+
217
+ body[data-theme='dark'] .extension-details-promo-content blockquote {
218
+ border-left: 3px #4b5563 solid;
219
+ }
220
+
221
+ .extension-details-promo-content pre {
222
+ white-space: pre;
223
+ font-family: monospace;
224
+ padding: 0.25rem 0.75rem;
225
+ border-radius: 0.25rem;
226
+ background-color: #f3f4f6;
227
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 5%);
228
+ }
229
+
230
+ body[data-theme='dark'] .extension-details-promo-content pre {
231
+ background-color: #1f2937;
232
+ }
233
+
234
+ .extension-details-promo-content pre > code {
235
+ font-family: monospace;
236
+ }
237
+
238
+ .extension-details-promo-content table {
239
+ border-collapse: collapse;
240
+ margin: 0;
241
+ overflow: hidden;
242
+ table-layout: fixed;
243
+ width: 100%;
244
+ }
245
+
246
+ .extension-details-promo-content table td,
247
+ .extension-details-promo-content table th {
248
+ border: 1px solid #d1d5db;
249
+ box-sizing: border-box;
250
+ min-width: 1em;
251
+ padding: 6px 8px;
252
+ position: relative;
253
+ vertical-align: top;
254
+ }
255
+
256
+ body[data-theme='dark'] .extension-details-promo-content table td,
257
+ body[data-theme='dark'] .extension-details-promo-content table th {
258
+ border: 1px solid #374151;
259
+ }
260
+
261
+ .extension-details-promo-content table td > *,
262
+ .extension-details-promo-content table th > * {
263
+ margin-bottom: 0;
264
+ }
265
+
266
+ .extension-details-promo-content table th {
267
+ background-color: #e5e7eb;
268
+ font-weight: bold;
269
+ text-align: left;
270
+ }
271
+
272
+ body[data-theme='dark'] .extension-details-promo-content table th {
273
+ background-color: #1f2937;
274
+ }
275
+
276
+ .extension-details-promo-content .tableWrapper {
277
+ margin: 1.5rem 0;
278
+ overflow-x: auto;
279
+ }
@@ -1,23 +1,17 @@
1
1
  <EmberWormhole @to="sidebar-menu-items">
2
- <Layout::Sidebar::Item @route="console.extensions.installed" @icon="inbox">Installed</Layout::Sidebar::Item>
3
- <Layout::Sidebar::Item @route="console.extensions.purchased" @icon="bag-shopping">Purchased</Layout::Sidebar::Item>
4
- <Layout::Sidebar::Panel @open={{true}} @title="Explore">
5
- <Layout::Sidebar::Item @route="console.extensions.explore.index" @icon="shapes">Explore All</Layout::Sidebar::Item>
2
+ <Layout::Sidebar::Item @route="console.extensions.installed" @icon="inbox" @permission="registry-bridge list extension-install" @visible={{can "registry-bridge see extension-install"}}>Installed</Layout::Sidebar::Item>
3
+ <Layout::Sidebar::Item @route="console.extensions.purchased" @icon="bag-shopping" @permission="registry-bridge list extension-purchase" @visible={{can "registry-bridge see extension-purchase"}}>Purchased</Layout::Sidebar::Item>
4
+ <Layout::Sidebar::Panel @open={{true}} @title="Explore" @visible={{can "registry-bridge see extension"}}>
5
+ <Layout::Sidebar::Item @route="console.extensions.explore.index" @icon="shapes" @permission="registry-bridge list extension">Explore All</Layout::Sidebar::Item>
6
6
  {{#each this.categories as |category|}}
7
- <LinkTo @route="explore.category" @model={{category}} class="next-nav-item">
8
- <div class="next-nav-item-icon-container">
9
- <FaIcon @icon={{category.icon}} @size="sm" />
10
- </div>
11
- <div class="truncate w-10/12">{{category.name}}</div>
12
- <div class="next-nav-item-right-side"></div>
13
- </LinkTo>
7
+ <Layout::Sidebar::Item @route="console.extensions.explore.category" @model={{category}} @icon={{category.icon}} @permission="registry-bridge list extension">{{category.name}}</Layout::Sidebar::Item>
14
8
  {{/each}}
15
9
  </Layout::Sidebar::Panel>
16
10
  <Layout::Sidebar::Panel @open={{true}} @title="Developers">
17
- <Layout::Sidebar::Item @route="console.extensions.developers.extensions" @icon="box-archive">Extensions</Layout::Sidebar::Item>
18
- <Layout::Sidebar::Item @route="console.extensions.developers.analytics" @icon="chart-simple">Analytics</Layout::Sidebar::Item>
19
- <Layout::Sidebar::Item @route="console.extensions.developers.payments" @icon="cash-register">Payments</Layout::Sidebar::Item>
20
- <Layout::Sidebar::Item @route="console.extensions.developers.credentials" @icon="key">Credentials</Layout::Sidebar::Item>
11
+ <Layout::Sidebar::Item @route="console.extensions.developers.extensions" @icon="box-archive" @permission="registry-bridge list extension-bundle" @visible={{can "registry-bridge see extension-bundle"}}>Extensions</Layout::Sidebar::Item>
12
+ <Layout::Sidebar::Item @route="console.extensions.developers.analytics" @icon="chart-simple" @permission="registry-bridge list extension-analytic" @visible={{can "registry-bridge see extension-analytic"}}>Analytics</Layout::Sidebar::Item>
13
+ <Layout::Sidebar::Item @route="console.extensions.developers.payments" @icon="cash-register" @permission="registry-bridge list extension-payment" @visible={{can "registry-bridge see extension-payment"}}>Payments</Layout::Sidebar::Item>
14
+ <Layout::Sidebar::Item @route="console.extensions.developers.credentials" @icon="key" @permission="registry-bridge list registry-token" @visible={{can "registry-bridge see registry-token"}}>Credentials</Layout::Sidebar::Item>
21
15
  </Layout::Sidebar::Panel>
22
16
  <Spacer @height="200px" />
23
17
  </EmberWormhole>
@@ -9,6 +9,8 @@
9
9
  @size="xs"
10
10
  @iconPrefix="fas"
11
11
  @triggerClass="hidden md:flex"
12
+ @permission="registry-bridge list extension"
13
+ @disabled={{not @model}}
12
14
  as |dd|
13
15
  >
14
16
  <div class="next-dd-menu mt-1 mx-0" aria-labelledby="user-menu">
@@ -1,5 +1,5 @@
1
1
  <Layout::Section::Header @title="Credentials">
2
- <Button @type="primary" @icon="plus" @iconPrefix="fas" @text="Create new credentials" class="mr-2" @onClick={{this.createCredentials}} />
2
+ <Button @type="primary" @icon="plus" @iconPrefix="fas" @text="Create new credentials" class="mr-2" @onClick={{this.createCredentials}} @permission="registry-bridge create registry-token" />
3
3
  </Layout::Section::Header>
4
4
 
5
5
  <Layout::Section::Body class="overflow-y-scroll h-full">
@@ -3,7 +3,7 @@
3
3
  <Badge @status={{@model.status}} />
4
4
  </div>
5
5
  <Button @type="default" @size="sm" @icon="arrow-left" @text={{t "common.back"}} @onClick={{transition-to "developers.extensions"}} @wrapperClass="mr-2" />
6
- <Button @type="primary" @size="sm" @icon="save" @text={{t "common.save"}} @onClick={{perform this.save}} @isLoading={{not this.save.isIdle}} @wrapperClass="mr-2" />
6
+ <Button @type="primary" @size="sm" @icon="save" @text={{t "common.save"}} @onClick={{perform this.save}} @isLoading={{not this.save.isIdle}} @wrapperClass="mr-2" @permission="registry-bridge update registry-bundle" />
7
7
  <Button
8
8
  @type="magic"
9
9
  @size="sm"
@@ -13,6 +13,7 @@
13
13
  @onClick={{this.submitForReview}}
14
14
  @disabled={{not this.isReady}}
15
15
  @isLoading={{not this.startReview.isIdle}}
16
+ @permission="registry-bridge submit registry-bundle"
16
17
  />
17
18
  </Layout::Section::Header>
18
19
 
@@ -1,5 +1,5 @@
1
1
  <Layout::Section::Header @title={{t "registry-bridge.developers.extensions.extensions"}}>
2
- <Button @type="primary" @size="sm" @icon="circle-plus" @text={{t "registry-bridge.developers.extensions.create-new-extension"}} @onClick={{transition-to "developers.extensions.new"}} />
2
+ <Button @type="primary" @size="sm" @icon="circle-plus" @text={{t "registry-bridge.developers.extensions.create-new-extension"}} @onClick={{transition-to "developers.extensions.new"}} @permission="registry-bridge create registry-bundle" />
3
3
  </Layout::Section::Header>
4
4
 
5
5
  <Layout::Section::Body class="overflow-y-scroll h-full">
@@ -29,7 +29,7 @@
29
29
  </InputGroup>
30
30
  </ContentPanel>
31
31
  <div class="mt-4 flex items-center space-x-4">
32
- <Button @size="lg" @type="primary" @text="Continue" @icon="check" @onClick={{perform this.save}} @isLoading={{not this.save.isIdle}} />
32
+ <Button @size="lg" @type="primary" @text="Continue" @icon="check" @onClick={{perform this.save}} @isLoading={{not this.save.isIdle}} @permission="registry-bridge create registry-bundle" />
33
33
  <Button @size="lg" @type="default" @text="Cancel" @icon="times" @onClick={{this.cancel}} />
34
34
  </div>
35
35
  </div>
@@ -32,7 +32,7 @@
32
32
  <div class="flex flex-col items-center justify-center">
33
33
  <h1 class="text-lg font-semibold mb-1">Your account is not setup to accept payments yet.</h1>
34
34
  <p class="text-sm mb-2">To accept payments for extensions, you must complete the onboard process via Stripe.</p>
35
- <Button @type="primary" @size="lg" @text="Start payments onboard" @onClick={{transition-to "developers.payments.onboard"}} />
35
+ <Button @type="primary" @size="lg" @text="Start payments onboard" @onClick={{transition-to "developers.payments.onboard"}} @permission="registry-bridge onboard extension-payment" />
36
36
  </div>
37
37
  </div>
38
38
  </div>
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/registry-bridge",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "require": {
22
22
  "php": "^8.0",
23
- "fleetbase/core-api": "^1.5.1",
23
+ "fleetbase/core-api": "^1.5.9",
24
24
  "laravel/cashier": "^15.2.1",
25
25
  "php-http/guzzle7-adapter": "^1.0",
26
26
  "psr/http-factory-implementation": "*",
@@ -10,9 +10,16 @@ module.exports = function (environment) {
10
10
  modulePrefix: name,
11
11
  environment,
12
12
  mountedEngineRoutePrefix: getMountedEngineRoutePrefix(),
13
+
13
14
  stripe: {
14
15
  publishableKey: getenv('STRIPE_KEY'),
15
16
  },
17
+
18
+ 'ember-leaflet': {
19
+ excludeCSS: true,
20
+ excludeJS: true,
21
+ excludeImages: true,
22
+ },
16
23
  };
17
24
 
18
25
  return ENV;
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Registry Bridge",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "repository": "https://github.com/fleetbase/registry-bridge",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/registry-bridge-engine",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "fleetbase": {
6
6
  "route": "extensions"
@@ -39,13 +39,14 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@babel/core": "^7.23.2",
42
- "@fleetbase/ember-core": "^0.2.14",
43
- "@fleetbase/ember-ui": "^0.2.21",
42
+ "@fleetbase/ember-core": "^0.2.19",
43
+ "@fleetbase/ember-ui": "^0.2.32",
44
44
  "@fortawesome/ember-fontawesome": "^2.0.0",
45
45
  "@fortawesome/fontawesome-svg-core": "6.4.0",
46
46
  "@fortawesome/free-solid-svg-icons": "6.4.0",
47
+ "@fortawesome/free-brands-svg-icons": "6.4.0",
47
48
  "@stripe/connect-js": "^3.3.10",
48
- "ember-auto-import": "^2.6.3",
49
+ "ember-auto-import": "^2.7.4",
49
50
  "ember-cli-babel": "^8.2.0",
50
51
  "ember-cli-htmlbars": "^6.3.0",
51
52
  "ember-intl": "6.3.2",
@@ -0,0 +1,215 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\RegistryBridge\Auth\Schemas;
4
+
5
+ class RegistryBridge
6
+ {
7
+ /**
8
+ * The permission schema Name.
9
+ */
10
+ public string $name = 'registry-bridge';
11
+
12
+ /**
13
+ * The permission schema Polict Name.
14
+ */
15
+ public string $policyName = 'RegistryBridge';
16
+
17
+ /**
18
+ * Guards these permissions should apply to.
19
+ */
20
+ public array $guards = ['sanctum'];
21
+
22
+ /**
23
+ * The permission schema resources.
24
+ */
25
+ public array $resources = [
26
+ [
27
+ 'name' => 'registry-token',
28
+ 'actions' => ['export'],
29
+ ],
30
+ [
31
+ 'name' => 'extension',
32
+ 'actions' => ['uninstall', 'install', 'purchase'],
33
+ ],
34
+ [
35
+ 'name' => 'extension-bundle',
36
+ 'actions' => ['submit'],
37
+ 'remove_actions' => [],
38
+ ],
39
+ [
40
+ 'name' => 'extension-purchase',
41
+ 'actions' => [],
42
+ 'remove_actions' => ['create', 'update', 'delete'],
43
+ ],
44
+ [
45
+ 'name' => 'extension-install',
46
+ 'actions' => [],
47
+ 'remove_actions' => ['create', 'update', 'delete'],
48
+ ],
49
+ [
50
+ 'name' => 'extension-analytic',
51
+ 'actions' => [],
52
+ 'remove_actions' => ['create', 'update', 'delete'],
53
+ ],
54
+ [
55
+ 'name' => 'extension-payment',
56
+ 'actions' => ['onboard'],
57
+ 'remove_actions' => ['create', 'update', 'delete'],
58
+ ],
59
+ ];
60
+
61
+ /**
62
+ * Policies provided by this schema.
63
+ */
64
+ public array $policies = [
65
+ [
66
+ 'name' => 'ExtensionDeveloper',
67
+ 'description' => 'Policy for developing and publishing extensions.',
68
+ 'permissions' => [
69
+ 'see extension',
70
+ 'see registry-token',
71
+ 'list registry-token',
72
+ 'create registry-token',
73
+ 'see extension-bundle',
74
+ 'list extension-bundle',
75
+ 'view extension-bundle',
76
+ 'create extension-bundle',
77
+ 'update extension-bundle',
78
+ 'delete extension-bundle',
79
+ 'submit extension-bundle',
80
+ 'see extension-analytic',
81
+ 'view extension-analytic',
82
+ 'see extension-payment',
83
+ 'view extension-payment',
84
+ 'list extension',
85
+ ],
86
+ ],
87
+ [
88
+ 'name' => 'ExtensionMarketing',
89
+ 'description' => 'Policy to provide insight for marketing extensions.',
90
+ 'permissions' => [
91
+ 'see extension',
92
+ 'see extension-bundle',
93
+ 'list extension-bundle',
94
+ 'update extension-bundle',
95
+ 'see extension-analytic',
96
+ 'view extension-analytic',
97
+ 'list extension-analytic',
98
+ 'list extension',
99
+ ],
100
+ ],
101
+ [
102
+ 'name' => 'ExtensionFinance',
103
+ 'description' => 'Policy for managing finance and accounting for extensions.',
104
+ 'permissions' => [
105
+ 'see extension',
106
+ 'see extension-analytic',
107
+ 'view extension-analytic',
108
+ 'list extension-analytic',
109
+ 'see extension-payment',
110
+ 'view extension-payment',
111
+ 'list extension-payment',
112
+ 'onboard extension-payment',
113
+ 'list extension',
114
+ ],
115
+ ],
116
+ [
117
+ 'name' => 'ExtensionSales',
118
+ 'description' => 'Policy for managing sales reports of extensions.',
119
+ 'permissions' => [
120
+ 'see extension',
121
+ 'see extension-analytic',
122
+ 'view extension-analytic',
123
+ 'list extension-analytic',
124
+ 'see extension-payment',
125
+ 'view extension-payment',
126
+ 'list extension-payment',
127
+ 'see extension-purchase',
128
+ 'view extension-purchase',
129
+ 'list extension-purchase',
130
+ 'list extension',
131
+ ],
132
+ ],
133
+ [
134
+ 'name' => 'ExtensionManager',
135
+ 'description' => 'Policy for complete management of extensions.',
136
+ 'permissions' => [
137
+ 'see extension',
138
+ 'see extension-bundle',
139
+ 'list extension-bundle',
140
+ 'view extension-bundle',
141
+ 'create extension-bundle',
142
+ 'update extension-bundle',
143
+ 'delete extension-bundle',
144
+ 'submit extension-bundle',
145
+ 'see extension-analytic',
146
+ 'view extension-analytic',
147
+ 'list extension-analytic',
148
+ 'see extension-payment',
149
+ 'view extension-payment',
150
+ 'list extension-payment',
151
+ 'list extension',
152
+ ],
153
+ ],
154
+ ];
155
+
156
+ /**
157
+ * Roles provided by this schema.
158
+ */
159
+ public array $roles = [
160
+ [
161
+ 'name' => 'Extension Developer',
162
+ 'description' => 'Role for developing and publishing extensions.',
163
+ 'policies' => [
164
+ 'ExtensionDeveloper',
165
+ ],
166
+ ],
167
+ [
168
+ 'name' => 'Quality Assurance',
169
+ 'description' => 'Role for testing extensions.',
170
+ 'permissions' => [
171
+ 'see extension',
172
+ 'list extension',
173
+ 'view extension',
174
+ 'see extension-bundle',
175
+ 'list extension-bundle',
176
+ 'view extension-bundle',
177
+ 'create extension-bundle',
178
+ 'update extension-bundle',
179
+ 'delete extension-bundle',
180
+ 'submit extension-bundle',
181
+ ],
182
+ ],
183
+ [
184
+ 'name' => 'Extension Auditor',
185
+ 'description' => 'Role for auditing and creating reports for extensions.',
186
+ 'permissions' => [
187
+ 'see extension',
188
+ 'list extension',
189
+ 'view extension',
190
+ 'see extension-bundle',
191
+ 'list extension-bundle',
192
+ 'view extension-bundle',
193
+ 'see extension-analytic',
194
+ 'list extension-analytic',
195
+ 'view extension-analytic',
196
+ 'see extension-payment',
197
+ 'list extension-payment',
198
+ 'view extension-payment',
199
+ ],
200
+ ],
201
+ [
202
+ 'name' => 'Extension Admin',
203
+ 'description' => 'Role for full control of extensions.',
204
+ 'permissions' => [
205
+ '* extension',
206
+ '* extension-bundle',
207
+ '* extension-analytic',
208
+ '* extension-payment',
209
+ '* extension-purchase',
210
+ '* extension-install',
211
+ '* registry-token',
212
+ ],
213
+ ],
214
+ ];
215
+ }
@@ -0,0 +1,132 @@
1
+ registry-bridge:
2
+ extension-name: جسر السجل
3
+ common:
4
+ install: تثبيت
5
+ uninstall: إلغاء التثبيت
6
+ approve: الموافقة
7
+ reject: الرفض
8
+ details: التفاصيل
9
+ about: حول
10
+ about-extension: حول {extensionName}
11
+ component:
12
+ extension-details-modal:
13
+ extension: الامتداد
14
+ author: المؤلف
15
+ description: الوصف
16
+ overview: نظرة عامة
17
+ details: التفاصيل
18
+ version: الإصدار
19
+ updated: تم التحديث
20
+ website: الموقع الإلكتروني
21
+ self-managed: مُدار ذاتيًا
22
+ self-managed-help-text: الامتداد المُدار ذاتيًا مصمم للمستخدمين الذين يستضيفون Fleetbase على خوادمهم الخاصة، خارج بيئة السحابة/SaaS. تتطلب هذه الامتدادات التثبيت والتكوين اليدوي. إذا كنت تستخدم Fleetbase كنسخة مستضافة ذاتيًا، يمكنك استخدام هذه الامتدادات لإضافة ميزات إضافية. ومع ذلك، فهي غير متاحة لمستخدمي السحابة/SaaS.
23
+ extension-pending-publish-viewer:
24
+ content-panel-title: الامتدادات المعلقة للنشر
25
+ focused-extension-title: >
26
+ تفاصيل {extensionName}
27
+ download-bundle: تحميل الحزمة
28
+ view-details: عرض التفاصيل
29
+ no-extensions-awaiting-publish: لا توجد امتدادات تنتظر النشر
30
+ extension-reviewer-control:
31
+ content-panel-title: الامتدادات التي تنتظر المراجعة
32
+ focused-extension-title: >
33
+ تفاصيل {extensionName}
34
+ approve-confirm-title: هل أنت متأكد أنك تريد الموافقة على هذا الامتداد؟
35
+ approve-confirm-body: يجب اختبار هذا الامتداد بدقة في بيئة التطوير والاختبار واختباره من حيث الامتثال وكذلك قضايا الأمان. بمجرد الموافقة، سيتعين نشر الامتداد يدويًا في السجل.
36
+ decline-confirm-title: هل أنت متأكد أنك تريد رفض هذا الامتداد؟
37
+ decline-confirm-body: سيتم رفض هذا الامتداد، ولكن سيتم منح المؤلف فرصة لإجراء التصحيحات وإعادة التقديم للمراجعة.
38
+ download-bundle: تحميل الحزمة
39
+ view-details: عرض التفاصيل
40
+ approve: الموافقة
41
+ reject: الرفض
42
+ no-extensions-awaiting-review: لا توجد امتدادات تنتظر المراجعة
43
+ installed:
44
+ title: الامتدادات المثبتة
45
+ purchased:
46
+ title: الامتدادات المشتراة
47
+ developers:
48
+ extensions:
49
+ extensions: الامتدادات
50
+ create-new-extension: إنشاء امتداد جديد
51
+ new-extension: امتداد جديد
52
+ extension: الامتداد
53
+ submission: التقديم
54
+ details: التفاصيل
55
+ bundles: الحزم
56
+ monetize: تحقيق الدخل
57
+ explore:
58
+ explore-extensions: استكشاف الامتدادات
59
+ explore-category-extensions: >
60
+ امتدادات {categoryName}
61
+ extension-form:
62
+ package-name: اسم الحزمة
63
+ package-json-name: اسم package.json
64
+ composer-json-name: اسم composer.json
65
+ submission-success-message: تم تقديم الامتداد وهو في انتظار المراجعة.
66
+ extension-category: الفئة
67
+ extension-category-help-text: اختر الفئة التي سيتمكن المستخدمون من العثور على الامتداد فيها.
68
+ extension-select-category: اختر الفئة
69
+ extension-name: اسم الامتداد
70
+ extension-icon: أيقونة الامتداد
71
+ upload-extension-icon: تحميل الأيقونة
72
+ extension-description: الوصف
73
+ extension-tags: العلامات
74
+ extension-add-tags: إضافة الكلمات الرئيسية والعلامات
75
+ extension-promotional-text: النص الترويجي
76
+ details-content-block: تفاصيل الامتداد
77
+ extension-listing-details: تفاصيل الإدراج
78
+ extension-subtitle: العنوان الفرعي
79
+ extension-website-url: عنوان الموقع الإلكتروني
80
+ extension-repo-url: عنوان المستودع
81
+ extension-support-url: عنوان الدعم
82
+ extension-privacy-policy-url: عنوان سياسة الخصوصية
83
+ extension-tos-url: عنوان شروط الخدمة
84
+ extension-primary-language: اللغة الأساسية
85
+ extension-version: الإصدار
86
+ extension-copyright: حقوق النشر
87
+ extension-copyright-help-text: أدخل إشعار حقوق النشر للامتداد الخاص بك. يتضمن هذا عادةً السنة واسم مالك حقوق النشر أو الشركة.
88
+ extension-payment-required: يتطلب الدفع
89
+ extension-payment-required-help-text: يحدد ما إذا كان الدفع أو الاشتراك مطلوبًا للمستخدمين لتثبيت هذا الامتداد.
90
+ extension-price: السعر
91
+ extension-price-help-text: حدد السعر العادي للامتداد. هذا هو التكلفة القياسية للمستخدمين لشراء أو الوصول إلى الامتداد.
92
+ extension-sale-price: سعر البيع
93
+ extension-sale-price-help-text: إذا كان الامتداد معروضًا حاليًا بسعر مخفض، أدخل سعر البيع هنا. يجب أن يكون أقل من السعر العادي.
94
+ extension-subscription-required: يتطلب الاشتراك
95
+ extension-subscription-required-help-text: يحدد ما إذا كان يجب على المستخدم أن يكون لديه اشتراك لتثبيت واستخدام هذا الامتداد.
96
+ extension-subscription-billing-period: فترة الفوترة للاشتراك
97
+ extension-subscription-billing-period-placeholder: اختر فترة الفوترة
98
+ extension-subscription-billing-period-help-text: حدد فترة الفوترة للاشتراك. تشمل الأمثلة شهريًا، ربع سنويًا، سنويًا، أو فترات مخصصة أخرى.
99
+ extension-subscription-amount: مبلغ الاشتراك
100
+ extension-subscription-amount-help-text: أدخل المبلغ القياسي الذي سيتم تحصيله للاشتراك. يجب أن يعكس هذا السعر العادي قبل أي خصومات أو تعديلات في التسعير المتدرج.
101
+ extension-subscription-model: نموذج الاشتراك
102
+ extension-subscription-model-help-text: يحدد نوع نموذج تسعير الاشتراك الذي يجب اتباعه. تشمل الخيارات السعر الثابت، المتدرج، والاشتراك القائم على الاستخدام.
103
+ extension-subscription-tiers: مستويات الاشتراك
104
+ extension-subscription-tiers-help-text: حدد مستويات التسعير المختلفة للاشتراك، إذا كان ذلك ممكنًا. قم بتضمين تفاصيل مثل مستويات الاشتراك، التسعير لكل مستوى، وأي ميزات أو قيود محددة لكل مستوى.
105
+ extension-name-help-text: أدخل الاسم الرسمي للامتداد. سيتم عرض هذا كالمعرف الأساسي للمستخدمين.
106
+ extension-icon-help-text: قم بتحميل أيقونة تمثل الامتداد. سيتم استخدام هذه الأيقونة في القوائم ونتائج البحث.
107
+ extension-description-help-text: قدم وصفًا تفصيليًا لما يفعله الامتداد وميزاته الرئيسية. تساعد هذه المعلومات المستخدمين على فهم وظيفة الامتداد.
108
+ extension-tags-help-text: أضف الكلمات الرئيسية والعلامات ذات الصلة التي تصف الامتداد، مما يساعد المستخدمين في العثور عليه من خلال البحث.
109
+ extension-promotional-text-help-text: اكتب نصًا ترويجيًا لتسليط الضوء على نقاط البيع الفريدة للامتداد. سيتم استخدام هذا النص في التسويق أو الأقسام المميزة.
110
+ extension-subtitle-help-text: قدم عنوانًا فرعيًا موجزًا يقدم سياقًا إضافيًا أو معلومات حول الامتداد.
111
+ extension-website-url-help-text: أدخل عنوان URL للموقع الرسمي للامتداد أو صفحة المنتج لمزيد من المعلومات.
112
+ extension-repo-url-help-text: قدم عنوان URL لمستودع كود الامتداد (مثل GitHub، GitLab)، إذا كان ذلك ممكنًا.
113
+ extension-support-url-help-text: حدد عنوان URL حيث يمكن للمستخدمين الحصول على الدعم، طرح الأسئلة، أو الإبلاغ عن المشكلات المتعلقة بالامتداد.
114
+ extension-privacy-policy-url-help-text: رابط إلى وثيقة سياسة الخصوصية التي توضح كيفية التعامل مع بيانات المستخدم وحمايتها.
115
+ extension-tos-url-help-text: رابط إلى وثيقة شروط الخدمة التي يجب على المستخدمين الموافقة عليها قبل استخدام الامتداد.
116
+ extension-primary-language-help-text: حدد اللغة الأساسية التي تم تطوير الامتداد بها أو المتاحة بها.
117
+ extension-version-help-text: حدد الإصدار الحالي للامتداد. هذا مهم لتتبع التحديثات والتوافق.
118
+ extension-payment-details: تفاصيل الدفع
119
+ extension-screenshots: لقطات الشاشة
120
+ upload-screenshots: تحميل لقطات الشاشة
121
+ submit-for-review: تقديم للمراجعة
122
+ extension-bundle: حزمة الامتداد
123
+ extension-upload-bundle: تحميل حزمة الامتداد
124
+ details: التفاصيل
125
+ bundle-id: معرف الحزمة
126
+ bundle-id-help-text: معرف الحزمة المنشورة حاليًا.
127
+ extension-id: معرف الامتداد
128
+ extension-id-help-text: المعرف الفريد لهذا الامتداد.
129
+ bundles: الحزم
130
+ upload-new-bundle: تحميل حزمة جديدة
131
+ self-managed: مُدار ذاتيًا
132
+ self-managed-help-text: قم بتمكين هذا الخيار إذا كان الموديل مخصصًا للحالات المستضافة ذاتيًا فقط. من خلال تحديد هذا، لن يكون الموديل متاحًا للتثبيت على إصدار السحابة/SaaS من Fleetbase ويجب تثبيته يدويًا بواسطة المستخدم على الخادم الخاص به.
@@ -12,6 +12,7 @@ registry-bridge:
12
12
  extension-details-modal:
13
13
  extension: Extension
14
14
  author: Author
15
+ description: Description
15
16
  overview: Overview
16
17
  details: Details
17
18
  version: Version
@@ -0,0 +1,131 @@
1
+ registry-bridge:
2
+ extension-name: Cầu nối Đăng ký
3
+ common:
4
+ install: Cài đặt
5
+ uninstall: Gỡ cài đặt
6
+ approve: Phê duyệt
7
+ reject: Từ chối
8
+ details: Chi tiết
9
+ about: Giới thiệu
10
+ about-extension: Giới thiệu {extensionName}
11
+ component:
12
+ extension-details-modal:
13
+ extension: Tiện ích mở rộng
14
+ author: Tác giả
15
+ overview: Tổng quan
16
+ details: Chi tiết
17
+ version: Phiên bản
18
+ updated: Cập nhật
19
+ website: Trang web
20
+ self-managed: Tự quản lý
21
+ self-managed-help-text: Một tiện ích mở rộng tự quản lý được thiết kế cho người dùng tự lưu trữ Fleetbase trên máy chủ của riêng họ, ngoài môi trường đám mây/SaaS. Những tiện ích mở rộng này yêu cầu cài đặt và cấu hình thủ công. Nếu bạn đang sử dụng Fleetbase như một phiên bản tự lưu trữ, bạn có thể sử dụng những tiện ích mở rộng này để thêm các tính năng bổ sung. Tuy nhiên, chúng không có sẵn cho người dùng đám mây/SaaS.
22
+ extension-pending-publish-viewer:
23
+ content-panel-title: Các tiện ích mở rộng đang chờ xuất bản
24
+ focused-extension-title: >
25
+ Chi tiết {extensionName}
26
+ download-bundle: Tải gói
27
+ view-details: Xem chi tiết
28
+ no-extensions-awaiting-publish: Không có tiện ích mở rộng nào đang chờ xuất bản
29
+ extension-reviewer-control:
30
+ content-panel-title: Các tiện ích mở rộng đang chờ xem xét
31
+ focused-extension-title: >
32
+ Chi tiết {extensionName}
33
+ approve-confirm-title: Bạn có chắc chắn muốn phê duyệt tiện ích mở rộng này không?
34
+ approve-confirm-body: Tiện ích mở rộng này nên được kiểm tra kỹ lưỡng trong cả môi trường phát triển và kiểm thử và kiểm tra tuân thủ cũng như các vấn đề bảo mật. Khi được phê duyệt, tiện ích mở rộng sẽ cần phải được xuất bản thủ công lên đăng ký.
35
+ decline-confirm-title: Bạn có chắc chắn muốn từ chối tiện ích mở rộng này không?
36
+ decline-confirm-body: Tiện ích mở rộng này sẽ bị từ chối, nhưng tác giả sẽ có cơ hội để sửa chữa và nộp lại để xem xét.
37
+ download-bundle: Tải gói
38
+ view-details: Xem chi tiết
39
+ approve: Phê duyệt
40
+ reject: Từ chối
41
+ no-extensions-awaiting-review: Không có tiện ích mở rộng nào đang chờ xem xét
42
+ installed:
43
+ title: Các tiện ích mở rộng đã cài đặt
44
+ purchased:
45
+ title: Các tiện ích mở rộng đã mua
46
+ developers:
47
+ extensions:
48
+ extensions: Tiện ích mở rộng
49
+ create-new-extension: Tạo tiện ích mở rộng mới
50
+ new-extension: Tiện ích mở rộng mới
51
+ extension: Tiện ích mở rộng
52
+ submission: Nộp đơn
53
+ details: Chi tiết
54
+ bundles: Gói
55
+ monetize: Kiếm tiền
56
+ explore:
57
+ explore-extensions: Khám phá tiện ích mở rộng
58
+ explore-category-extensions: >
59
+ Tiện ích mở rộng {categoryName}
60
+ extension-form:
61
+ package-name: Tên gói
62
+ package-json-name: tên package.json
63
+ composer-json-name: tên composer.json
64
+ submission-success-message: Tiện ích mở rộng đã được nộp và đang chờ xem xét.
65
+ extension-category: Danh mục
66
+ extension-category-help-text: Chọn danh mục mà người dùng sẽ có thể tìm thấy tiện ích mở rộng của bạn.
67
+ extension-select-category: Chọn danh mục
68
+ extension-name: Tên tiện ích mở rộng
69
+ extension-icon: Biểu tượng tiện ích mở rộng
70
+ upload-extension-icon: Tải lên biểu tượng
71
+ extension-description: Mô tả
72
+ extension-tags: Thẻ
73
+ extension-add-tags: Thêm từ khóa và thẻ
74
+ extension-promotional-text: Văn bản quảng cáo
75
+ details-content-block: Chi tiết tiện ích mở rộng
76
+ extension-listing-details: Chi tiết niêm yết
77
+ extension-subtitle: Phụ đề
78
+ extension-website-url: URL trang web
79
+ extension-repo-url: URL kho lưu trữ
80
+ extension-support-url: URL hỗ trợ
81
+ extension-privacy-policy-url: URL chính sách bảo mật
82
+ extension-tos-url: URL điều khoản dịch vụ
83
+ extension-primary-language: Ngôn ngữ chính
84
+ extension-version: Phiên bản
85
+ extension-copyright: Bản quyền
86
+ extension-copyright-help-text: Nhập thông báo bản quyền cho tiện ích mở rộng của bạn. Thông thường bao gồm năm và tên của chủ sở hữu bản quyền hoặc công ty.
87
+ extension-payment-required: Cần thanh toán
88
+ extension-payment-required-help-text: Xác định xem có cần thanh toán hoặc đăng ký để người dùng cài đặt tiện ích mở rộng này không.
89
+ extension-price: Giá
90
+ extension-price-help-text: Xác định giá thông thường của tiện ích mở rộng. Đây là chi phí tiêu chuẩn để người dùng mua hoặc truy cập tiện ích mở rộng.
91
+ extension-sale-price: Giá bán
92
+ extension-sale-price-help-text: Nếu tiện ích mở rộng hiện đang được cung cấp với mức giá giảm, hãy nhập giá bán ở đây. Điều này nên thấp hơn giá thông thường.
93
+ extension-subscription-required: Cần đăng ký
94
+ extension-subscription-required-help-text: Xác định xem người dùng phải có đăng ký để cài đặt và sử dụng tiện ích mở rộng này không.
95
+ extension-subscription-billing-period: Thời gian thanh toán đăng ký
96
+ extension-subscription-billing-period-placeholder: Chọn thời gian thanh toán
97
+ extension-subscription-billing-period-help-text: Xác định thời gian thanh toán cho đăng ký. Ví dụ bao gồm hàng tháng, hàng quý, hàng năm hoặc các khoảng thời gian tùy chỉnh khác.
98
+ extension-subscription-amount: Số tiền đăng ký
99
+ extension-subscription-amount-help-text: Nhập số tiền tiêu chuẩn được tính cho đăng ký. Điều này nên phản ánh giá thông thường trước bất kỳ giảm giá hoặc điều chỉnh giá theo cấp độ nào.
100
+ extension-subscription-model: Mô hình đăng ký
101
+ extension-subscription-model-help-text: Xác định loại mô hình giá đăng ký. Các tùy chọn bao gồm giá cố định, theo cấp độ và dựa trên mức sử dụng.
102
+ extension-subscription-tiers: Các cấp đăng ký
103
+ extension-subscription-tiers-help-text: Xác định các cấp giá khác nhau cho đăng ký, nếu có. Bao gồm chi tiết như các cấp độ, giá cho mỗi cấp và bất kỳ tính năng hoặc hạn chế cụ thể nào của mỗi cấp.
104
+ extension-name-help-text: Nhập tên chính thức của tiện ích mở rộng. Tên này sẽ được hiển thị như một chỉ định chính cho người dùng.
105
+ extension-icon-help-text: Tải lên một biểu tượng đại diện cho tiện ích mở rộng. Biểu tượng này sẽ được sử dụng trong các danh sách và kết quả tìm kiếm.
106
+ extension-description-help-text: Cung cấp một mô tả chi tiết về những gì tiện ích mở rộng làm và các tính năng chính của nó. Thông tin này giúp người dùng hiểu chức năng của tiện ích mở rộng.
107
+ extension-tags-help-text: Thêm từ khóa và thẻ liên quan mô tả tiện ích mở rộng, giúp người dùng tìm thấy nó qua tìm kiếm.
108
+ extension-promotional-text-help-text: Viết văn bản quảng cáo để làm nổi bật các điểm bán hàng độc đáo của tiện ích mở rộng. Văn bản này sẽ được sử dụng trong các phần tiếp thị hoặc nổi bật.
109
+ extension-subtitle-help-text: Cung cấp một phụ đề ngắn gọn cung cấp ngữ cảnh hoặc thông tin bổ sung về tiện ích mở rộng.
110
+ extension-website-url-help-text: Nhập URL đến trang web chính thức của tiện ích mở rộng hoặc trang sản phẩm để biết thêm thông tin.
111
+ extension-repo-url-help-text: "Cung cấp URL đến kho lưu trữ mã của tiện ích mở rộng (ví dụ: GitHub, GitLab), nếu có."
112
+ extension-support-url-help-text: Xác định URL nơi người dùng có thể nhận hỗ trợ, đặt câu hỏi hoặc báo cáo sự cố với tiện ích mở rộng.
113
+ extension-privacy-policy-url-help-text: Liên kết đến tài liệu chính sách bảo mật chi tiết cách dữ liệu người dùng được xử lý và bảo vệ.
114
+ extension-tos-url-help-text: Liên kết đến tài liệu Điều khoản dịch vụ mà người dùng phải đồng ý trước khi sử dụng tiện ích mở rộng.
115
+ extension-primary-language-help-text: Xác định ngôn ngữ chính mà tiện ích mở rộng được phát triển hoặc có sẵn.
116
+ extension-version-help-text: Cho biết phiên bản hiện tại của tiện ích mở rộng. Điều này quan trọng để theo dõi các bản cập nhật và khả năng tương thích.
117
+ extension-payment-details: Chi tiết thanh toán
118
+ extension-screenshots: Ảnh chụp màn hình
119
+ upload-screenshots: Tải lên ảnh chụp màn hình
120
+ submit-for-review: Nộp để xem xét
121
+ extension-bundle: Gói tiện ích mở rộng
122
+ extension-upload-bundle: Tải lên gói tiện ích mở rộng
123
+ details: Chi tiết
124
+ bundle-id: ID gói
125
+ bundle-id-help-text: Định danh của gói hiện đang được xuất bản.
126
+ extension-id: ID tiện ích mở rộng
127
+ extension-id-help-text: Định danh duy nhất cho tiện ích mở rộng này.
128
+ bundles: Gói
129
+ upload-new-bundle: Tải lên gói mới
130
+ self-managed: Tự quản lý
131
+ self-managed-help-text: Kích hoạt tùy chọn này nếu mô-đun của bạn chỉ dành cho các phiên bản tự lưu trữ. Khi chọn tùy chọn này, mô-đun sẽ không có sẵn để cài đặt trên phiên bản đám mây/SaaS của Fleetbase và phải được người dùng cài đặt thủ công trên máy chủ của riêng họ.