diff --git a/README.md b/README.md index 5e878ef..653d868 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ An instance initializer registers the descriptors with the `resource-registry` s Opening a resource loads the Fleet-Ops engine on demand and hands the record to its action service, so the engine bundle is only fetched when someone clicks. When Fleet-Ops itself boots it replaces these descriptors with its own, which open the same panels directly. -The helpers behind the descriptors and the styled placeholder images are exported from `@fleetbase/fleetops-data/utils/resource-descriptors` and `@fleetbase/fleetops-data/utils/placeholder-images` for other packages that build descriptors. +The helpers behind the descriptors and the styled placeholder images are exported from `@fleetbase/fleetops-data/utils/resource-descriptors` and `@fleetbase/fleetops-data/utils/placeholder-images` for other packages that build descriptors. `@fleetbase/fleetops-data/utils/identity-stub` builds a stand-in record for a relation a row only knows by name (`driver_name`, `pickupName`), so an identity cell can render it and load the real record on demand. ## Contributing diff --git a/addon/utils/identity-stub.js b/addon/utils/identity-stub.js new file mode 100644 index 0000000..ee67944 --- /dev/null +++ b/addon/utils/identity-stub.js @@ -0,0 +1,56 @@ +import { get } from '@ember/object'; + +const inflight = new WeakMap(); + +/** + * A stand-in for a related record a row only knows by name: enough for an + * identity cell, pill or summary to render (`name`, `resourceType`) and a + * `loadResource()` they call when the real record is needed. The stub never + * carries a UUID in an identifier field, so nothing renders one as a plate + * or an id, and concurrent loads of the same row share one request. + * + * `load` receives the row and returns the record (or a promise of it); with + * no `load`, `loadResource()` resolves to null and the stub stays static. + */ +export function buildIdentityStub(row, { type, nameKey = `${type}_name`, name, load, extra = {} } = {}) { + const label = name ?? (row ? get(row, nameKey) : null); + + if (!label) { + return null; + } + + return { + ...extra, + name: label, + display_name: label, + displayName: label, + resourceType: type, + isIdentityStub: true, + loadResource: async () => { + if (typeof load !== 'function') { + return null; + } + + let loads = inflight.get(row); + + if (!loads) { + loads = new Map(); + inflight.set(row, loads); + } + + if (!loads.has(type)) { + loads.set( + type, + Promise.resolve() + .then(() => load(row)) + .catch(() => null) + .finally(() => loads.delete(type)) + ); + } + + return loads.get(type); + }, + }; +} + +export default buildIdentityStub; diff --git a/addon/utils/resource-descriptors/helpers.js b/addon/utils/resource-descriptors/helpers.js index 07e88f7..3948d4c 100644 --- a/addon/utils/resource-descriptors/helpers.js +++ b/addon/utils/resource-descriptors/helpers.js @@ -107,11 +107,10 @@ export function typeLabel(value) { return null; } - const head = String(value) - .split(':')[0] - .replace(/-fleet-ops$/, '') - .replace(/[-_]+/g, ' ') - .trim(); + const [prefix, ...rest] = String(value).split(':'); + // "customer-fleet-ops:contact" reads as "Customer"; a bare "fleet-ops:contact" has + // nothing before the extension name, so it reads as the type after the colon. + const head = (prefix.replace(/-?fleet-ops$/, '') || rest.join(':')).replace(/[-_]+/g, ' ').trim(); return head ? head.charAt(0).toUpperCase() + head.slice(1) : null; } @@ -138,7 +137,18 @@ export function polymorphicType(record, relationName, typeAttr) { /** The universe extension manager, through either of its service names. */ export function extensionManager(owner) { - return lookupService(owner, 'universe/extension-manager') ?? lookupService(owner, 'universe')?.extensionManager ?? null; + const direct = lookupService(owner, 'universe/extension-manager'); + + if (direct) { + return direct; + } + + // The universe service resolves its manager lazily and throws on a host that has none. + try { + return lookupService(owner, 'universe')?.extensionManager ?? null; + } catch { + return null; + } } /** Whether an engine is installed, so a resource it owns has somewhere to open. */ diff --git a/addon/components/cell/contact-identity.hbs b/app/components/cell/contact-identity.hbs similarity index 100% rename from addon/components/cell/contact-identity.hbs rename to app/components/cell/contact-identity.hbs diff --git a/app/components/cell/contact-identity.js b/app/components/cell/contact-identity.js deleted file mode 100644 index 3a97c2a..0000000 --- a/app/components/cell/contact-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/contact-identity'; diff --git a/addon/components/cell/customer-identity.hbs b/app/components/cell/customer-identity.hbs similarity index 100% rename from addon/components/cell/customer-identity.hbs rename to app/components/cell/customer-identity.hbs diff --git a/app/components/cell/customer-identity.js b/app/components/cell/customer-identity.js deleted file mode 100644 index 7832618..0000000 --- a/app/components/cell/customer-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/customer-identity'; diff --git a/addon/components/cell/driver-identity.hbs b/app/components/cell/driver-identity.hbs similarity index 100% rename from addon/components/cell/driver-identity.hbs rename to app/components/cell/driver-identity.hbs diff --git a/app/components/cell/driver-identity.js b/app/components/cell/driver-identity.js deleted file mode 100644 index 431d0ec..0000000 --- a/app/components/cell/driver-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/driver-identity'; diff --git a/addon/components/cell/fleet-identity.hbs b/app/components/cell/fleet-identity.hbs similarity index 100% rename from addon/components/cell/fleet-identity.hbs rename to app/components/cell/fleet-identity.hbs diff --git a/app/components/cell/fleet-identity.js b/app/components/cell/fleet-identity.js deleted file mode 100644 index 263b22b..0000000 --- a/app/components/cell/fleet-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/fleet-identity'; diff --git a/addon/components/cell/order-identity.hbs b/app/components/cell/order-identity.hbs similarity index 100% rename from addon/components/cell/order-identity.hbs rename to app/components/cell/order-identity.hbs diff --git a/app/components/cell/order-identity.js b/app/components/cell/order-identity.js deleted file mode 100644 index 4bb3613..0000000 --- a/app/components/cell/order-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/order-identity'; diff --git a/addon/components/cell/place-identity.hbs b/app/components/cell/place-identity.hbs similarity index 100% rename from addon/components/cell/place-identity.hbs rename to app/components/cell/place-identity.hbs diff --git a/app/components/cell/place-identity.js b/app/components/cell/place-identity.js deleted file mode 100644 index eb31bd1..0000000 --- a/app/components/cell/place-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/place-identity'; diff --git a/addon/components/cell/vehicle-identity.hbs b/app/components/cell/vehicle-identity.hbs similarity index 100% rename from addon/components/cell/vehicle-identity.hbs rename to app/components/cell/vehicle-identity.hbs diff --git a/app/components/cell/vehicle-identity.js b/app/components/cell/vehicle-identity.js deleted file mode 100644 index 9e54d46..0000000 --- a/app/components/cell/vehicle-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/vehicle-identity'; diff --git a/addon/components/cell/vendor-identity.hbs b/app/components/cell/vendor-identity.hbs similarity index 100% rename from addon/components/cell/vendor-identity.hbs rename to app/components/cell/vendor-identity.hbs diff --git a/app/components/cell/vendor-identity.js b/app/components/cell/vendor-identity.js deleted file mode 100644 index 9ef5d6c..0000000 --- a/app/components/cell/vendor-identity.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/cell/vendor-identity'; diff --git a/addon/components/contact/pill.hbs b/app/components/contact/pill.hbs similarity index 100% rename from addon/components/contact/pill.hbs rename to app/components/contact/pill.hbs diff --git a/app/components/contact/pill.js b/app/components/contact/pill.js deleted file mode 100644 index c7a297a..0000000 --- a/app/components/contact/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/contact/pill'; diff --git a/addon/components/contact/summary.hbs b/app/components/contact/summary.hbs similarity index 100% rename from addon/components/contact/summary.hbs rename to app/components/contact/summary.hbs diff --git a/app/components/contact/summary.js b/app/components/contact/summary.js deleted file mode 100644 index 7e7c800..0000000 --- a/app/components/contact/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/contact/summary'; diff --git a/addon/components/customer/pill.hbs b/app/components/customer/pill.hbs similarity index 100% rename from addon/components/customer/pill.hbs rename to app/components/customer/pill.hbs diff --git a/app/components/customer/pill.js b/app/components/customer/pill.js deleted file mode 100644 index aea27bb..0000000 --- a/app/components/customer/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/customer/pill'; diff --git a/addon/components/customer/summary.hbs b/app/components/customer/summary.hbs similarity index 100% rename from addon/components/customer/summary.hbs rename to app/components/customer/summary.hbs diff --git a/app/components/customer/summary.js b/app/components/customer/summary.js deleted file mode 100644 index 899a942..0000000 --- a/app/components/customer/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/customer/summary'; diff --git a/addon/components/driver/pill.hbs b/app/components/driver/pill.hbs similarity index 100% rename from addon/components/driver/pill.hbs rename to app/components/driver/pill.hbs diff --git a/app/components/driver/pill.js b/app/components/driver/pill.js deleted file mode 100644 index 3490441..0000000 --- a/app/components/driver/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/driver/pill'; diff --git a/addon/components/driver/summary.hbs b/app/components/driver/summary.hbs similarity index 100% rename from addon/components/driver/summary.hbs rename to app/components/driver/summary.hbs diff --git a/app/components/driver/summary.js b/app/components/driver/summary.js deleted file mode 100644 index e96be83..0000000 --- a/app/components/driver/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/driver/summary'; diff --git a/addon/components/fleet/pill.hbs b/app/components/fleet/pill.hbs similarity index 100% rename from addon/components/fleet/pill.hbs rename to app/components/fleet/pill.hbs diff --git a/app/components/fleet/pill.js b/app/components/fleet/pill.js deleted file mode 100644 index d849910..0000000 --- a/app/components/fleet/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/fleet/pill'; diff --git a/addon/components/fleet/summary.hbs b/app/components/fleet/summary.hbs similarity index 100% rename from addon/components/fleet/summary.hbs rename to app/components/fleet/summary.hbs diff --git a/app/components/fleet/summary.js b/app/components/fleet/summary.js deleted file mode 100644 index e8a90c5..0000000 --- a/app/components/fleet/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/fleet/summary'; diff --git a/addon/components/order/pill.hbs b/app/components/order/pill.hbs similarity index 100% rename from addon/components/order/pill.hbs rename to app/components/order/pill.hbs diff --git a/app/components/order/pill.js b/app/components/order/pill.js deleted file mode 100644 index f3ceb03..0000000 --- a/app/components/order/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/order/pill'; diff --git a/addon/components/order/summary.hbs b/app/components/order/summary.hbs similarity index 100% rename from addon/components/order/summary.hbs rename to app/components/order/summary.hbs diff --git a/app/components/order/summary.js b/app/components/order/summary.js deleted file mode 100644 index 24bf123..0000000 --- a/app/components/order/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/order/summary'; diff --git a/addon/components/place/pill.hbs b/app/components/place/pill.hbs similarity index 100% rename from addon/components/place/pill.hbs rename to app/components/place/pill.hbs diff --git a/app/components/place/pill.js b/app/components/place/pill.js deleted file mode 100644 index 2757d3a..0000000 --- a/app/components/place/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/place/pill'; diff --git a/addon/components/place/summary.hbs b/app/components/place/summary.hbs similarity index 100% rename from addon/components/place/summary.hbs rename to app/components/place/summary.hbs diff --git a/app/components/place/summary.js b/app/components/place/summary.js deleted file mode 100644 index 3ee08c9..0000000 --- a/app/components/place/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/place/summary'; diff --git a/addon/components/select-option/contact.hbs b/app/components/select-option/contact.hbs similarity index 100% rename from addon/components/select-option/contact.hbs rename to app/components/select-option/contact.hbs diff --git a/app/components/select-option/contact.js b/app/components/select-option/contact.js deleted file mode 100644 index fd9d008..0000000 --- a/app/components/select-option/contact.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/contact'; diff --git a/addon/components/select-option/customer.hbs b/app/components/select-option/customer.hbs similarity index 100% rename from addon/components/select-option/customer.hbs rename to app/components/select-option/customer.hbs diff --git a/app/components/select-option/customer.js b/app/components/select-option/customer.js deleted file mode 100644 index 26fd3c3..0000000 --- a/app/components/select-option/customer.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/customer'; diff --git a/addon/components/select-option/driver.hbs b/app/components/select-option/driver.hbs similarity index 100% rename from addon/components/select-option/driver.hbs rename to app/components/select-option/driver.hbs diff --git a/app/components/select-option/driver.js b/app/components/select-option/driver.js deleted file mode 100644 index a08f3e0..0000000 --- a/app/components/select-option/driver.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/driver'; diff --git a/addon/components/select-option/fleet.hbs b/app/components/select-option/fleet.hbs similarity index 100% rename from addon/components/select-option/fleet.hbs rename to app/components/select-option/fleet.hbs diff --git a/app/components/select-option/fleet.js b/app/components/select-option/fleet.js deleted file mode 100644 index 3e6eba6..0000000 --- a/app/components/select-option/fleet.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/fleet'; diff --git a/addon/components/select-option/order.hbs b/app/components/select-option/order.hbs similarity index 100% rename from addon/components/select-option/order.hbs rename to app/components/select-option/order.hbs diff --git a/app/components/select-option/order.js b/app/components/select-option/order.js deleted file mode 100644 index 34dc932..0000000 --- a/app/components/select-option/order.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/order'; diff --git a/addon/components/select-option/place.hbs b/app/components/select-option/place.hbs similarity index 100% rename from addon/components/select-option/place.hbs rename to app/components/select-option/place.hbs diff --git a/app/components/select-option/place.js b/app/components/select-option/place.js deleted file mode 100644 index d78d889..0000000 --- a/app/components/select-option/place.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/place'; diff --git a/addon/components/select-option/vehicle.hbs b/app/components/select-option/vehicle.hbs similarity index 100% rename from addon/components/select-option/vehicle.hbs rename to app/components/select-option/vehicle.hbs diff --git a/app/components/select-option/vehicle.js b/app/components/select-option/vehicle.js deleted file mode 100644 index 3c49271..0000000 --- a/app/components/select-option/vehicle.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/vehicle'; diff --git a/addon/components/select-option/vendor.hbs b/app/components/select-option/vendor.hbs similarity index 100% rename from addon/components/select-option/vendor.hbs rename to app/components/select-option/vendor.hbs diff --git a/app/components/select-option/vendor.js b/app/components/select-option/vendor.js deleted file mode 100644 index da45f9c..0000000 --- a/app/components/select-option/vendor.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/select-option/vendor'; diff --git a/addon/components/vehicle/pill.hbs b/app/components/vehicle/pill.hbs similarity index 100% rename from addon/components/vehicle/pill.hbs rename to app/components/vehicle/pill.hbs diff --git a/app/components/vehicle/pill.js b/app/components/vehicle/pill.js deleted file mode 100644 index e1f78a1..0000000 --- a/app/components/vehicle/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/vehicle/pill'; diff --git a/addon/components/vehicle/summary.hbs b/app/components/vehicle/summary.hbs similarity index 100% rename from addon/components/vehicle/summary.hbs rename to app/components/vehicle/summary.hbs diff --git a/app/components/vehicle/summary.js b/app/components/vehicle/summary.js deleted file mode 100644 index 5dae0f5..0000000 --- a/app/components/vehicle/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/vehicle/summary'; diff --git a/addon/components/vendor/pill.hbs b/app/components/vendor/pill.hbs similarity index 100% rename from addon/components/vendor/pill.hbs rename to app/components/vendor/pill.hbs diff --git a/app/components/vendor/pill.js b/app/components/vendor/pill.js deleted file mode 100644 index fa810e5..0000000 --- a/app/components/vendor/pill.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/vendor/pill'; diff --git a/addon/components/vendor/summary.hbs b/app/components/vendor/summary.hbs similarity index 100% rename from addon/components/vendor/summary.hbs rename to app/components/vendor/summary.hbs diff --git a/app/components/vendor/summary.js b/app/components/vendor/summary.js deleted file mode 100644 index b9f4eee..0000000 --- a/app/components/vendor/summary.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@fleetbase/fleetops-data/components/vendor/summary'; diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters/application.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters/application.js.html new file mode 100644 index 0000000..70224c2 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters/application.js.html @@ -0,0 +1,796 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters/application.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters application.js

+
+ +
+ 47.91% + Statements + 23/48 +
+ + +
+ 25% + Branches + 8/32 +
+ + +
+ 33.33% + Functions + 3/9 +
+ + +
+ 47.91% + Lines + 23/48 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +86x +  +86x +86x +86x +  +  +  +  +  +  +  +  +86x +86x +86x +86x +  +  +  +86x +86x +86x +  +  +86x +86x +86x +  +  +  +  +  +  +  +  +  +  +86x +  +86x +  +  +  +86x +  +  +  +86x +  +  +  +86x +86x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +23x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import RESTAdapter from '@ember-data/adapter/rest';
+import AdapterError from '@ember-data/adapter/error';
+import { tracked } from '@glimmer/tracking';
+import { inject as service } from '@ember/service';
+import { storageFor } from 'ember-local-storage';
+import { get } from '@ember/object';
+import { isBlank } from '@ember/utils';
+import { isArray } from '@ember/array';
+import { dasherize } from '@ember/string';
+import { pluralize } from 'ember-inflector';
+import { decompress as decompressJson } from 'compress-json';
+import getUserOptions from '../utils/get-user-options';
+import config from 'ember-get-config';
+ 
+/* istanbul ignore if -- runs at import; whichever of adapters/application or services/fetch loads first sets the host, so the other can never enter */
+if (isBlank(config.API.host)) {
+    config.API.host = `${window.location.protocol}//${window.location.hostname}`;
+}
+const DEFAULT_ERROR_MESSAGE = 'Oops! Something went wrong. Please try again or contact support if the issue persists.';
+export default class ApplicationAdapter extends RESTAdapter {
+    /**
+     * Inject the `session` service
+     *
+     * @var {Service}
+     */
+    @service session;
+ 
+    /**
+     * Inject the `currentUser` service
+     *
+     * @var {Service}
+     */
+    @service currentUser;
+ 
+    /**
+     * User options for setting specific headers
+     *
+     * @var StorageObject
+     */
+    @storageFor('user-options') userOptions;
+ 
+    /**
+     * The default namespace for the adapter
+     *
+     * @var {String}
+     */
+    @tracked host;
+ 
+    /**
+     * The default namespace for adapter
+     *
+     * @var {String}
+     */
+    @tracked namespace;
+ 
+    /**
+     * Credentials
+     *
+     * @var {String}
+     */
+    @tracked credentials = 'include';
+ 
+    /**
+     * Mutable headers property.
+     *
+     * @var {Array}
+     */
+    @tracked _headers;
+ 
+    /**
+     * Creates an instance of ApplicationAdapter.
+     * @memberof ApplicationAdapter
+     */
+    constructor() {
+        super(...arguments);
+ 
+        this.host = get(config, 'API.host');
+        this.namespace = get(config, 'API.namespace');
+        this.headers = this.setupHeaders();
+    }
+ 
+    /**
+     * Setup headers that should be sent with request.
+     *
+     * @return {Object}
+     */
+    setupHeaders() {
+        const headers = {};
+        const userId = this.session.data.authenticated.user;
+        const userOptions = getUserOptions();
+        const isSandbox = get(userOptions, `${userId}:sandbox`) === true;
+        // `currentUser.setOption` dasherizes before storing, so the key written
+        // by `setOption('testKey', …)` is `<user>:test-key`. Reading `testKey`
+        // never matched anything, so this header was never sent.
+        const testKey = get(userOptions, `${userId}:test-key`);
+        let isAuthenticated = this.session.isAuthenticated;
+        let { token } = this.session.data.authenticated;
+ 
+        // If the session data is not yet available, check localStorage
+        Eif (!isAuthenticated) {
+            const localStorageSession = JSON.parse(window.localStorage.getItem('ember_simple_auth-session'));
+            Iif (localStorageSession) {
+                const { authenticated } = localStorageSession;
+                if (authenticated) {
+                    token = authenticated.token;
+                }
+ 
+                // Check isAuthenticated again
+                isAuthenticated = !!token;
+            }
+        }
+ 
+        headers['Content-Type'] = 'application/json';
+ 
+        Iif (isAuthenticated) {
+            headers['Authorization'] = `Bearer ${token}`;
+        }
+ 
+        Iif (isAuthenticated && isSandbox) {
+            headers['Access-Console-Sandbox'] = true;
+        }
+ 
+        Iif (isAuthenticated && !isBlank(testKey)) {
+            headers['Access-Console-Sandbox-Key'] = testKey;
+        }
+ 
+        this.headers = headers;
+        return this.headers;
+    }
+ 
+    /**
+     * Configure AJAX options for request, return as options hash
+     *
+     * @param {String} url
+     * @param {String} type The request type GET, POST, PUT, DELETE etc.
+     * @param {Object} options
+     *
+     * @return {Object}
+     */
+    ajaxOptions(url, type, options) {
+        this.setupHeaders();
+ 
+        const ajaxOptions = super.ajaxOptions(url, type, options);
+        ajaxOptions.credentials = this.credentials;
+ 
+        return ajaxOptions;
+    }
+ 
+    /**
+     * Dasherize the path for type
+     *
+     * @param {Object} type
+     */
+    pathForType(type) {
+        return dasherize(pluralize(type));
+    }
+ 
+    /**
+     * Handles the response from an AJAX request.
+     * It decompresses the payload if needed, checks for error responses, and returns an AdapterError for error scenarios.
+     * For valid responses, the handling is delegated to the superclass's handleResponse method.
+     *
+     * @param {number} status - HTTP status code of the response.
+     * @param {object} headers - Response headers.
+     * @param {object} payload - Response payload.
+     * @param {object} requestData - Original request data.
+     * @return {Object | AdapterError} - The response object or an AdapterError in case of errors.
+     */
+    handleResponse(status, headers, payload, requestData) {
+        const decompressedPayload = this.decompressPayload(payload, headers);
+        if (this.isErrorResponse(status, decompressedPayload)) {
+            const errors = this.getResponseErrors(decompressedPayload);
+            const errorMessage = this.getErrorMessage(errors);
+            return new AdapterError(errors, errorMessage);
+        }
+ 
+        return super.handleResponse(status, headers, decompressedPayload, requestData);
+    }
+ 
+    /**
+     * Decompresses the response payload if it's marked as compressed in the response headers.
+     *
+     * This method checks the response headers for a specific 'x-compressed-json' flag.
+     * If this flag is set, indicating that the response payload is compressed, the method
+     * decompresses the payload. The decompressed payload is then parsed as JSON and returned.
+     * If the payload is not compressed, it is returned as is.
+     *
+     * @param {object} payload - The original payload of the response.
+     * @param {object} headers - The headers of the response, used to check if the payload is compressed.
+     * @return {object} The decompressed payload if it was compressed, or the original payload otherwise.
+     */
+    decompressPayload(payload, headers) {
+        // Check if the response is compressed
+        if (headers['x-compressed-json'] === '1' || headers['x-compressed-json'] === 1) {
+            // Decompress the payload
+            const decompressedPayload = decompressJson(payload);
+            // Replace payload with decompressed json payload
+            payload = JSON.parse(decompressedPayload);
+        }
+ 
+        return payload;
+    }
+ 
+    /**
+     * Extracts the error message from a list of errors.
+     * Returns a default error message if the provided list is empty or undefined.
+     *
+     * @param {Array} errors - Array of error messages or objects.
+     * @return {string} - The extracted error message.
+     */
+    getErrorMessage(errors = []) {
+        return errors[0] ? errors[0] : DEFAULT_ERROR_MESSAGE;
+    }
+ 
+    /**
+     * Extracts errors from a payload.
+     * Assumes the payload contains an `errors` array; returns a default error message otherwise.
+     *
+     * @param {object} payload - The response payload.
+     * @return {Array} - Array of extracted errors or a default error message.
+     */
+    getResponseErrors(payload) {
+        return isArray(payload.errors) ? payload.errors : [DEFAULT_ERROR_MESSAGE];
+    }
+ 
+    /**
+     * Determines if the response status indicates an error.
+     * Checks both the HTTP status code and the presence of errors in the payload.
+     *
+     * @param {number} status - The HTTP status code.
+     * @param {object} payload - The response payload.
+     * @return {boolean} - True if the response indicates an error, false otherwise.
+     */
+    isErrorResponse(status, payload) {
+        return (status >= 400 && status < 600) || (!isBlank(payload) && isArray(payload.errors));
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters/index.html new file mode 100644 index 0000000..be444ed --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/adapters

+
+ +
+ 47.91% + Statements + 23/48 +
+ + +
+ 25% + Branches + 8/32 +
+ + +
+ 33.33% + Functions + 3/9 +
+ + +
+ 47.91% + Lines + 23/48 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
application.js +
+
47.91%23/4825%8/3233.33%3/947.91%23/48
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/base-contract.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/base-contract.js.html new file mode 100644 index 0000000..e961f73 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/base-contract.js.html @@ -0,0 +1,412 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/base-contract.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts base-contract.js

+
+ +
+ 0% + Statements + 0/10 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/9 +
+ + +
+ 0% + Lines + 0/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { tracked } from '@glimmer/tracking';
+ 
+/**
+ * Base class for all extension contracts
+ * Provides common functionality for validation, serialization, and option management
+ *
+ * Uses a two-phase construction pattern:
+ * 1. Constructor - Sets up initial state
+ * 2. setup() - Called after construction for validation and post-init logic
+ *
+ * @class BaseContract
+ */
+export default class BaseContract {
+    /* istanbul ignore next -- the constructor assigns _options unconditionally, so this default is never read */
+    @tracked _options = {};
+ 
+    constructor(options = {}) {
+        this._options = { ...options };
+        // Don't validate here - let subclasses set properties first
+    }
+ 
+    /**
+     * Setup method called after construction
+     * Subclasses should call super.setup() to trigger validation
+     *
+     * @method setup
+     */
+    setup() {
+        this.validate();
+    }
+ 
+    /**
+     * Validate the contract
+     * Override in subclasses to add specific validation logic
+     *
+     * @method validate
+     */
+    validate() {
+        // Base validation - override in subclasses
+    }
+ 
+    /**
+     * Get the plain object representation of this contract
+     *
+     * @method toObject
+     * @returns {Object} Plain object representation
+     */
+    toObject() {
+        return { ...this._options };
+    }
+ 
+    /**
+     * Set an option with method chaining support
+     *
+     * @method setOption
+     * @param {String} key The option key
+     * @param {*} value The option value
+     * @returns {BaseContract} This instance for chaining
+     */
+    setOption(key, value) {
+        this._options[key] = value;
+        return this;
+    }
+ 
+    /**
+     * Get an option value
+     *
+     * @method getOption
+     * @param {String} key The option key
+     * @param {*} defaultValue Default value if option doesn't exist
+     * @returns {*} The option value or default
+     */
+    getOption(key, defaultValue = null) {
+        return this._options[key] !== undefined ? this._options[key] : defaultValue;
+    }
+ 
+    /**
+     * Check if an option exists
+     *
+     * @method hasOption
+     * @param {String} key The option key
+     * @returns {Boolean} True if option exists
+     */
+    hasOption(key) {
+        return this._options[key] !== undefined;
+    }
+ 
+    /**
+     * Remove an option
+     *
+     * @method removeOption
+     * @param {String} key The option key
+     * @returns {BaseContract} This instance for chaining
+     */
+    removeOption(key) {
+        delete this._options[key];
+        return this;
+    }
+ 
+    /**
+     * Get all options
+     *
+     * @method getOptions
+     * @returns {Object} All options
+     */
+    getOptions() {
+        return { ...this._options };
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/extension-component.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/extension-component.js.html new file mode 100644 index 0000000..6a6d940 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/extension-component.js.html @@ -0,0 +1,604 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/extension-component.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts extension-component.js

+
+ +
+ 0% + Statements + 0/38 +
+ + +
+ 0% + Branches + 0/19 +
+ + +
+ 0% + Functions + 0/8 +
+ + +
+ 0% + Lines + 0/38 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import BaseContract from './base-contract';
+ 
+/**
+ * Represents a lazy-loadable component from an engine
+ *
+ * This contract defines a component that will be loaded on-demand from an engine,
+ * preserving lazy loading capabilities while allowing cross-engine component usage.
+ *
+ * @class ExtensionComponent
+ * @extends BaseContract
+ *
+ * @example
+ * // Simple usage
+ * new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/navigator-app')
+ *
+ * @example
+ * // With options
+ * new ExtensionComponent('@fleetbase/fleetops-engine', {
+ *   path: 'components/admin/navigator-app',
+ *   loadingComponent: 'loading-spinner',
+ *   errorComponent: 'error-display'
+ * })
+ *
+ * @example
+ * // With method chaining
+ * new ExtensionComponent('@fleetbase/fleetops-engine', 'components/widget/metrics')
+ *   .withLoadingComponent('skeletons/widget')
+ *   .withErrorComponent('error-boundary')
+ *   .withData({ refreshInterval: 5000 })
+ */
+export default class ExtensionComponent extends BaseContract {
+    /**
+     * Create a new ExtensionComponent
+     *
+     * @constructor
+     * @param {String} engineName The name of the engine (e.g., '@fleetbase/fleetops-engine')
+     * @param {String|Function|Object} pathClassOrOptions Component path, component class, or options object
+     */
+    constructor(engineName, pathClassOrOptions = {}) {
+        // Handle component class
+        if (typeof pathClassOrOptions === 'function' && pathClassOrOptions.prototype) {
+            const componentClass = pathClassOrOptions;
+            super({
+                engine: engineName,
+                class: componentClass,
+                name: componentClass.name,
+            });
+ 
+            this.engine = engineName;
+            this.class = componentClass;
+            this.name = componentClass.name;
+            this.path = null; // No path for classes
+            this.isClass = true;
+            this.loadingComponent = null;
+            this.errorComponent = null;
+            return;
+        }
+ 
+        // Handle string path or options object
+        const options = typeof pathClassOrOptions === 'string' ? { path: pathClassOrOptions } : pathClassOrOptions;
+ 
+        super({
+            engine: engineName,
+            ...options,
+        });
+ 
+        this.engine = engineName;
+        this.path = options.path;
+        this.name = options.path; // Add name for parity
+        this.class = null;
+        this.isClass = false;
+        this.loadingComponent = options.loadingComponent || null;
+        this.errorComponent = options.errorComponent || null;
+    }
+ 
+    /**
+     * Validate the component definition
+     *
+     * @method validate
+     * @throws {Error} If engine name or path/class is missing
+     */
+    validate() {
+        if (!this.engine) {
+            throw new Error('ExtensionComponent requires an engine name');
+        }
+        if (!this.path && !this.class) {
+            throw new Error('ExtensionComponent requires a component path or class');
+        }
+    }
+ 
+    /**
+     * Set a custom loading component to display while the engine loads
+     *
+     * @method withLoadingComponent
+     * @param {String} componentName Name of the loading component
+     * @returns {ExtensionComponent} This instance for chaining
+     */
+    withLoadingComponent(componentName) {
+        this.loadingComponent = componentName;
+        this._options.loadingComponent = componentName;
+        return this;
+    }
+ 
+    /**
+     * Set a custom error component to display if loading fails
+     *
+     * @method withErrorComponent
+     * @param {String} componentName Name of the error component
+     * @returns {ExtensionComponent} This instance for chaining
+     */
+    withErrorComponent(componentName) {
+        this.errorComponent = componentName;
+        this._options.errorComponent = componentName;
+        return this;
+    }
+ 
+    /**
+     * Add custom data to pass to the component when it renders
+     *
+     * @method withData
+     * @param {Object} data Custom data object
+     * @returns {ExtensionComponent} This instance for chaining
+     */
+    withData(data) {
+        this._options.data = data;
+        return this;
+    }
+ 
+    /**
+     * Set a timeout for loading the component
+     *
+     * @method withTimeout
+     * @param {Number} milliseconds Timeout in milliseconds
+     * @returns {ExtensionComponent} This instance for chaining
+     */
+    withTimeout(milliseconds) {
+        this._options.timeout = milliseconds;
+        return this;
+    }
+ 
+    /**
+     * Get the plain object representation
+     *
+     * @method toObject
+     * @returns {Object} Plain object with all component definition properties
+     */
+    toObject() {
+        return {
+            engine: this.engine,
+            path: this.path,
+            name: this.name,
+            class: this.class,
+            isClass: this.isClass,
+            loadingComponent: this.loadingComponent,
+            errorComponent: this.errorComponent,
+            ...this._options,
+        };
+    }
+ 
+    /**
+     * Get the string representation
+     *
+     * @method toString
+     * @returns {String} Plain string component definition properties
+     */
+    toString() {
+        if (this.isClass) {
+            return `#extension-component:${this.engine}:${this.name}`;
+        }
+ 
+        return `#extension-component:${this.engine}:${this.path}`;
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook-registry.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook-registry.js.html new file mode 100644 index 0000000..abe19fb --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook-registry.js.html @@ -0,0 +1,139 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook-registry.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts hook-registry.js

+
+ +
+ 0% + Statements + 0/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 0% + Lines + 0/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { tracked } from '@glimmer/tracking';
+ 
+/**
+ * HookRegistry
+ *
+ * Shared registry object for application hooks.
+ * Registered as a singleton in the application container to ensure
+ * all HookService instances (app and engines) share the same hooks.
+ *
+ * @class HookRegistry
+ */
+export default class HookRegistry {
+    /**
+     * Map of hook names to arrays of hook objects
+     * @type {Object}
+     */
+    @tracked hooks = {};
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook.js.html new file mode 100644 index 0000000..a040416 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook.js.html @@ -0,0 +1,715 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/hook.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts hook.js

+
+ +
+ 0% + Statements + 0/39 +
+ + +
+ 0% + Branches + 0/35 +
+ + +
+ 0% + Functions + 0/11 +
+ + +
+ 0% + Lines + 0/39 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import BaseContract from './base-contract';
+import { guidFor } from '@ember/object/internals';
+import isObject from '../utils/is-object';
+ 
+/**
+ * Represents a lifecycle or application hook
+ *
+ * Hooks allow extensions to inject custom logic at specific points in the application lifecycle.
+ *
+ * @class Hook
+ * @extends BaseContract
+ *
+ * @example
+ * // Simple hook with chaining
+ * new Hook('application:before-model', (session, router) => {
+ *   if (session.isCustomer) {
+ *     router.transitionTo('customer-portal');
+ *   }
+ * })
+ *
+ * @example
+ * // Full definition object (first-class)
+ * new Hook({
+ *   name: 'application:before-model',
+ *   handler: (session, router) => {
+ *     if (session.isCustomer) {
+ *       router.transitionTo('customer-portal');
+ *     }
+ *   },
+ *   priority: 10,
+ *   once: false,
+ *   id: 'customer-redirect'
+ * })
+ *
+ * @example
+ * // Hook with method chaining
+ * new Hook('order:before-save')
+ *   .withPriority(10)
+ *   .once()
+ *   .execute(async (order) => {
+ *     await validateOrder(order);
+ *   })
+ */
+export default class Hook extends BaseContract {
+    /**
+     * Create a new Hook
+     *
+     * @constructor
+     * @param {String|Object} nameOrDefinition Hook name or full definition object
+     * @param {Function} handlerOrOptions Handler function or options object (only used if first param is string)
+     */
+    constructor(nameOrDefinition, handlerOrOptions = null) {
+        // Prepare options for super
+        const options = typeof handlerOrOptions === 'function' ? { handler: handlerOrOptions } : handlerOrOptions || {};
+ 
+        // Call super FIRST (JavaScript requirement)
+        super(isObject(nameOrDefinition) && nameOrDefinition.name ? nameOrDefinition : { name: nameOrDefinition, ...options });
+ 
+        // THEN set properties
+        if (isObject(nameOrDefinition) && nameOrDefinition.name) {
+            const definition = nameOrDefinition;
+ 
+            this.name = definition.name;
+            this.handler = definition.handler || null;
+            this.priority = definition.priority !== undefined ? definition.priority : 0;
+            this.runOnce = definition.once || false;
+            this.id = definition.id || guidFor(this);
+            this.enabled = definition.enabled !== undefined ? definition.enabled : true;
+        } else {
+            this.name = nameOrDefinition;
+            this.handler = options.handler || null;
+            this.priority = options.priority || 0;
+            this.runOnce = options.once || false;
+            this.id = options.id || guidFor(this);
+            this.enabled = options.enabled !== undefined ? options.enabled : true;
+        }
+ 
+        // Call setup() to trigger validation after properties are set
+        super.setup();
+    }
+ 
+    /**
+     * Validate the hook
+     *
+     * @method validate
+     * @throws {Error} If name is missing
+     */
+    validate() {
+        if (!this.name) {
+            throw new Error('Hook requires a name');
+        }
+    }
+ 
+    /**
+     * Set the hook handler function
+     *
+     * @method execute
+     * @param {Function} handler The handler function
+     * @returns {Hook} This instance for chaining
+     */
+    execute(handler) {
+        this.handler = handler;
+        this._options.handler = handler;
+        return this;
+    }
+ 
+    /**
+     * Set the hook priority
+     * Lower numbers execute first
+     *
+     * @method withPriority
+     * @param {Number} priority Priority value
+     * @returns {Hook} This instance for chaining
+     */
+    withPriority(priority) {
+        this.priority = priority;
+        this._options.priority = priority;
+        return this;
+    }
+ 
+    /**
+     * Mark this hook to run only once
+     * After execution, it will be automatically removed
+     *
+     * @method once
+     * @returns {Hook} This instance for chaining
+     */
+    once() {
+        this.runOnce = true;
+        this._options.once = true;
+        return this;
+    }
+ 
+    /**
+     * Set a unique ID for this hook
+     * Useful for removing specific hooks later
+     *
+     * @method withId
+     * @param {String} id Unique identifier
+     * @returns {Hook} This instance for chaining
+     */
+    withId(id) {
+        this.id = id;
+        this._options.id = id;
+        return this;
+    }
+ 
+    /**
+     * Enable or disable the hook
+     *
+     * @method setEnabled
+     * @param {Boolean} enabled Whether the hook is enabled
+     * @returns {Hook} This instance for chaining
+     */
+    setEnabled(enabled) {
+        this.enabled = enabled;
+        this._options.enabled = enabled;
+        return this;
+    }
+ 
+    /**
+     * Disable the hook
+     *
+     * @method disable
+     * @returns {Hook} This instance for chaining
+     */
+    disable() {
+        return this.setEnabled(false);
+    }
+ 
+    /**
+     * Enable the hook
+     *
+     * @method enable
+     * @returns {Hook} This instance for chaining
+     */
+    enable() {
+        return this.setEnabled(true);
+    }
+ 
+    /**
+     * Add metadata to the hook
+     *
+     * @method withMetadata
+     * @param {Object} metadata Metadata object
+     * @returns {Hook} This instance for chaining
+     */
+    withMetadata(metadata) {
+        this._options.metadata = metadata;
+        return this;
+    }
+ 
+    /**
+     * Get the plain object representation
+     *
+     * @method toObject
+     * @returns {Object} Plain object with all hook properties
+     */
+    toObject() {
+        return {
+            name: this.name,
+            handler: this.handler,
+            priority: this.priority,
+            once: this.runOnce,
+            id: this.id,
+            enabled: this.enabled,
+            ...this._options,
+        };
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/index.html new file mode 100644 index 0000000..ced228a --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/index.html @@ -0,0 +1,236 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts

+
+ +
+ 0% + Statements + 0/335 +
+ + +
+ 0% + Branches + 0/213 +
+ + +
+ 0% + Functions + 0/72 +
+ + +
+ 0% + Lines + 0/334 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
base-contract.js +
+
0%0/100%0/40%0/90%0/10
extension-component.js +
+
0%0/380%0/190%0/80%0/38
hook-registry.js +
+
0%0/1100%0/0100%0/00%0/1
hook.js +
+
0%0/390%0/350%0/110%0/39
menu-item.js +
+
0%0/1320%0/980%0/200%0/132
menu-panel.js +
+
0%0/350%0/230%0/90%0/34
template-helper.js +
+
0%0/180%0/40%0/10%0/18
universe-registry.js +
+
0%0/1100%0/0100%0/00%0/1
widget.js +
+
0%0/610%0/300%0/140%0/61
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-item.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-item.js.html new file mode 100644 index 0000000..3682f4d --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-item.js.html @@ -0,0 +1,1834 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-item.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts menu-item.js

+
+ +
+ 0% + Statements + 0/132 +
+ + +
+ 0% + Branches + 0/98 +
+ + +
+ 0% + Functions + 0/20 +
+ + +
+ 0% + Lines + 0/132 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import BaseContract from './base-contract';
+import ExtensionComponent from './extension-component';
+import { dasherize } from '@ember/string';
+import { isArray } from '@ember/array';
+import isObject from '../utils/is-object';
+ 
+/**
+ * Represents a menu item in the application
+ *
+ * Menu items can be simple navigation links or complex interactive components.
+ * They support routing, icons, priorities, click handlers, and lazy-loaded components.
+ *
+ * @class MenuItem
+ * @extends BaseContract
+ *
+ * @example
+ * // Simple menu item with chaining
+ * new MenuItem('Fleet-Ops', 'console.fleet-ops')
+ *   .withIcon('route')
+ *   .withPriority(0)
+ *
+ * @example
+ * // Full definition object (first-class)
+ * new MenuItem({
+ *   title: 'Fleet-Ops',
+ *   route: 'console.fleet-ops',
+ *   icon: 'route',
+ *   priority: 0,
+ *   component: { engine: '@fleetbase/fleetops-engine', path: 'components/admin/navigator-app' }
+ * })
+ *
+ * @example
+ * // Menu item with component
+ * new MenuItem('Settings')
+ *   .withComponent(new ExtensionComponent('@fleetbase/my-engine', 'components/settings'))
+ *   .withOnClick((menuItem, router) => {
+ *     router.transitionTo('virtual', menuItem.slug);
+ *   })
+ *
+ * @example
+ * // Menu item with description and shortcuts (Phase 2)
+ * new MenuItem('Fleet-Ops', 'console.fleet-ops')
+ *   .withIcon('route')
+ *   .withDescription('Manage fleets, drivers, and live orders')
+ *   .withShortcuts([
+ *     { title: 'Scheduler', route: 'console.fleet-ops.scheduler', icon: 'calendar' },
+ *     { title: 'Order Config', route: 'console.fleet-ops.order-configs', icon: 'gear' },
+ *   ])
+ */
+export default class MenuItem extends BaseContract {
+    /**
+     * Create a new MenuItem
+     *
+     * @constructor
+     * @param {String|Object} titleOrDefinition The menu item title or full definition object
+     * @param {String} route Optional route name (only used if first param is string)
+     */
+    constructor(titleOrDefinition, route = null) {
+        // Call super FIRST (JavaScript requirement)
+        super(isObject(titleOrDefinition) ? titleOrDefinition : { title: titleOrDefinition, route });
+ 
+        // THEN set properties
+        if (isObject(titleOrDefinition)) {
+            const definition = titleOrDefinition;
+ 
+            // Core properties
+            this.title = definition.title || null;
+            this.text = definition.text || definition.title;
+            this.label = definition.label || definition.title;
+            this.id = definition.id || (definition.title ? dasherize(definition.title) : null);
+            this.slug = definition.slug || (this.title ? dasherize(this.title) : null);
+ 
+            // Routing properties
+            this.route = definition.route || null;
+            this.section = definition.section || null;
+            this.queryParams = definition.queryParams || {};
+            this.routeParams = definition.routeParams || [];
+            this.view = definition.view || (this.title ? dasherize(this.title) : null);
+ 
+            // Display properties
+            this.icon = definition.icon || 'circle-dot';
+            this.iconComponent = definition.iconComponent || null;
+            this.iconComponentOptions = definition.iconComponentOptions || {};
+            this.iconSize = definition.iconSize || null;
+            this.iconPrefix = definition.iconPrefix || null;
+            this.iconClass = definition.iconClass || null;
+ 
+            // Component properties
+            this.component = definition.component || null;
+            this.componentParams = definition.componentParams || {};
+            this.renderComponentInPlace = definition.renderComponentInPlace || false;
+ 
+            // Styling properties
+            this.class = definition.class || null;
+            this.inlineClass = definition.inlineClass || null;
+            this.wrapperClass = definition.wrapperClass || null;
+            this.overwriteWrapperClass = definition.overwriteWrapperClass || false;
+ 
+            // Behavior properties
+            this.priority = definition.priority !== undefined ? definition.priority : 9;
+            this.index = definition.index !== undefined ? definition.index : 0;
+            this.type = definition.type || 'default';
+            this.buttonType = definition.buttonType || null;
+            this.onClick = definition.onClick || null;
+ 
+            // State properties
+            this.disabled = definition.disabled || false;
+            this.isLoading = definition.isLoading || false;
+ 
+            // Permission and i18n
+            this.permission = definition.permission || null;
+            this.intl = definition.intl || null;
+ 
+            // Nested items
+            this.items = definition.items || null;
+ 
+            // ── Phase 2 additions ──────────────────────────────────────────
+            // A short human-readable description of the extension shown in the
+            // overflow dropdown.  Optional – defaults to null.
+            this.description = definition.description || null;
+ 
+            // An array of shortcut items (sub-menu links) displayed beneath the
+            // extension name in the multi-column dropdown.  Each shortcut is a
+            // plain object with at minimum { title, route } and optionally
+            // { icon, iconPrefix, id }.  Optional – defaults to null.
+            this.shortcuts = definition.shortcuts || null;
+ 
+            // An array of string tags used to improve search discoverability in
+            // the overflow dropdown.  e.g. ['logistics', 'tracking', 'fleet'].
+            // Optional – defaults to null.
+            this.tags = isArray(definition.tags) ? definition.tags : definition.tags ? [definition.tags] : null;
+        } else {
+            // Handle string title with optional route (chaining pattern)
+            this.title = titleOrDefinition;
+            this.text = titleOrDefinition;
+            this.label = titleOrDefinition;
+            this.id = dasherize(titleOrDefinition);
+            this.slug = dasherize(titleOrDefinition);
+ 
+            // Routing properties
+            this.route = route;
+            this.section = null;
+            this.queryParams = {};
+            this.routeParams = [];
+            this.view = dasherize(titleOrDefinition);
+ 
+            // Display properties
+            this.icon = 'circle-dot';
+            this.iconComponent = null;
+            this.iconComponentOptions = {};
+            this.iconSize = null;
+            this.iconPrefix = null;
+            this.iconClass = null;
+ 
+            // Component properties
+            this.component = null;
+            this.componentParams = {};
+            this.renderComponentInPlace = false;
+ 
+            // Styling properties
+            this.class = null;
+            this.inlineClass = null;
+            this.wrapperClass = null;
+            this.overwriteWrapperClass = false;
+ 
+            // Behavior properties
+            this.priority = 9;
+            this.index = 0;
+            this.type = 'default';
+            this.buttonType = null;
+            this.onClick = null;
+ 
+            // State properties
+            this.disabled = false;
+            this.isLoading = false;
+ 
+            // Permission and i18n
+            this.permission = null;
+            this.intl = null;
+ 
+            // Nested items
+            this.items = null;
+ 
+            // ── Phase 2 additions ──────────────────────────────────────────
+            this.description = null;
+            this.shortcuts = null;
+            this.tags = null;
+        }
+ 
+        // Call setup() to trigger validation after properties are set
+        super.setup();
+    }
+ 
+    /**
+     * Validate the menu item
+     *
+     * @method validate
+     * @throws {Error} If title is missing
+     */
+    validate() {
+        if (!this.title) {
+            throw new Error('MenuItem requires a title');
+        }
+    }
+ 
+    /**
+     * Set the menu item icon
+     *
+     * @method withIcon
+     * @param {String} icon Icon name (FontAwesome or custom)
+     * @returns {MenuItem} This instance for chaining
+     */
+    withIcon(icon) {
+        this.icon = icon;
+        this._options.icon = icon;
+        return this;
+    }
+ 
+    /**
+     * Set the menu item priority
+     * Lower numbers appear first in the menu
+     *
+     * @method withPriority
+     * @param {Number} priority Priority value (default: 9)
+     * @returns {MenuItem} This instance for chaining
+     */
+    withPriority(priority) {
+        this.priority = priority;
+        this._options.priority = priority;
+        return this;
+    }
+ 
+    /**
+     * Set a component for the menu item
+     *
+     * @method withComponent
+     * @param {ExtensionComponent|Object} component Component definition
+     * @returns {MenuItem} This instance for chaining
+     */
+    withComponent(component) {
+        if (component instanceof ExtensionComponent) {
+            this.component = component.toObject();
+        } else {
+            this.component = component;
+        }
+        this._options.component = this.component;
+        return this;
+    }
+ 
+    /**
+     * Set a click handler for the menu item.
+     *
+     * `onClick` itself is the handler property every consumer calls, so the setter
+     * cannot share its name: an instance's `onClick` field would shadow it.
+     *
+     * @method withOnClick
+     * @param {Function} handler Click handler function
+     * @returns {MenuItem} This instance for chaining
+     */
+    withOnClick(handler) {
+        this.onClick = handler;
+        this._options.onClick = handler;
+        return this;
+    }
+ 
+    /**
+     * Set the menu item slug
+     *
+     * @method withSlug
+     * @param {String} slug URL-friendly slug
+     * @returns {MenuItem} This instance for chaining
+     */
+    withSlug(slug) {
+        this.slug = slug;
+        this._options.slug = slug;
+        return this;
+    }
+ 
+    /**
+     * Set query parameters for the route
+     *
+     * @method withQueryParams
+     * @param {Object} params Query parameters object
+     * @returns {MenuItem} This instance for chaining
+     */
+    withQueryParams(params) {
+        this.queryParams = params;
+        this._options.queryParams = params;
+        return this;
+    }
+ 
+    /**
+     * Set route parameters
+     *
+     * @method withRouteParams
+     * @param {...*} params Route parameters
+     * @returns {MenuItem} This instance for chaining
+     */
+    withRouteParams(...params) {
+        this.routeParams = params;
+        this._options.routeParams = params;
+        return this;
+    }
+ 
+    /**
+     * Set the section this menu item belongs to
+     *
+     * @method inSection
+     * @param {String} section Section name
+     * @returns {MenuItem} This instance for chaining
+     */
+    inSection(section) {
+        this.section = section;
+        this._options.section = section;
+        return this;
+    }
+ 
+    /**
+     * Set the index position within its section
+     *
+     * @method atIndex
+     * @param {Number} index Index position
+     * @returns {MenuItem} This instance for chaining
+     */
+    atIndex(index) {
+        this.index = index;
+        this._options.index = index;
+        return this;
+    }
+ 
+    /**
+     * Set the menu item type
+     *
+     * @method withType
+     * @param {String} type Type (e.g., 'link', 'button', 'default')
+     * @returns {MenuItem} This instance for chaining
+     */
+    withType(type) {
+        this.type = type;
+        this._options.type = type;
+        return this;
+    }
+ 
+    /**
+     * Set wrapper CSS class
+     *
+     * @method withWrapperClass
+     * @param {String} wrapperClass CSS class for wrapper element
+     * @returns {MenuItem} This instance for chaining
+     */
+    withWrapperClass(wrapperClass) {
+        this.wrapperClass = wrapperClass;
+        this._options.wrapperClass = wrapperClass;
+        return this;
+    }
+ 
+    /**
+     * Set component parameters
+     *
+     * @method withComponentParams
+     * @param {Object} params Parameters to pass to component
+     * @returns {MenuItem} This instance for chaining
+     */
+    withComponentParams(params) {
+        this._options.componentParams = params;
+        return this;
+    }
+ 
+    /**
+     * Set whether to render component in place
+     *
+     * @method renderInPlace
+     * @param {Boolean} inPlace Whether to render in place
+     * @returns {MenuItem} This instance for chaining
+     */
+    renderInPlace(inPlace = true) {
+        this._options.renderComponentInPlace = inPlace;
+        return this;
+    }
+ 
+    // ── Phase 2 builder methods ────────────────────────────────────────────
+ 
+    /**
+     * Set a short human-readable description for the extension.
+     * Displayed beneath the extension title in the overflow dropdown.
+     *
+     * @method withDescription
+     * @param {String} description Short description text
+     * @returns {MenuItem} This instance for chaining
+     *
+     * @example
+     * new MenuItem('Fleet-Ops', 'console.fleet-ops')
+     *   .withDescription('Manage fleets, drivers, and live orders')
+     */
+    withDescription(description) {
+        this.description = description;
+        this._options.description = description;
+        return this;
+    }
+ 
+    /**
+     * Set an array of shortcut items displayed as independent sibling cards in
+     * the multi-column overflow dropdown (AWS Console style).  Each shortcut
+     * supports the full MenuItem property surface:
+     *
+     *   Required:
+     *     title    {String}   Display label
+     *
+     *   Routing:
+     *     route    {String}   Ember route name
+     *     queryParams {Object}
+     *     routeParams {Array}
+     *
+     *   Identity:
+     *     id       {String}   Explicit id (auto-dasherized from title if omitted)
+     *     slug     {String}   URL slug (falls back to id)
+     *
+     *   Icons:
+     *     icon            {String}   FontAwesome icon name
+     *     iconPrefix      {String}   FA prefix (e.g. 'far', 'fab')
+     *     iconSize        {String}   FA size string
+     *     iconClass       {String}   Extra CSS class on the icon element
+     *     iconComponent   {String}   Lazy-loaded engine component path
+     *     iconComponentOptions {Object}
+     *
+     *   Metadata:
+     *     description {String}   Short description shown in the card
+     *     tags        {String[]} Search tags
+     *
+     *   Behaviour:
+     *     onClick  {Function}  Click handler (receives the shortcut item)
+     *     disabled {Boolean}
+     *
+     * Shortcuts are registered as first-class header menu items at boot time
+     * and can be individually pinned to the navigation bar.
+     *
+     * @method withShortcuts
+     * @param {Array<Object>} shortcuts Array of shortcut definition objects
+     * @returns {MenuItem} This instance for chaining
+     *
+     * @example
+     * new MenuItem('Fleet-Ops', 'console.fleet-ops')
+     *   .withShortcuts([
+     *     {
+     *       title: 'Scheduler',
+     *       route: 'console.fleet-ops.scheduler',
+     *       icon: 'calendar',
+     *       description: 'Plan and visualise driver schedules',
+     *       tags: ['schedule', 'calendar'],
+     *     },
+     *     {
+     *       title: 'Live Map',
+     *       route: 'console.fleet-ops',
+     *       iconComponent: 'fleet-ops@components/live-map-icon',
+     *       description: 'Real-time vehicle tracking',
+     *     },
+     *   ])
+     */
+    withShortcuts(shortcuts) {
+        this.shortcuts = isArray(shortcuts) ? shortcuts : null;
+        this._options.shortcuts = this.shortcuts;
+        return this;
+    }
+ 
+    /**
+     * Set an array of string tags for this menu item.
+     * Tags are matched against the search query in the overflow dropdown,
+     * making items discoverable even when the query doesn't match the title
+     * or description.
+     *
+     * @method withTags
+     * @param {String|String[]} tags One tag string or an array of tag strings
+     * @returns {MenuItem} This instance for chaining
+     *
+     * @example
+     * new MenuItem('Fleet-Ops', 'console.fleet-ops')
+     *   .withTags(['logistics', 'tracking', 'fleet', 'drivers'])
+     */
+    withTags(tags) {
+        this.tags = isArray(tags) ? tags : tags ? [tags] : null;
+        this._options.tags = this.tags;
+        return this;
+    }
+ 
+    /**
+     * Add a single shortcut to the existing shortcuts array.
+     * Creates the array if it does not yet exist.
+     *
+     * @method addShortcut
+     * @param {Object} shortcut Shortcut definition object { title, route, icon?, iconPrefix?, id? }
+     * @returns {MenuItem} This instance for chaining
+     *
+     * @example
+     * new MenuItem('Fleet-Ops', 'console.fleet-ops')
+     *   .addShortcut({ title: 'Scheduler', route: 'console.fleet-ops.scheduler' })
+     *   .addShortcut({ title: 'Order Config', route: 'console.fleet-ops.order-configs' })
+     */
+    addShortcut(shortcut) {
+        if (!isArray(this.shortcuts)) {
+            this.shortcuts = [];
+        }
+        this.shortcuts = [...this.shortcuts, shortcut];
+        this._options.shortcuts = this.shortcuts;
+        return this;
+    }
+ 
+    /**
+     * Get the plain object representation
+     *
+     * @method toObject
+     * @returns {Object} Plain object with all menu item properties
+     */
+    toObject() {
+        return {
+            // Core properties
+            id: this.id,
+            title: this.title,
+            text: this.text,
+            label: this.label,
+            slug: this.slug,
+ 
+            // Routing properties
+            route: this.route,
+            section: this.section,
+            view: this.view,
+            queryParams: this.queryParams,
+            routeParams: this.routeParams,
+ 
+            // Display properties
+            icon: this.icon,
+            iconComponent: this.iconComponent,
+            iconComponentOptions: this.iconComponentOptions,
+            iconSize: this.iconSize,
+            iconPrefix: this.iconPrefix,
+            iconClass: this.iconClass,
+ 
+            // Component properties
+            component: this.component,
+            componentParams: this.componentParams,
+            renderComponentInPlace: this.renderComponentInPlace,
+ 
+            // Styling properties
+            class: this.class,
+            inlineClass: this.inlineClass,
+            wrapperClass: this.wrapperClass,
+            overwriteWrapperClass: this.overwriteWrapperClass,
+ 
+            // Behavior properties
+            priority: this.priority,
+            index: this.index,
+            type: this.type,
+            buttonType: this.buttonType,
+            onClick: this.onClick,
+ 
+            // State properties
+            disabled: this.disabled,
+            isLoading: this.isLoading,
+ 
+            // Permission and i18n
+            permission: this.permission,
+            intl: this.intl,
+ 
+            // Nested items
+            items: this.items,
+ 
+            // ── Phase 2 additions ──────────────────────────────────────────
+            // Optional short description shown in the overflow dropdown card
+            description: this.description,
+ 
+            // Optional array of shortcut sub-links shown inside the extension card
+            shortcuts: this.shortcuts,
+ 
+            // Optional array of string tags for search discoverability
+            tags: this.tags,
+ 
+            // Indicator flag
+            _isMenuItem: true,
+ 
+            // Include any additional options
+            ...this._options,
+        };
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-panel.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-panel.js.html new file mode 100644 index 0000000..87161d6 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-panel.js.html @@ -0,0 +1,592 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/menu-panel.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts menu-panel.js

+
+ +
+ 0% + Statements + 0/35 +
+ + +
+ 0% + Branches + 0/23 +
+ + +
+ 0% + Functions + 0/9 +
+ + +
+ 0% + Lines + 0/34 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import BaseContract from './base-contract';
+import MenuItem from './menu-item';
+import { dasherize } from '@ember/string';
+import isObject from '../utils/is-object';
+ 
+/**
+ * Represents a menu panel containing multiple menu items
+ *
+ * Menu panels are used in admin settings and other sections to group related menu items.
+ *
+ * @class MenuPanel
+ * @extends BaseContract
+ *
+ * @example
+ * // With chaining
+ * new MenuPanel('Fleet-Ops Config')
+ *   .withSlug('fleet-ops')
+ *   .withIcon('truck')
+ *   .addItem(new MenuItem('Navigator App').withIcon('location-arrow'))
+ *   .addItem(new MenuItem('Avatar Management').withIcon('images'))
+ *
+ * @example
+ * // Full definition object (first-class)
+ * new MenuPanel({
+ *   title: 'Fleet-Ops Config',
+ *   slug: 'fleet-ops',
+ *   icon: 'truck',
+ *   priority: 5,
+ *   items: [
+ *     { title: 'Navigator App', icon: 'location-arrow' },
+ *     { title: 'Avatar Management', icon: 'images' }
+ *   ]
+ * })
+ */
+export default class MenuPanel extends BaseContract {
+    /**
+     * Create a new MenuPanel
+     *
+     * @constructor
+     * @param {String|Object} titleOrDefinition The panel title or full definition object
+     * @param {Array} items Optional array of menu items (only used if first param is string)
+     */
+    constructor(titleOrDefinition, items = []) {
+        // Call super FIRST (JavaScript requirement)
+        super(isObject(titleOrDefinition) && titleOrDefinition.title ? titleOrDefinition : { title: titleOrDefinition });
+ 
+        // THEN set properties
+        if (isObject(titleOrDefinition) && titleOrDefinition.title) {
+            const definition = titleOrDefinition;
+ 
+            this.title = definition.title;
+            this.items = definition.items || [];
+            this.slug = definition.slug || dasherize(this.title);
+            this.icon = definition.icon || null;
+            this.open = definition.open !== undefined ? definition.open : true;
+            this.priority = definition.priority !== undefined ? definition.priority : 9;
+        } else {
+            // Handle string title (chaining pattern)
+            this.title = titleOrDefinition;
+            this.items = items;
+            this.slug = dasherize(titleOrDefinition);
+            this.icon = null;
+            this.open = true;
+            this.priority = 9;
+        }
+ 
+        // Call setup() to trigger validation after properties are set
+        super.setup();
+    }
+ 
+    /**
+     * Validate the menu panel
+     *
+     * @method validate
+     * @throws {Error} If title is missing
+     */
+    validate() {
+        if (!this.title) {
+            throw new Error('MenuPanel requires a title');
+        }
+    }
+ 
+    /**
+     * Set the panel slug
+     *
+     * @method withSlug
+     * @param {String} slug URL-friendly slug
+     * @returns {MenuPanel} This instance for chaining
+     */
+    withSlug(slug) {
+        this.slug = slug;
+        this._options.slug = slug;
+        return this;
+    }
+ 
+    /**
+     * Set the panel icon
+     *
+     * @method withIcon
+     * @param {String} icon Icon name
+     * @returns {MenuPanel} This instance for chaining
+     */
+    withIcon(icon) {
+        this.icon = icon;
+        this._options.icon = icon;
+        return this;
+    }
+ 
+    /**
+     * Set the panel priority
+     *
+     * @method withPriority
+     * @param {Number} priority Priority value
+     * @returns {MenuPanel} This instance for chaining
+     */
+    withPriority(priority) {
+        this.priority = priority;
+        this._options.priority = priority;
+        return this;
+    }
+ 
+    /**
+     * Add a menu item to the panel
+     *
+     * @method addItem
+     * @param {MenuItem|Object} item Menu item to add
+     * @returns {MenuPanel} This instance for chaining
+     */
+    addItem(item) {
+        if (item instanceof MenuItem) {
+            this.items.push(item.toObject());
+        } else {
+            this.items.push(item);
+        }
+        return this;
+    }
+ 
+    /**
+     * Add multiple menu items to the panel
+     *
+     * @method addItems
+     * @param {Array} items Array of menu items
+     * @returns {MenuPanel} This instance for chaining
+     */
+    addItems(items) {
+        items.forEach((item) => this.addItem(item));
+        return this;
+    }
+ 
+    /**
+     * Get the plain object representation
+     *
+     * @method toObject
+     * @returns {Object} Plain object with all panel properties
+     */
+    toObject() {
+        return {
+            title: this.title,
+            slug: this.slug,
+            icon: this.icon,
+            open: this.open,
+            priority: this.priority,
+            items: this.items,
+            // Indicator flag
+            _isMenuPanel: true,
+            ...this._options,
+        };
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/template-helper.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/template-helper.js.html new file mode 100644 index 0000000..bc38fe2 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/template-helper.js.html @@ -0,0 +1,367 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/template-helper.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts template-helper.js

+
+ +
+ 0% + Statements + 0/18 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/18 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Represents a helper that can be lazy-loaded from an engine or registered directly.
+ * Used by the RegistryService to share helpers across engines.
+ *
+ * @class TemplateHelper
+ * @example
+ * // Lazy loading from engine
+ * new TemplateHelper('@fleetbase/storefront-engine', 'helpers/calculate-delivery-fee')
+ *
+ * // Direct class registration
+ * new TemplateHelper(null, CalculateDeliveryFeeHelper)
+ */
+export default class TemplateHelper {
+    /**
+     * The name of the engine this helper belongs to
+     * @type {string|null}
+     */
+    engineName = null;
+ 
+    /**
+     * The path to the helper within the engine (for lazy loading)
+     * @type {string|null}
+     */
+    path = null;
+ 
+    /**
+     * The helper class or function (for direct registration)
+     * @type {Function|null}
+     */
+    class = null;
+ 
+    /**
+     * Whether this is a direct class registration (true) or lazy loading (false)
+     * @type {boolean}
+     */
+    isClass = false;
+ 
+    /**
+     * The resolved helper name (extracted from path or class name)
+     * @type {string|null}
+     */
+    name = null;
+ 
+    /**
+     * Creates a new TemplateHelper instance
+     *
+     * @param {string|null} engineName - The engine name (e.g., '@fleetbase/storefront-engine') or null for direct class
+     * @param {string|Function} pathOrClass - Either a path string for lazy loading or a helper class/function
+     */
+    constructor(engineName, pathOrClass) {
+        this.engineName = engineName;
+ 
+        if (typeof pathOrClass === 'string') {
+            // Lazy loading case
+            this.path = pathOrClass;
+            this.isClass = false;
+            this.name = this.#extractNameFromPath(pathOrClass);
+        } else {
+            // Direct class/function registration
+            this.class = pathOrClass;
+            this.isClass = true;
+            this.name = this.#extractNameFromClass(pathOrClass);
+        }
+    }
+ 
+    /**
+     * Extracts helper name from path
+     * @private
+     * @param {string} path - The helper path (e.g., 'helpers/calculate-delivery-fee')
+     * @returns {string} The helper name (e.g., 'calculate-delivery-fee')
+     */
+    #extractNameFromPath(path) {
+        const parts = path.split('/');
+        return parts[parts.length - 1];
+    }
+ 
+    /**
+     * Extracts helper name from class/function
+     * @private
+     * @param {Function} classOrFn - The helper class or function
+     * @returns {string|null} The helper name or null if not available
+     */
+    #extractNameFromClass(classOrFn) {
+        if (classOrFn.name) {
+            // Convert PascalCase or camelCase to kebab-case
+            return classOrFn.name
+                .replace(/([a-z])([A-Z])/g, '$1-$2')
+                .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
+                .toLowerCase()
+                .replace(/helper$/, ''); // Remove 'helper' suffix if present
+        }
+        return null;
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/universe-registry.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/universe-registry.js.html new file mode 100644 index 0000000..0d9991c --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/universe-registry.js.html @@ -0,0 +1,157 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/universe-registry.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts universe-registry.js

+
+ +
+ 0% + Statements + 0/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 0% + Lines + 0/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { tracked } from '@glimmer/tracking';
+import { TrackedMap } from 'tracked-built-ins';
+ 
+/**
+ * UniverseRegistry
+ *
+ * A singleton registry class that stores all universe registrations.
+ * This class is registered to the application container to ensure
+ * the same registry instance is shared across the app and all engines.
+ *
+ * Pattern inspired by RouteOptimizationRegistry - ensures registrations
+ * persist across engine boundaries by storing data in the application
+ * container rather than in service instances.
+ *
+ * @class UniverseRegistry
+ */
+export default class UniverseRegistry {
+    /**
+     * TrackedMap of section name → TrackedObject with dynamic lists
+     * Fully reactive - templates update when registries change
+     * @type {TrackedMap<string, TrackedObject>}
+     */
+    @tracked registries = new TrackedMap();
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/widget.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/widget.js.html new file mode 100644 index 0000000..4065c3b --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/widget.js.html @@ -0,0 +1,910 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts/widget.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/contracts widget.js

+
+ +
+ 0% + Statements + 0/61 +
+ + +
+ 0% + Branches + 0/30 +
+ + +
+ 0% + Functions + 0/14 +
+ + +
+ 0% + Lines + 0/61 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import BaseContract from './base-contract';
+import ExtensionComponent from './extension-component';
+import isObject from '../utils/is-object';
+ 
+/**
+ * Represents a dashboard widget
+ *
+ * Widgets are modular components that can be added to dashboards.
+ * They support grid layout options, custom configurations, and lazy-loaded components.
+ *
+ * @class Widget
+ * @extends BaseContract
+ *
+ * @example
+ * // With chaining
+ * new Widget('fleet-ops-metrics')
+ *   .withName('Fleet-Ops Metrics')
+ *   .withDescription('Key metrics from Fleet-Ops')
+ *   .withIcon('truck')
+ *   .withComponent(new ExtensionComponent('@fleetbase/fleetops-engine', 'components/widget/metrics'))
+ *   .withGridOptions({ w: 12, h: 12, minW: 8, minH: 12 })
+ *   .asDefault()
+ *
+ * @example
+ * // Full definition object (first-class)
+ * new Widget({
+ *   id: 'fleet-ops-metrics',
+ *   name: 'Fleet-Ops Metrics',
+ *   description: 'Key metrics from Fleet-Ops',
+ *   icon: 'truck',
+ *   component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/widget/metrics'),
+ *   grid_options: { w: 12, h: 12, minW: 8, minH: 12 },
+ *   default: true
+ * })
+ *
+ * @example
+ * // Full definition with string component (local)
+ * new Widget({
+ *   id: 'welcome',
+ *   name: 'Welcome',
+ *   component: 'widget/welcome',
+ *   default: true
+ * })
+ */
+export default class Widget extends BaseContract {
+    /**
+     * Create a new Widget
+     *
+     * @constructor
+     * @param {String|Object} idOrDefinition Unique widget identifier or full definition object
+     */
+    constructor(idOrDefinition) {
+        // Call super FIRST (JavaScript requirement)
+        super(isObject(idOrDefinition) ? idOrDefinition : { id: idOrDefinition });
+ 
+        // THEN set properties
+        if (isObject(idOrDefinition)) {
+            const definition = idOrDefinition;
+ 
+            // Support both id and widgetId for backward compatibility
+            this.id = definition.id || definition.widgetId;
+            this.name = definition.name || null;
+            this.description = definition.description || null;
+            this.icon = definition.icon || null;
+            this.grid_options = definition.grid_options || {};
+            this.options = definition.options || {};
+            this.category = definition.category || 'default';
+ 
+            // Handle component - support both string and ExtensionComponent
+            if (definition.component instanceof ExtensionComponent) {
+                this.component = definition.component.toObject();
+            } else if (isObject(definition.component)) {
+                // Plain object component definition
+                this.component = definition.component;
+            } else {
+                // String component path
+                this.component = definition.component || null;
+            }
+ 
+            // Store default flag if present
+            if (definition.default) {
+                this._options.default = true;
+            }
+        } else {
+            // Handle string id (chaining pattern)
+            this.id = idOrDefinition;
+            this.name = null;
+            this.description = null;
+            this.icon = null;
+            this.component = null;
+            this.grid_options = {};
+            this.options = {};
+            this.category = 'default';
+        }
+ 
+        // Call setup() to trigger validation after properties are set
+        super.setup();
+    }
+ 
+    /**
+     * Validate the widget
+     *
+     * @method validate
+     * @throws {Error} If id is missing
+     */
+    validate() {
+        if (!this.id) {
+            throw new Error('Widget requires an id');
+        }
+    }
+ 
+    /**
+     * Set the widget name
+     *
+     * @method withName
+     * @param {String} name Display name
+     * @returns {Widget} This instance for chaining
+     */
+    withName(name) {
+        this.name = name;
+        this._options.name = name;
+        return this;
+    }
+ 
+    /**
+     * Set the widget description
+     *
+     * @method withDescription
+     * @param {String} description Widget description
+     * @returns {Widget} This instance for chaining
+     */
+    withDescription(description) {
+        this.description = description;
+        this._options.description = description;
+        return this;
+    }
+ 
+    /**
+     * Set the widget icon
+     *
+     * @method withIcon
+     * @param {String} icon Icon name
+     * @returns {Widget} This instance for chaining
+     */
+    withIcon(icon) {
+        this.icon = icon;
+        this._options.icon = icon;
+        return this;
+    }
+ 
+    /**
+     * Set the widget component
+     * Supports both string paths and ExtensionComponent instances
+     *
+     * @method withComponent
+     * @param {String|ExtensionComponent|Object} component Component definition
+     * @returns {Widget} This instance for chaining
+     */
+    withComponent(component) {
+        if (component instanceof ExtensionComponent) {
+            this.component = component.toObject();
+        } else {
+            this.component = component;
+        }
+        this._options.component = this.component;
+        return this;
+    }
+ 
+    /**
+     * Set grid layout options
+     *
+     * @method withGridOptions
+     * @param {Object} options Grid options (w, h, minW, minH, etc.)
+     * @returns {Widget} This instance for chaining
+     */
+    withGridOptions(options) {
+        this.grid_options = { ...this.grid_options, ...options };
+        this._options.grid_options = this.grid_options;
+        return this;
+    }
+ 
+    /**
+     * Set widget-specific options
+     *
+     * @method withOptions
+     * @param {Object} options Widget options
+     * @returns {Widget} This instance for chaining
+     */
+    withOptions(options) {
+        this.options = { ...this.options, ...options };
+        this._options.options = this.options;
+        return this;
+    }
+ 
+    /**
+     * Set the widget category
+     *
+     * @method withCategory
+     * @param {String} category Category name
+     * @returns {Widget} This instance for chaining
+     */
+    withCategory(category) {
+        this.category = category;
+        this._options.category = category;
+        return this;
+    }
+ 
+    /**
+     * Mark this widget as a default widget
+     * Default widgets are automatically added to new dashboards
+     *
+     * @method asDefault
+     * @returns {Widget} This instance for chaining
+     */
+    asDefault() {
+        this._options.default = true;
+        return this;
+    }
+ 
+    /**
+     * Check if this widget is marked as default
+     *
+     * @method isDefault
+     * @returns {Boolean} True if widget is a default widget
+     */
+    isDefault() {
+        return this._options.default === true;
+    }
+ 
+    /**
+     * Set the widget title
+     *
+     * @method withTitle
+     * @param {String} title Widget title
+     * @returns {Widget} This instance for chaining
+     */
+    withTitle(title) {
+        this.options.title = title;
+        this._options.options = this.options;
+        return this;
+    }
+ 
+    /**
+     * Set refresh interval for the widget
+     *
+     * @method withRefreshInterval
+     * @param {Number} milliseconds Refresh interval in milliseconds
+     * @returns {Widget} This instance for chaining
+     */
+    withRefreshInterval(milliseconds) {
+        this.options.refreshInterval = milliseconds;
+        this._options.options = this.options;
+        return this;
+    }
+ 
+    /**
+     * Get the plain object representation
+     *
+     * @method toObject
+     * @returns {Object} Plain object with all widget properties
+     */
+    toObject() {
+        return {
+            id: this.id,
+            name: this.name,
+            description: this.description,
+            icon: this.icon,
+            component: this.component,
+            grid_options: this.grid_options,
+            options: this.options,
+            category: this.category,
+            ...this._options,
+        };
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/index.html new file mode 100644 index 0000000..fe8c013 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers

+
+ +
+ 77.77% + Statements + 7/9 +
+ + +
+ 50% + Branches + 2/4 +
+ + +
+ 50% + Functions + 2/4 +
+ + +
+ 77.77% + Lines + 7/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
load-socketcluster-client.js +
+
100%5/550%1/2100%1/1100%5/5
local-storage-adapter.js +
+
50%2/450%1/233.33%1/350%2/4
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/load-socketcluster-client.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/load-socketcluster-client.js.html new file mode 100644 index 0000000..08db821 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/load-socketcluster-client.js.html @@ -0,0 +1,127 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/load-socketcluster-client.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers load-socketcluster-client.js

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 50% + Branches + 1/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15  +  +  +1x +1x +1x +1x +1x +  +  +  +  +  +  + 
export function initialize() {
+    // Check if the script already exists
+    // Only insert the script tag if it doesn't already exist
+    Eif (!document.querySelector('script[data-socketcluster-client]')) {
+        const socketClusterClientScript = document.createElement('script');
+        socketClusterClientScript.setAttribute('data-socketcluster-client', '1');
+        socketClusterClientScript.src = '/assets/socketcluster-client.min.js';
+        document.body.appendChild(socketClusterClientScript);
+    }
+}
+ 
+export default {
+    initialize,
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/local-storage-adapter.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/local-storage-adapter.js.html new file mode 100644 index 0000000..b20e6a4 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/local-storage-adapter.js.html @@ -0,0 +1,148 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers/local-storage-adapter.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/initializers local-storage-adapter.js

+
+ +
+ 50% + Statements + 2/4 +
+ + +
+ 50% + Branches + 1/2 +
+ + +
+ 33.33% + Functions + 1/3 +
+ + +
+ 50% + Lines + 2/4 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22  +  +  +  +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Store from '@ember-data/store';
+import { importData, exportData } from 'ember-local-storage/helpers/import-export';
+ 
+export function initialize() {
+    Eif (!Store.prototype._emberLocalStoragePatched) {
+        Store.reopen({
+            _emberLocalStoragePatched: true,
+            importData: function (json, options) {
+                return importData(this, json, options);
+            },
+            exportData: function (types, options) {
+                return exportData(this, types, options);
+            },
+        });
+    }
+}
+ 
+export default {
+    name: 'local-storage-adapter',
+    initialize: initialize,
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers/application.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers/application.js.html new file mode 100644 index 0000000..585cc29 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers/application.js.html @@ -0,0 +1,331 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers/application.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers application.js

+
+ +
+ 100% + Statements + 18/18 +
+ + +
+ 80% + Branches + 4/5 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 18/18 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83  +  +  +  +  +  +353x +  +  +  +  +  +  +353x +  +  +  +  +  +  +  +  +14x +  +  +  +  +  +  +  +  +  +  +  +94x +  +94x +  +  +94x +380x +  +380x +216x +  +  +  +  +216x +188x +  +  +28x +  +  +  +94x +  +  +  +94x +94x +  +  +94x +  +  +  +  +  +  +  +  +  +  +  +  +161x +  +161x +  +  + 
import RESTSerializer from '@ember-data/serializer/rest';
+import { isNone } from '@ember/utils';
+import { underscore } from '@ember/string';
+import normalizePolymorphicTypeWithinHash from '../utils/serialize/normalize-polymorphic-type-within-hash';
+ 
+export default class ApplicationSerializer extends RESTSerializer {
+    readOnlyAttributes = ['slug'];
+ 
+    /**
+     * Default primary keys to uuid
+     *
+     * @var {string}
+     */
+    primaryKey = 'uuid';
+ 
+    /**
+     * keyForPolymorphicType can be used to define a custom key when serializing and deserializing a polymorphic type.
+     * By default, the returned key is ${key}Type.
+     *
+     * @var {String}
+     */
+    keyForPolymorphicType(key) {
+        return `${underscore(key)}_type`;
+    }
+ 
+    /**
+     * Customize serializer so that any attributes that are instances of Models or objects
+     * that are to accept and ID get serialized into the id only
+     *
+     * @param {Snapshot} snapshot
+     * @param {Object} options
+     * @return {Object} json
+     */
+    serialize(snapshot) {
+        const json = super.serialize(...arguments);
+ 
+        this.removeReadOnlyAttributes(json);
+ 
+        // for each relationship make sure the id is set
+        snapshot.eachRelationship((key, relationship) => {
+            const { kind } = relationship.meta;
+ 
+            if (kind === 'belongsTo') {
+                const relationSnapshot = snapshot.belongsTo(key);
+ 
+                /* istanbul ignore next -- keyForRelationship is defined on the serializer prototype, so the fallback arm is unreachable */
+                key = this.keyForRelationship ? this.keyForRelationship(key, 'belongsTo', 'serialize') : key;
+ 
+                if (isNone(relationSnapshot)) {
+                    return;
+                }
+ 
+                json[`${key}_uuid`] = relationSnapshot.id;
+            }
+        });
+ 
+        return json;
+    }
+ 
+    removeReadOnlyAttributes(json = {}) {
+        this.readOnlyAttributes.forEach((attribute) => {
+            delete json[attribute];
+        });
+ 
+        return json;
+    }
+ 
+    /**
+     * Normalizes a part of the JSON payload returned by the server.
+     *
+     * @method normalize
+     * @param {Model} modelClass
+     * @param {Object} resourceHash
+     * @param {String} prop
+     * @return {Object}
+     */
+    normalize(model, hash, prop) {
+        hash = normalizePolymorphicTypeWithinHash(hash);
+ 
+        return super.normalize(model, hash, prop);
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers/index.html new file mode 100644 index 0000000..3ceea81 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/serializers

+
+ +
+ 100% + Statements + 18/18 +
+ + +
+ 80% + Branches + 4/5 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 18/18 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
application.js +
+
100%18/1880%4/5100%6/6100%18/18
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/current-user.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/current-user.js.html new file mode 100644 index 0000000..b530d77 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/current-user.js.html @@ -0,0 +1,1447 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/current-user.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services current-user.js

+
+ +
+ 0% + Statements + 0/150 +
+ + +
+ 0% + Branches + 0/66 +
+ + +
+ 0% + Functions + 0/29 +
+ + +
+ 0% + Lines + 0/148 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service from '@ember/service';
+import Evented from '@ember/object/evented';
+import { inject as service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { dasherize } from '@ember/string';
+import { get } from '@ember/object';
+import { isBlank } from '@ember/utils';
+import { alias } from '@ember/object/computed';
+import { storageFor } from 'ember-local-storage';
+import { debug } from '@ember/debug';
+import lookupUserIp, { getBrowserTimezone } from '../utils/lookup-user-ip';
+ 
+/**
+ * CurrentUserService
+ *
+ * Manages the authenticated user's identity and preferences. Extends Evented
+ * so that any service or component can subscribe to user lifecycle events
+ * directly on this service.
+ *
+ * Session lifecycle events emitted (on both this service and via EventsService
+ * which re-broadcasts them on the universe bus for cross-engine listeners):
+ *
+ *   user.loaded          — fired after a successful login or session restore.
+ *                          Payload: (user, organization, properties)
+ *
+ *   user.updated         — fired when the user record is refreshed in-session
+ *                          (e.g. profile edit). Payload: (user, properties)
+ *
+ *   user.organization_switched — fired when the user switches active org.
+ *                          Payload: (organization, properties)
+ */
+export default class CurrentUserService extends Service.extend(Evented) {
+    @service session;
+    @service store;
+    @service fetch;
+    @service theme;
+    @service notifications;
+    @service intl;
+    @service events;
+    @service universe;
+ 
+    @tracked user = { id: 'anon' };
+    @tracked userSnapshot = { id: 'anon' };
+    @tracked company = {};
+    @tracked permissions = [];
+    @tracked organizations = [];
+    @tracked whoisData = {};
+    @tracked locale = 'en-us';
+ 
+    @storageFor('user-options') options;
+    @storageFor('local-cache') cache;
+    @alias('userSnapshot.id') id;
+    @alias('userSnapshot.name') name;
+    @alias('userSnapshot.phone') phone;
+    @alias('userSnapshot.email') email;
+    @alias('userSnapshot.avatar_url') avatarUrl;
+    @alias('userSnapshot.is_admin') isAdmin;
+    @alias('userSnapshot.company_uuid') companyId;
+    @alias('userSnapshot.company_name') companyName;
+    @alias('userSnapshot.role_name') roleName;
+    @alias('userSnapshot.role') role;
+ 
+    get authenticatedOptionOwnerId() {
+        const authenticatedUserId = this.session?.data?.authenticated?.user;
+ 
+        if (this.session?.isAuthenticated && authenticatedUserId) {
+            return authenticatedUserId;
+        }
+ 
+        try {
+            const localStorageSession = JSON.parse(window.localStorage.getItem('ember_simple_auth-session'));
+            const authenticatedSession = localStorageSession?.authenticated;
+ 
+            if (authenticatedSession?.token && authenticatedSession?.user) {
+                return authenticatedSession.user;
+            }
+        } catch (error) {
+            // Ignore malformed session storage and fall back to the current snapshot.
+        }
+ 
+        return null;
+    }
+ 
+    get optionsPrefix() {
+        return `${this.authenticatedOptionOwnerId || this.id || 'anon'}:`;
+    }
+ 
+    get latitude() {
+        return this.whois('latitude');
+    }
+ 
+    get longitude() {
+        return this.whois('longitude');
+    }
+ 
+    get currency() {
+        return this.whois('currency.code');
+    }
+ 
+    get city() {
+        return this.whois('city');
+    }
+ 
+    get country() {
+        return this.whois('country_code');
+    }
+ 
+    get timezone() {
+        return this.whois('timezone') || getBrowserTimezone();
+    }
+ 
+    async load() {
+        if (this.session.isAuthenticated) {
+            const user = await this.store.findRecord('user', 'me');
+ 
+            // set user
+            await this.setUser(user);
+ 
+            // Load preferences
+            await this.loadPreferences();
+ 
+            return user;
+        }
+ 
+        return null;
+    }
+ 
+    async promiseUser(options = {}) {
+        const NoUserAuthenticatedError = new Error('Failed to authenticate user.');
+        if (!this.session.isAuthenticated) {
+            throw NoUserAuthenticatedError;
+        }
+ 
+        try {
+            const user = await this.store.queryRecord('user', { me: true });
+ 
+            // set user
+            await this.setUser(user);
+ 
+            // Load user whois data
+            await this.loadWhois();
+ 
+            // Load user organizations
+            await this.loadOrganizations();
+ 
+            // Optional callback
+            if (typeof options?.onUserResolved === 'function') {
+                options.onUserResolved(user);
+            }
+ 
+            return user;
+        } catch (error) {
+            debug(`Error loading current user : ${error.message}`);
+            throw error;
+        }
+    }
+ 
+    async loadPreferences() {
+        await this.loadLocale();
+        await this.loadWhois();
+        await this.loadOrganizations();
+    }
+ 
+    async loadLocale() {
+        try {
+            const { locale } = await this.fetch.get('users/locale');
+            this.setLocale(locale);
+ 
+            return locale;
+        } catch (error) {
+            this.notifications.serverError(error);
+        }
+    }
+ 
+    async loadOrganizations() {
+        try {
+            const organizations = await this.fetch.get('auth/organizations', {}, { normalizeToEmberData: true, normalizeModelType: 'company' });
+            this.setOption('organizations', organizations);
+            this.organizations = organizations;
+ 
+            return organizations;
+        } catch (error) {
+            this.notifications.serverError(error);
+        }
+    }
+ 
+    async loadWhois() {
+        // Use frontend IP lookup to get accurate user location
+        // This avoids the issue of server-side lookup returning server IP instead of user IP
+        const whois = await lookupUserIp({
+            timeout: 5000,
+            cache: true,
+        });
+ 
+        // lookupUserIp absorbs its own failures and hands back a fallback rather than
+        // rejecting, so the result, not an exception, says the location is unknown.
+        if (whois._source === 'fallback') {
+            this.notifications.warning('Unable to detect your location. Some features may use default settings.');
+        }
+ 
+        this.whoisData = whois;
+        try {
+            this.setOption('whois', whois);
+        } catch (error) {
+            console.error('[currentUser] Failed to store whois:', error);
+        }
+ 
+        return whois;
+    }
+ 
+    getCompany() {
+        this.company = this.store.peekRecord('company', this.user.company_uuid);
+        return this.company;
+    }
+ 
+    async loadCompany() {
+        const company = this.store.peekRecord('company', this.user.company_uuid);
+        if (company) {
+            return company;
+        }
+ 
+        return this.store.findRecord('company', this.user.company_uuid);
+    }
+ 
+    getUserPermissions(user) {
+        const permissions = [];
+ 
+        // get direct applied permissions
+        if (user.get('permissions')) {
+            permissions.push(...user.get('permissions').toArray());
+        }
+ 
+        // get role permissions and role policies permissions
+        if (user.get('role')) {
+            if (user.get('role.permissions')) {
+                permissions.push(...user.get('role.permissions').toArray());
+            }
+ 
+            if (user.get('role.policies')) {
+                for (let i = 0; i < user.get('role.policies').length; i++) {
+                    const policy = user.get('role.policies').objectAt(i);
+                    if (policy.get('permissions')) {
+                        permissions.push(...policy.get('permissions').toArray());
+                    }
+                }
+            }
+        }
+ 
+        // get direct applied policy permissions
+        if (user.get('policies')) {
+            for (let i = 0; i < user.get('policies').length; i++) {
+                const policy = user.get('policies').objectAt(i);
+                if (policy.get('permissions')) {
+                    permissions.push(...policy.get('permissions').toArray());
+                }
+            }
+        }
+ 
+        return permissions;
+    }
+ 
+    whois(key) {
+        return this.getWhoisProperty(key);
+    }
+ 
+    setLocale(locale) {
+        this.setOption('locale', locale);
+        this.intl.setLocale(locale);
+        this.locale = locale;
+ 
+        return this;
+    }
+ 
+    setOption(key, value) {
+        key = `${this.optionsPrefix}${dasherize(key)}`;
+ 
+        this.options.set(key, value);
+ 
+        return this;
+    }
+ 
+    getOption(key, defaultValue = null) {
+        key = `${this.optionsPrefix}${dasherize(key)}`;
+ 
+        const value = this.options.get(key);
+        return value !== undefined ? value : defaultValue;
+    }
+ 
+    getWhoisProperty(prop) {
+        const whois = this.getOption('whois');
+ 
+        if (!whois || typeof whois !== 'object') {
+            // Fallback to lookup/whois in local-cache
+            const cachedWhois = this.cache.get('lookup/whois');
+            if (cachedWhois) {
+                return get(cachedWhois, prop);
+            }
+            return null;
+        }
+ 
+        return get(whois, prop);
+    }
+ 
+    hasOption(key) {
+        // Read storage directly rather than going through getOption: its
+        // `defaultValue = null` parameter applies whenever the stored value is
+        // undefined, so getOption can never return undefined and this was
+        // always true.
+        key = `${this.optionsPrefix}${dasherize(key)}`;
+ 
+        return this.options.get(key) !== undefined;
+    }
+ 
+    filledOption(key) {
+        return !isBlank(this.getOption(key));
+    }
+ 
+    async getUserSnapshot(user) {
+        const role = await user.get('role');
+        const snapshot = user.serialize({ includeId: true });
+ 
+        return {
+            ...snapshot,
+            id: snapshot.uuid,
+            company_name: user.get('company_name'),
+            role_name: user.get('role_name'),
+            role: {
+                ...role.serialize({ includeId: true }),
+                id: role.get('id'),
+            },
+        };
+    }
+ 
+    getCompanyOption(key, defaultValue = null) {
+        const company = this.store.peekRecord('company', this.companyId);
+        if (company) {
+            const value = get(company, `options.${key}`);
+            if (value === undefined) {
+                return defaultValue;
+            }
+ 
+            return value;
+        }
+ 
+        return defaultValue;
+    }
+ 
+    /**
+     * Sets the current user and fires all user.loaded lifecycle events.
+     *
+     * This is the canonical place where the authenticated user identity is
+     * established. It fires:
+     *
+     *   1. `this.trigger('user.loaded', user)` — on the currentUser service
+     *      itself (Evented), for direct service-level listeners.
+     *
+     *   2. `this.events.trackUserLoaded(user, organization)` — on the events
+     *      service, which re-broadcasts on both the events bus and the universe
+     *      bus so cross-engine listeners (Intercom, PostHog, Attio, etc.) can
+     *      subscribe via `universe.on('user.loaded', handler)`.
+     *
+     * @param {Model} user
+     */
+    async setUser(user) {
+        const snapshot = await this.getUserSnapshot(user);
+ 
+        // Set current user
+        this.set('user', user);
+        this.set('userSnapshot', snapshot);
+        this.theme.syncThemeFromCurrentUser();
+ 
+        // Resolve the organization for event payload
+        const organization = this.store.peekRecord('company', user.get('company_uuid'));
+ 
+        // 1. Trigger on the currentUser Evented bus (backward-compatible)
+        this.trigger('user.loaded', user);
+ 
+        // 2. Fire through the events service — broadcasts on both events bus
+        //    and universe bus for cross-engine listeners
+        if (this.events) {
+            this.events.trackUserLoaded(user, organization);
+        }
+ 
+        // 3. Trigger directly on universe for framework-level uniformity —
+        //    guarantees delivery to all engines on the shared bus
+        if (this.universe) {
+            this.universe.trigger('user.loaded', user, organization);
+        }
+ 
+        // Set permissions
+        this.permissions = this.getUserPermissions(user);
+ 
+        // Set environment from user option
+        this.theme.setEnvironment();
+ 
+        // Set locale
+        if (user.locale) {
+            this.setLocale(user.locale);
+        } else {
+            await this.loadLocale();
+        }
+    }
+ 
+    /**
+     * Fires a user.updated event when the user record is refreshed in-session.
+     * Call this after any in-session profile update to keep integrations in sync.
+     *
+     * @param {Model} user
+     */
+    async refreshUser(user) {
+        const snapshot = await this.getUserSnapshot(user);
+        this.set('user', user);
+        this.set('userSnapshot', snapshot);
+ 
+        const organization = this.store.peekRecord('company', user.get('company_uuid'));
+ 
+        this.trigger('user.updated', user);
+ 
+        if (this.events) {
+            this.events.trackEvent('user.updated', {
+                user_id: user?.id,
+                organization_id: organization?.id,
+                organization_name: organization?.name,
+            });
+        }
+ 
+        if (this.universe) {
+            this.universe.trigger('user.updated', user, organization);
+        }
+    }
+ 
+    /**
+     * Fires a user.organization_switched event when the user changes their
+     * active organization. Call this after a successful org switch.
+     *
+     * @param {Model} organization
+     */
+    switchOrganization(organization) {
+        this.company = organization;
+ 
+        this.trigger('user.organization_switched', organization);
+ 
+        if (this.events) {
+            this.events.trackEvent('user.organization_switched', {
+                organization_id: organization?.id,
+                organization_name: organization?.name,
+            });
+        }
+ 
+        if (this.universe) {
+            this.universe.trigger('user.organization_switched', organization);
+        }
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/events.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/events.js.html new file mode 100644 index 0000000..89db37a --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/events.js.html @@ -0,0 +1,1435 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/events.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services events.js

+
+ +
+ 0% + Statements + 0/75 +
+ + +
+ 0% + Branches + 0/57 +
+ + +
+ 0% + Functions + 0/17 +
+ + +
+ 0% + Lines + 0/75 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service, { inject as service } from '@ember/service';
+import Evented from '@ember/object/evented';
+import config from 'ember-get-config';
+ 
+/**
+ * EventsService
+ *
+ * Provides a centralized, standardized event tracking system for Fleetbase.
+ *
+ * This service is the single source of truth for all application lifecycle
+ * events. It emits every event on two buses simultaneously:
+ *
+ *   1. Its own Evented bus — for direct service/component listeners:
+ *        this.events.on('user.loaded', handler)
+ *
+ *   2. The universe service bus — for cross-engine listeners (recommended
+ *      for use in Ember Engines / extensions):
+ *        this.universe.on('user.loaded', handler)
+ *
+ * ─────────────────────────────────────────────────────────────────────────────
+ * Session Lifecycle Events
+ * ─────────────────────────────────────────────────────────────────────────────
+ *
+ *   session.authenticated    Fired after a successful login or session restore.
+ *                            Payload: (properties)
+ *
+ *   session.invalidated      Fired after the session is destroyed (logout).
+ *                            Payload: (duration_seconds, properties)
+ *
+ *   session.terminated       Alias for session.invalidated — provided for
+ *                            backward compatibility and semantic clarity.
+ *                            Payload: (duration_seconds, properties)
+ *
+ *   user.loaded              Fired after the authenticated user record and
+ *                            organization have been fully loaded into the
+ *                            currentUser service. This is the canonical event
+ *                            for integrations to boot (Intercom, PostHog, etc.)
+ *                            Payload: (user, organization, properties)
+ *
+ *   user.updated             Fired when the user record is refreshed in-session
+ *                            (e.g. after a profile edit).
+ *                            Payload: (user, properties)
+ *
+ *   user.deauthenticated     Fired when the user identity is cleared on logout.
+ *                            Semantic alias for session.invalidated — use this
+ *                            to shut down integrations cleanly (Intercom, etc.)
+ *                            Payload: (duration_seconds, properties)
+ *
+ *   user.organization_switched  Fired when the user switches their active org.
+ *                            Payload: (organization, properties)
+ *
+ * ─────────────────────────────────────────────────────────────────────────────
+ * Resource Events
+ * ─────────────────────────────────────────────────────────────────────────────
+ *
+ *   resource.created         Generic resource creation.
+ *   resource.updated         Generic resource update.
+ *   resource.deleted         Generic resource deletion.
+ *   resource.imported        Bulk import.
+ *   resource.exported        Export.
+ *   resource.bulk_action     Bulk action (delete, archive, etc.)
+ *   {modelName}.created      Model-specific creation (e.g. order.created)
+ *   {modelName}.updated      Model-specific update.
+ *   {modelName}.deleted      Model-specific deletion.
+ *   {modelName}.exported     Model-specific export.
+ *
+ * @class EventsService
+ * @extends Service
+ */
+export default class EventsService extends Service.extend(Evented) {
+    @service universe;
+    @service currentUser;
+ 
+    // =========================================================================
+    // Session Lifecycle Tracking
+    // =========================================================================
+ 
+    /**
+     * Tracks a successful authentication (login or session restore).
+     *
+     * Called by SessionService.handleAuthentication().
+     *
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackSessionAuthenticated(props = {}) {
+        const properties = this.#enrichProperties(props);
+        this.#trigger('session.authenticated', properties);
+    }
+ 
+    /**
+     * Tracks when a user session is terminated (logout).
+     *
+     * Called by SessionService.handleInvalidation(). Also fires the semantic
+     * `user.deauthenticated` event so integrations can react to the user
+     * identity being cleared without needing to know about session internals.
+     *
+     * @param {Number|null} duration - Session duration in seconds (null if unknown)
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackSessionTerminated(duration, props = {}) {
+        const properties = this.#enrichProperties({
+            session_duration: duration,
+            ...props,
+        });
+ 
+        // Fire session.invalidated (technical event)
+        this.#trigger('session.invalidated', duration, properties);
+ 
+        // Fire session.terminated (backward-compatible alias)
+        this.#trigger('session.terminated', duration, properties);
+ 
+        // Fire user.deauthenticated (semantic event for integrations)
+        this.#trigger('user.deauthenticated', duration, properties);
+    }
+ 
+    /**
+     * Tracks when the current user is loaded (session initialized).
+     *
+     * Called by CurrentUserService.setUser() after a successful login or
+     * session restore. This is the canonical event for integrations to boot.
+     *
+     * @param {Object} user         - The authenticated user model
+     * @param {Object} organization - The user's active organization model
+     * @param {Object} [props={}]   - Additional properties to include
+     */
+    trackUserLoaded(user, organization, props = {}) {
+        const properties = this.#enrichProperties({
+            user_id: user?.id,
+            organization_id: organization?.id,
+            organization_name: organization?.name,
+            ...props,
+        });
+ 
+        this.#trigger('user.loaded', user, organization, properties);
+    }
+ 
+    /**
+     * Tracks when the user record is refreshed in-session (e.g. profile edit).
+     *
+     * Called by CurrentUserService.refreshUser().
+     *
+     * @param {Object} user       - The updated user model
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackUserUpdated(user, props = {}) {
+        const properties = this.#enrichProperties({
+            user_id: user?.id,
+            ...props,
+        });
+ 
+        this.#trigger('user.updated', user, properties);
+    }
+ 
+    /**
+     * Tracks when the user switches their active organization.
+     *
+     * Called by CurrentUserService.switchOrganization().
+     *
+     * @param {Object} organization - The new active organization model
+     * @param {Object} [props={}]   - Additional properties to include
+     */
+    trackOrganizationSwitched(organization, props = {}) {
+        const properties = this.#enrichProperties({
+            organization_id: organization?.id,
+            organization_name: organization?.name,
+            ...props,
+        });
+ 
+        this.#trigger('user.organization_switched', organization, properties);
+    }
+ 
+    // =========================================================================
+    // Resource Event Tracking
+    // =========================================================================
+ 
+    /**
+     * Tracks the creation of a resource
+     *
+     * @param {Object} resource - The created resource/model
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackResourceCreated(resource, props = {}) {
+        const events = this.#getResourceEvents(resource, 'created');
+        const properties = this.#enrichProperties({
+            ...this.#getSafeProperties(resource),
+            ...props,
+        });
+ 
+        events.forEach((eventName) => {
+            this.#trigger(eventName, resource, properties);
+        });
+    }
+ 
+    /**
+     * Tracks the update of a resource
+     *
+     * @param {Object} resource - The updated resource/model
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackResourceUpdated(resource, props = {}) {
+        const events = this.#getResourceEvents(resource, 'updated');
+        const properties = this.#enrichProperties({
+            ...this.#getSafeProperties(resource),
+            ...props,
+        });
+ 
+        events.forEach((eventName) => {
+            this.#trigger(eventName, resource, properties);
+        });
+    }
+ 
+    /**
+     * Tracks the deletion of a resource
+     *
+     * @param {Object} resource - The deleted resource/model
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackResourceDeleted(resource, props = {}) {
+        const events = this.#getResourceEvents(resource, 'deleted');
+        const properties = this.#enrichProperties({
+            ...this.#getSafeProperties(resource),
+            ...props,
+        });
+ 
+        events.forEach((eventName) => {
+            this.#trigger(eventName, resource, properties);
+        });
+    }
+ 
+    /**
+     * Tracks a bulk import of resources
+     *
+     * @param {String} modelName - The name of the model being imported
+     * @param {Number} count - Number of resources imported
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackResourceImported(modelName, count, props = {}) {
+        const properties = this.#enrichProperties({
+            model_name: modelName,
+            count: count,
+            ...props,
+        });
+ 
+        this.#trigger('resource.imported', modelName, count, properties);
+    }
+ 
+    /**
+     * Tracks a resource export
+     *
+     * @param {String} modelName - The name of the model being exported
+     * @param {String} format - Export format (csv, xlsx, etc.)
+     * @param {Object} [params={}] - Export parameters/filters
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackResourceExported(modelName, format, params = {}, props = {}) {
+        const properties = this.#enrichProperties({
+            model_name: modelName,
+            export_format: format,
+            has_filters: !!(params && Object.keys(params).length > 0),
+            ...props,
+        });
+ 
+        this.#trigger('resource.exported', modelName, format, params, properties);
+ 
+        // Also trigger model-specific export event
+        const specificEvent = `${modelName}.exported`;
+        this.#trigger(specificEvent, modelName, format, params, properties);
+    }
+ 
+    /**
+     * Tracks a bulk action on multiple resources
+     *
+     * @param {String} verb - The action verb (delete, archive, etc.)
+     * @param {Array} resources - Array of selected resources
+     * @param {Object} [props={}] - Additional properties to include
+     */
+    trackBulkAction(verb, resources, props = {}) {
+        const firstResource = resources && resources.length > 0 ? resources[0] : null;
+        const modelName = this.#getModelName(firstResource);
+ 
+        const properties = this.#enrichProperties({
+            action: verb,
+            count: resources?.length || 0,
+            model_name: modelName,
+            ...props,
+        });
+ 
+        this.#trigger('resource.bulk_action', verb, resources, firstResource, properties);
+    }
+ 
+    // =========================================================================
+    // Generic Event Tracking
+    // =========================================================================
+ 
+    /**
+     * Tracks a generic custom event
+     *
+     * @param {String} eventName - The event name (dot notation)
+     * @param {Object} [props={}] - Event properties
+     */
+    trackEvent(eventName, props = {}) {
+        const properties = this.#enrichProperties(props);
+        this.#trigger(eventName, properties);
+    }
+ 
+    /**
+     * Checks if event tracking is enabled
+     *
+     * @returns {Boolean}
+     */
+    isEnabled() {
+        const eventsConfig = config?.events || {};
+        return eventsConfig.enabled !== false; // Enabled by default
+    }
+ 
+    // =========================================================================
+    // Private Methods (using # syntax)
+    // =========================================================================
+ 
+    /**
+     * Triggers an event on both the events service and universe service.
+     *
+     * This dual event system allows listeners to subscribe to events on either:
+     *   - this.events.on('event.name', handler)  — local listeners
+     *   - this.universe.on('event.name', handler) — cross-engine listeners
+     *
+     * @private
+     * @param {String} eventName - The event name
+     * @param {...*} args - Arguments to pass to event listeners
+     */
+    #trigger(eventName, ...args) {
+        if (!this.isEnabled()) {
+            return;
+        }
+ 
+        // Debug logging if enabled
+        if (config?.events?.debug) {
+            console.log(`[Events] ${eventName}`, args);
+        }
+ 
+        // Trigger on events service (local listeners)
+        this.trigger(eventName, ...args);
+ 
+        // Trigger on universe service (cross-engine listeners)
+        if (this.universe) {
+            this.universe.trigger(eventName, ...args);
+        } else {
+            console.warn('[Events] Universe service not available');
+        }
+    }
+ 
+    /**
+     * Generates both generic and specific event names for a resource action
+     *
+     * @private
+     * @param {Object} resource - The resource/model
+     * @param {String} action - The action (created, updated, deleted)
+     * @returns {Array<String>} Array of event names
+     */
+    #getResourceEvents(resource, action) {
+        const modelName = this.#getModelName(resource);
+        return [`resource.${action}`, `${modelName}.${action}`];
+    }
+ 
+    /**
+     * Extracts safe, serializable properties from a resource
+     *
+     * @private
+     * @param {Object} resource - The resource/model
+     * @returns {Object} Safe properties object
+     */
+    #getSafeProperties(resource) {
+        if (!resource) {
+            return {};
+        }
+ 
+        const props = {
+            id: resource.id,
+            model_name: this.#getModelName(resource),
+        };
+ 
+        // Add common properties if available
+        const commonProps = ['name', 'status', 'type', 'slug', 'public_id'];
+        commonProps.forEach((prop) => {
+            if (resource[prop] !== undefined && resource[prop] !== null) {
+                props[prop] = resource[prop];
+            }
+        });
+ 
+        return props;
+    }
+ 
+    /**
+     * Enriches properties with user, organization, and timestamp context
+     *
+     * @private
+     * @param {Object} props - Base properties
+     * @returns {Object} Enriched properties
+     */
+    #enrichProperties(props) {
+        const eventsConfig = config?.events || {};
+        const enrichConfig = eventsConfig.enrich || {};
+        const enriched = { ...props };
+ 
+        // Add user context if enabled
+        if (enrichConfig.user !== false && this.currentUser?.user) {
+            enriched.user_id = this.currentUser.user.id;
+        }
+ 
+        // Add organization context if enabled
+        if (enrichConfig.organization !== false && this.currentUser?.organization) {
+            enriched.organization_id = this.currentUser.organization.id;
+        }
+ 
+        // Add timestamp if enabled
+        if (enrichConfig.timestamp !== false) {
+            enriched.timestamp = new Date().toISOString();
+        }
+ 
+        return enriched;
+    }
+ 
+    /**
+     * Safely extracts the model name from a resource
+     *
+     * @private
+     * @param {Object} resource - The resource/model
+     * @returns {String} Model name or 'unknown'
+     */
+    #getModelName(resource) {
+        if (!resource) {
+            return 'unknown';
+        }
+ 
+        // Try multiple ways to get model name
+        if (resource.constructor?.modelName) {
+            return resource.constructor.modelName;
+        }
+ 
+        if (resource._internalModel?.modelName) {
+            return resource._internalModel.modelName;
+        }
+ 
+        if (resource.modelName) {
+            return resource.modelName;
+        }
+ 
+        return 'unknown';
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/fetch.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/fetch.js.html new file mode 100644 index 0000000..df2e690 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/fetch.js.html @@ -0,0 +1,2236 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/fetch.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services fetch.js

+
+ +
+ 0.47% + Statements + 1/212 +
+ + +
+ 0.53% + Branches + 1/187 +
+ + +
+ 0% + Functions + 0/46 +
+ + +
+ 0.48% + Lines + 1/206 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { inject as service } from '@ember/service';
+import { get, set } from '@ember/object';
+import { isBlank } from '@ember/utils';
+import { dasherize, underscore } from '@ember/string';
+import { isArray } from '@ember/array';
+import { singularize, pluralize } from 'ember-inflector';
+import { task } from 'ember-concurrency';
+import { storageFor } from 'ember-local-storage';
+import { add, isAfter, parseISO } from 'date-fns';
+import { decompress as decompressJson } from 'compress-json';
+import config from 'ember-get-config';
+import corslite from '../utils/corslite';
+import getMimeType from '../utils/get-mime-type';
+import download from '../utils/download';
+import getUserOptions from '../utils/get-user-options';
+import isEmptyObject from '../utils/is-empty-object';
+import isObject from '../utils/is-object';
+import fetch from 'fetch';
+ 
+/* istanbul ignore if -- runs at import; whichever of adapters/application or services/fetch loads first sets the host, so the other can never enter */
+if (isBlank(config.API.host)) {
+    config.API.host = `${window.location.protocol}//${window.location.hostname}`;
+}
+ 
+export default class FetchService extends Service {
+    /**
+     * Creates an instance of FetchService.
+     * @memberof FetchService
+     */
+    constructor() {
+        super(...arguments);
+ 
+        this.headers = this.getHeaders();
+        this.host = get(config, 'API.host');
+        this.namespace = get(config, 'API.namespace');
+    }
+ 
+    /**
+     * Mutable headers property.
+     *
+     * @var {Array}
+     */
+    @tracked headers;
+ 
+    /**
+     * Mutable namespace property.
+     *
+     * @var {String}
+     */
+    @tracked namespace;
+ 
+    /**
+     * Mutable host property.
+     *
+     * @var {String}
+     */
+    @tracked host;
+ 
+    /**
+     * Gets headers that should be sent with request.
+     *
+     * @return {Object}
+     */
+    getHeaders() {
+        const headers = {};
+        const isAuthenticated = this.session.isAuthenticated;
+        const userId = this.session.data.authenticated.user;
+        const userOptions = getUserOptions();
+        const isSandbox = get(userOptions, `${userId}:sandbox`) === true;
+        // See the note in adapters/application.js: `setOption` dasherizes, so
+        // the stored key is `<user>:test-key`, not `<user>:testKey`.
+        const testKey = get(userOptions, `${userId}:test-key`);
+ 
+        headers['Content-Type'] = 'application/json';
+ 
+        if (isAuthenticated) {
+            headers['Authorization'] = `Bearer ${this.session.data.authenticated.token}`;
+        }
+ 
+        if (isAuthenticated && isSandbox) {
+            headers['Access-Console-Sandbox'] = true;
+        }
+ 
+        if (isAuthenticated && !isBlank(testKey)) {
+            headers['Access-Console-Sandbox-Key'] = testKey;
+        }
+ 
+        return headers;
+    }
+ 
+    /**
+     * Updates headers property before making request.
+     *
+     * @return {FetchService}
+     * @memberof FetchService
+     */
+    refreshHeaders() {
+        this.headers = this.getHeaders();
+ 
+        return this;
+    }
+ 
+    /**
+     * Allows namespace to be set before making fetch request.
+     *
+     * @param {String} namespace
+     * @return {FetchService}
+     * @memberof FetchService
+     */
+    setNamespace(namespace) {
+        this.namespace = namespace;
+ 
+        return this;
+    }
+ 
+    /**
+     * Allows host to be set before making fetch request.
+     *
+     * @param {String} host
+     * @return {FetchService}
+     * @memberof FetchService
+     */
+    setHost(host) {
+        this.host = host;
+ 
+        return this;
+    }
+ 
+    /**
+     * Credentials
+     *
+     * @var {String}
+     */
+    credentials = 'include';
+ 
+    /**
+     * Inject the `store` service
+     *
+     * @var {Service}
+     */
+    @service store;
+ 
+    /**
+     * Inject the `session` service
+     *
+     * @var {Service}
+     */
+    @service session;
+ 
+    /**
+     * Inject the `currentUser` service
+     *
+     * @var {Service}
+     */
+    @service currentUser;
+ 
+    /**
+     * Inject the `notifications` service
+     *
+     * @var {Service}
+     */
+    @service notifications;
+ 
+    /**
+     * Local cache for some static requests
+     *
+     * @var StorageObject
+     */
+    @storageFor('local-cache') localCache;
+ 
+    /**
+     * Normalizes a model response from fetch to a ember data model
+     *
+     * @param  {Object} payload   A response from a network request
+     * @param  {String} modelType The type of model to be normalized too
+     *
+     * @return {Model}            An ember model
+     */
+    normalizeModel(payload, modelType = null) {
+        if (modelType === null) {
+            const modelTypeKeys = Object.keys(payload);
+            // `Object.keys` returns a plain array, which has no `firstObject`
+            // once prototype extensions are off — this silently yielded
+            // undefined, so the payload was returned unnormalized.
+            modelType = modelTypeKeys.length ? modelTypeKeys[0] : false;
+        }
+ 
+        if (typeof modelType !== 'string') {
+            return payload;
+        }
+ 
+        const type = dasherize(singularize(modelType));
+        const pluralizedModelType = pluralize(underscore(modelType));
+ 
+        if (isArray(payload)) {
+            return payload.map((instance) => this.store.push(this.store.normalize(type, instance)));
+        }
+ 
+        if (isArray(payload[pluralizedModelType])) {
+            return payload[pluralizedModelType].map((instance) => this.store.push(this.store.normalize(type, instance)));
+        }
+ 
+        if (isArray(payload[modelType])) {
+            return payload[modelType].map((instance) => this.store.push(this.store.normalize(type, instance)));
+        }
+ 
+        if (!isBlank(payload) && isBlank(payload[modelType])) {
+            return this.jsonToModel(payload, type);
+        }
+ 
+        return this.store.push(this.store.normalize(type, payload[modelType]));
+    }
+ 
+    /**
+     * Normalizes a model response from a JSON object or string
+     *
+     * @param  {Object} payload   A response from a network request
+     * @param  {String} modelType The type of model to be normalized too
+     *
+     * @return {Model}            An ember model
+     */
+    jsonToModel(attributes = {}, modelType) {
+        if (typeof attributes === 'string') {
+            attributes = JSON.parse(attributes);
+        }
+ 
+        const type = dasherize(modelType);
+        const normalized = this.store.push(this.store.normalize(type, attributes));
+ 
+        return normalized;
+    }
+ 
+    /**
+     * Parses the JSON returned by a network request
+     *
+     * @param  {Object} response A response from a network request
+     * @return {Object}          The parsed JSON, status from the response
+     *
+     * @return {Promise}
+     */
+    async parseJSON(response) {
+        try {
+            const compressedHeader = await response.headers.get('x-compressed-json');
+            let json;
+ 
+            if (compressedHeader === '1') {
+                // Handle compressed json
+                const text = await response.text();
+                json = JSON.parse(text);
+                json = decompressJson(json);
+                json = JSON.parse(json);
+            } else {
+                // Handle regular json
+                json = await response.json();
+            }
+ 
+            return {
+                statusText: response.statusText,
+                status: response.status,
+                ok: response.ok,
+                json,
+            };
+        } catch (error) {
+            throw new Error('Error processing response: ' + error.message);
+        }
+    }
+ 
+    /**
+     * The base request method
+     *
+     * @param {String} path
+     * @param {String} method
+     * @param {Object} data
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    request(path, method = 'GET', data = {}, options = {}) {
+        const headers = Object.assign(this.getHeaders(), options.headers ?? {});
+        const host = options.host ?? this.host;
+        const namespace = options.namespace ?? this.namespace;
+        const url = options.externalRequest === true ? path : [host, namespace, path].filter(Boolean).join('/');
+ 
+        return new Promise((resolve, reject) => {
+            return fetch(url, {
+                method,
+                mode: options.mode || 'cors',
+                credentials: options.credentials || this.credentials,
+                headers,
+                ...data,
+            })
+                .then(this.parseJSON)
+                .then((response) => {
+                    if (response.ok) {
+                        if (options.normalizeToEmberData) {
+                            const normalized = this.normalizeModel(response.json, options.normalizeModelType);
+ 
+                            if (typeof options.onSuccess === 'function') {
+                                options.onSuccess(normalized);
+                            }
+ 
+                            return resolve(normalized);
+                        }
+ 
+                        if (typeof options.onSuccess === 'function') {
+                            options.onSuccess(response.json);
+                        }
+ 
+                        return resolve(response.json);
+                    }
+ 
+                    if (typeof options.onError === 'function') {
+                        options.onError(response.json);
+                    }
+ 
+                    if (options.rawError) {
+                        return reject(response.json);
+                    }
+ 
+                    if (isArray(response.json.errors)) {
+                        // Decoded JSON is a plain array, so `firstObject` was
+                        // undefined and every such error surfaced as the
+                        // literal string "undefined".
+                        return reject(new Error(response.json.errors[0] ?? response.statusText));
+                    }
+ 
+                    if (response.json.error && typeof response.json.error === 'string') {
+                        return reject(new Error(response.json.error));
+                    }
+ 
+                    // error objects
+                    if (response.json.error && isObject(response.json.error)) {
+                        return reject(response.json.error);
+                    }
+ 
+                    if (response.json.message && typeof response.json.message === 'string') {
+                        return reject(new Error(response.json.message));
+                    }
+ 
+                    return reject(response.json);
+                })
+                .catch(reject);
+        });
+    }
+ 
+    /**
+     * Makes a GET request with fetch
+     *
+     * @param {String} path
+     * @param {Object} query
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    get(path, query = {}, options = {}) {
+        // handle if want to request from cache
+        if (options.fromCache === true) {
+            return this.cachedGet(...arguments);
+        }
+ 
+        const urlParams = !isEmptyObject(query) ? new URLSearchParams(query).toString() : '';
+ 
+        return this.request(`${path}${urlParams ? '?' + urlParams : ''}`, 'GET', {}, options);
+    }
+ 
+    /**
+     * Makes a GET request with fetch, but if the fetch is stored in local cache,
+     * retrieve from storage to prevent unnecessary netwrok request
+     *
+     * @param {String} path
+     * @param {Object} query
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    cachedGet(path, query = {}, options = {}) {
+        const pathKey = dasherize(path);
+        const pathKeyVersion = new Date().toISOString();
+ 
+        const request = () => {
+            delete options.fromCache;
+            return this.get(path, query, options).then((response) => {
+                // cache the response
+                this.localCache.set(pathKey, response);
+                this.localCache.set(`${pathKey}-version`, pathKeyVersion);
+ 
+                // return response
+                return response;
+            });
+        };
+ 
+        // check to see if in storage already
+        if (this.localCache.get(pathKey)) {
+            return new Promise((resolve) => {
+                // get cached data
+                const data = this.localCache.get(pathKey);
+ 
+                // get the path key version value
+                const version = this.localCache.get(`${pathKey}-version`);
+                const expirationInterval = options.expirationInterval ?? 3;
+                const expirationIntervalUnit = pluralize(options.expirationIntervalUnit ?? 'days');
+ 
+                // Expire once the whole interval has passed since the cache version. (The
+                // interval's unit component alone would read a month-old cache as 0 days old.)
+                const shouldExpire = isAfter(new Date(), add(parseISO(version), { [expirationIntervalUnit]: expirationInterval }));
+ 
+                // if the version is older than 3 days clear it
+                if (!version || shouldExpire || options.clearData === true) {
+                    this.flushRequestCache(path);
+                    return request().then(resolve);
+                }
+ 
+                if (options.normalizeToEmberData) {
+                    return resolve(this.normalizeModel(data, options.normalizeModelType));
+                }
+ 
+                // return cached response
+                return resolve(data);
+            });
+        }
+ 
+        // if no cached data request from server
+        return request();
+    }
+ 
+    /**
+     * Flushes the local cache for a specific path by setting its value and version to undefined.
+     *
+     * @param {string} path - The path for which the cache should be flushed.
+     */
+    flushRequestCache(path) {
+        const pathKey = dasherize(path);
+ 
+        this.localCache.set(pathKey, undefined);
+        this.localCache.set(`${pathKey}-version`, undefined);
+    }
+ 
+    /**
+     * Determines whether the cache should be reset by comparing the current version
+     * of the console with the cached version. If they differ, the cache is cleared
+     * and the new version is saved.
+     */
+    shouldResetCache() {
+        const consoleVersion = this.localCache.get('console-version');
+ 
+        if (!consoleVersion || consoleVersion !== config.APP.version) {
+            this.localCache.clear();
+            this.localCache.set('console-version', config.APP.version);
+        }
+    }
+ 
+    /**
+     * Makes a POST request with fetch
+     *
+     * @param {String} path
+     * @param {Object} data
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    post(path, data = {}, options = {}) {
+        return this.request(path, 'POST', { body: JSON.stringify(data) }, options);
+    }
+ 
+    /**
+     * Makes a PUT request with fetch
+     *
+     * @param {String} path
+     * @param {Object} data
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    put(path, data = {}, options = {}) {
+        return this.request(path, 'PUT', { body: JSON.stringify(data) }, options);
+    }
+ 
+    /**
+     * Makes a DELETE request with fetch
+     *
+     * @param {String} path
+     * @param {Object} data
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    delete(path, data = {}, options = {}) {
+        return this.request(path, 'DELETE', { body: JSON.stringify(data) }, options);
+    }
+ 
+    /**
+     * Makes a PATCH request with fetch
+     * @param {String} path
+     * @param {Object} data
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    patch(path, data = {}, options = {}) {
+        return this.request(path, 'PATCH', { body: JSON.stringify(data) }, options);
+    }
+ 
+    /**
+     * Makes a upload request with fetch
+     *
+     * @param {String} path
+     * @param {Array} files
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    upload(path, files = [], options = {}) {
+        const body = new FormData();
+        files.forEach((file) => {
+            body.append('file', file);
+        });
+        return this.request(path, 'POST', { body }, options);
+    }
+ 
+    /**
+     * Sends request to routing service.
+     *
+     * @param {Array} coordinates
+     * @param {Object} query
+     * @param {String} service
+     * @param {String} profile
+     * @param {String} version
+     */
+    routing(coordinates, query = {}, options = {}) {
+        let service = options?.service ?? 'trip';
+        let profile = options?.profile ?? 'driving';
+        let version = options?.version ?? 'v1';
+        let host = options?.host ?? `https://${options?.subdomain ?? 'routing'}.fleetbase.io`;
+        let route = coordinates.map((coords) => coords.join(',')).join(';');
+        let params = !isEmptyObject(query) ? new URLSearchParams(query).toString() : '';
+        let path = `${host}/${service}/${version}/${profile}/${route}`;
+        let url = `${path}${params ? '?' + params : ''}`;
+ 
+        return new Promise((resolve, reject) => {
+            corslite(url, (container, xhr) => {
+                if (!xhr || !xhr.response) {
+                    reject(new Error('Request failed.'));
+                    return;
+                }
+ 
+                let response = xhr.response;
+                let isJson = typeof response === 'string' && response.startsWith('{');
+ 
+                resolve(isJson ? JSON.parse(response) : response);
+            });
+        });
+    }
+ 
+    /**
+     * Concurrency task to handle a file upload
+     *
+     * @void
+     */
+    @task({ enqueue: true, maxConcurrency: 3 }) *uploadFile(file, params = {}, callback, errorCallback) {
+        const { queue } = file;
+        const headers = this.getHeaders();
+ 
+        // make sure this task runs once for this file in correct state
+        // this can occur when the task is called twice when upload button exists inside upload dropzone
+        if (['queued', 'failed', 'timed_out', 'aborted'].indexOf(file.state) === -1) return;
+ 
+        // remove Content-Type header
+        delete headers['Content-Type'];
+ 
+        try {
+            const upload = yield file
+                .upload(`${get(config, 'API.host')}/${get(config, 'API.namespace')}/files/upload`, {
+                    data: {
+                        ...params,
+                        file_size: file.size,
+                    },
+                    withCredentials: true,
+                    headers,
+                })
+                .then((response) => response.json())
+                .catch((error) => {
+                    this.notifications.serverError(error, 'File upload failed.');
+ 
+                    if (typeof errorCallback === 'function') {
+                        errorCallback(error);
+                    }
+                });
+ 
+            if (upload) {
+                const model = this.store.push(this.store.normalize('file', upload.file));
+                set(file, 'model', model);
+ 
+                if (typeof callback === 'function') {
+                    callback(model);
+                }
+ 
+                return model;
+            }
+ 
+            return null;
+        } catch (error) {
+            queue.remove(file);
+            this.notifications.serverError(error, 'File upload failed.');
+ 
+            if (typeof errorCallback === 'function') {
+                errorCallback(error);
+            }
+        }
+    }
+ 
+    /**
+     * Downloads blob of the request path to user
+     *
+     * @param {String} path
+     * @param {Object} query
+     * @param {Object} options
+     *
+     * @return {Promise}
+     */
+    download(path, query = {}, options = {}) {
+        const headers = Object.assign(this.getHeaders(), options.headers ?? {});
+        const method = options.method ?? 'GET';
+        const credentials = options.credentials ?? this.credentials;
+        const baseUrl = options.externalRequest === true ? '' : `${options.host ?? this.host}/${options.namespace || this.namespace}`;
+        const isReadOnlyRequest = ['GET', 'HEAD'].includes(method.toUpperCase());
+        const params = isReadOnlyRequest && !isEmptyObject(query) ? `?${new URLSearchParams(query).toString()}` : '';
+        const body = !isReadOnlyRequest ? JSON.stringify(query) : {};
+        const fetchOptions = {
+            method,
+            credentials,
+            headers,
+        };
+ 
+        // Only supply body to fetch if not GET or HEAD request
+        if (!isReadOnlyRequest) {
+            fetchOptions.body = body;
+        }
+ 
+        return new Promise((resolve, reject) => {
+            return fetch(`${baseUrl ? baseUrl + '/' : ''}${path}${params}`, fetchOptions)
+                .then((response) => {
+                    options.fileName = this.getFilenameFromResponse(response, options.fileName);
+                    options.mimeType = this.getMimeTypeFromResponse(response, options.mimeType);
+ 
+                    if (!options.mimeType) {
+                        options.mimeType = getMimeType(options.fileName);
+                    }
+ 
+                    return response;
+                })
+                .then((response) => response.blob())
+                .then((blob) => resolve(download(blob, options.fileName, options.mimeType)))
+                .catch((error) => {
+                    reject(error);
+                });
+        });
+    }
+ 
+    getFilenameFromResponse(response, defaultFilename = null) {
+        const contentDisposition = response.headers.get('content-disposition');
+        let fileName = defaultFilename;
+ 
+        // A name the caller asked for wins; the header only fills in when there is none.
+        if (contentDisposition && !defaultFilename) {
+            const results = /filename=(.*)/.exec(contentDisposition);
+ 
+            if (isArray(results) && results.length > 1) {
+                fileName = results[1];
+ 
+                // clean fileName
+                fileName = fileName.replaceAll('"', '');
+            }
+        }
+ 
+        return fileName;
+    }
+ 
+    getMimeTypeFromResponse(response, defaultMimeType = null) {
+        const contentType = response.headers.get('content-type');
+        let mimeType = defaultMimeType;
+ 
+        if (contentType) {
+            // The media type is everything before the first parameter, if there are any:
+            // `text/csv` and `text/csv; charset=utf-8` both mean `text/csv`.
+            mimeType = contentType.split(';')[0].trim();
+        }
+ 
+        return mimeType;
+    }
+ 
+    fetchOrderConfigurations(params = {}) {
+        return new Promise((resolve, reject) => {
+            this.request('fleet-ops/order-configs/get-installed', params)
+                .then((configs) => {
+                    const serialized = [];
+ 
+                    for (let i = 0; i < configs.length; i++) {
+                        // `configs` is decoded JSON and `serialized` is a plain
+                        // array literal; neither has the Ember array methods
+                        // once prototype extensions are off.
+                        const config = configs[i];
+                        const normalizedConfig = this.store.normalize('order-config', config);
+                        const serializedConfig = this.store.push(normalizedConfig);
+ 
+                        serialized.push(serializedConfig);
+                    }
+ 
+                    resolve(serialized);
+                })
+                .catch((error) => {
+                    reject(error);
+                });
+        });
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/index.html new file mode 100644 index 0000000..904317d --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/index.html @@ -0,0 +1,206 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services

+
+ +
+ 0.14% + Statements + 1/709 +
+ + +
+ 0.21% + Branches + 1/467 +
+ + +
+ 0% + Functions + 0/196 +
+ + +
+ 0.14% + Lines + 1/692 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
current-user.js +
+
0%0/1500%0/660%0/290%0/148
events.js +
+
0%0/750%0/570%0/170%0/75
fetch.js +
+
0.47%1/2120.53%1/1870%0/460.48%1/206
notifications.js +
+
0%0/130%0/120%0/20%0/13
session.js +
+
0%0/680%0/240%0/190%0/68
universe.js +
+
0%0/1390%0/1120%0/630%0/130
url-search-params.js +
+
0%0/520%0/90%0/200%0/52
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/notifications.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/notifications.js.html new file mode 100644 index 0000000..1624d27 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/notifications.js.html @@ -0,0 +1,184 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/notifications.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services notifications.js

+
+ +
+ 0% + Statements + 0/13 +
+ + +
+ 0% + Branches + 0/12 +
+ + +
+ 0% + Functions + 0/2 +
+ + +
+ 0% + Lines + 0/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import EmberNotificationsService from 'ember-cli-notifications/services/notifications';
+import { isArray } from '@ember/array';
+import getWithDefault from '../utils/get-with-default';
+ 
+export default class NotificationsService extends EmberNotificationsService {
+    serverError(error, fallbackMessage = 'Oops! Something went wrong with your request.', options = {}) {
+        if (error && isArray(error.errors)) {
+            const errors = getWithDefault(error, 'errors');
+            const errorMessage = getWithDefault(errors, '0', fallbackMessage);
+ 
+            return this.error(errorMessage, options);
+        }
+ 
+        if (error instanceof Error) {
+            const errorMessage = getWithDefault(error, 'message', fallbackMessage);
+            return this.error(errorMessage, options);
+        }
+ 
+        if (typeof error === 'string') {
+            return this.error(error, options);
+        }
+ 
+        return this.error(fallbackMessage, options);
+    }
+ 
+    invoke(type, message, ...params) {
+        if (typeof message === 'function') {
+            this[type](message(...params));
+        } else {
+            this[type](message);
+        }
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/session.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/session.js.html new file mode 100644 index 0000000..0f60dfd --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/session.js.html @@ -0,0 +1,1093 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/session.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services session.js

+
+ +
+ 0% + Statements + 0/68 +
+ + +
+ 0% + Branches + 0/24 +
+ + +
+ 0% + Functions + 0/19 +
+ + +
+ 0% + Lines + 0/68 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import SimpleAuthSessionService from 'ember-simple-auth/services/session';
+import { tracked } from '@glimmer/tracking';
+import { inject as service } from '@ember/service';
+import { later } from '@ember/runloop';
+import { debug } from '@ember/debug';
+import getWithDefault from '../utils/get-with-default';
+ 
+/**
+ * SessionService
+ *
+ * Extends ember-simple-auth's session service to add:
+ *
+ *   - Proper lifecycle event firing through the EventsService (which
+ *     re-broadcasts on both the events bus and the universe bus).
+ *   - Session start timestamp tracking for duration calculation.
+ *
+ * Session lifecycle events fired (via EventsService → universe bus):
+ *
+ *   session.authenticated   — fired after a successful login/session restore.
+ *                             Payload: (properties)
+ *
+ *   session.invalidated     — fired after the session is destroyed (logout).
+ *                             Payload: (duration_seconds, properties)
+ *
+ *   user.deauthenticated    — alias for session.invalidated, provided as a
+ *                             semantic convenience for integrations that want
+ *                             to react to the user identity being cleared
+ *                             (e.g. Intercom shutdown, PostHog reset).
+ *                             Payload: (duration_seconds, properties)
+ *
+ * All events are fired on both the EventsService Evented bus and the universe
+ * service, so listeners can use either:
+ *
+ *   this.events.on('session.authenticated', handler)
+ *   this.universe.on('session.authenticated', handler)   ← recommended for engines
+ */
+export default class SessionService extends SimpleAuthSessionService {
+    @service router;
+    @service currentUser;
+    @service fetch;
+    @service notifications;
+    @service events;
+    @service universe;
+ 
+    /**
+     * Set where to transition to
+     *
+     * @var {String}
+     */
+    @tracked redirectTo = 'console';
+ 
+    /**
+     * If session is onboarding a user.
+     *
+     * @var {String}
+     */
+    @tracked _isOnboarding = false;
+ 
+    /**
+     * Timestamp (ms) when the session was authenticated.
+     * Used to calculate session duration on invalidation.
+     *
+     * @var {Number|null}
+     */
+    @tracked _sessionStartedAt = null;
+ 
+    /**
+     * Set this as onboarding.
+     *
+     * @return {SessionService}
+     */
+    isOnboarding() {
+        this._isOnboarding = true;
+ 
+        return this;
+    }
+ 
+    /**
+     * Manually authenticate user
+     */
+    manuallyAuthenticate(authToken) {
+        return this.session._setup('authenticator:fleetbase', { token: authToken }, true);
+    }
+ 
+    /**
+     * Overwrite the handle authentication method.
+     *
+     * Fires `session.authenticated` through the events service so that
+     * integrations (Intercom, PostHog, etc.) can react to a successful login.
+     *
+     * @void
+     */
+    async handleAuthentication() {
+        // Record session start time for duration tracking on logout
+        this._sessionStartedAt = Date.now();
+ 
+        // Fire session.authenticated event
+        this._fireSessionEvent('session.authenticated');
+ 
+        if (this._isOnboarding) {
+            return;
+        }
+ 
+        const loaderNode = this.showLoader('Starting session...');
+        const removeLoaderNode = () => {
+            later(
+                this,
+                () => {
+                    // remove node from body
+                    document.body.removeChild(loaderNode);
+                    this.isLoaderNodeOpen = false;
+                },
+                600 * 3
+            );
+        };
+        this.isLoaderNodeOpen = true;
+ 
+        try {
+            await this.router.transitionTo(this.redirectTo);
+        } catch (error) {
+            debug(`Session's handleAuthentication() failed to transition: ${error.message}`);
+        }
+ 
+        removeLoaderNode();
+    }
+ 
+    /**
+     * Extends the parent handleInvalidation method.
+     *
+     * IMPORTANT: super.handleInvalidation(routeAfterInvalidation) is called
+     * first to preserve the ember-simple-auth behaviour of redirecting the
+     * user to the login page (via handleSessionInvalidated). Our event
+     * firing happens after so it cannot interfere with the redirect.
+     *
+     * @param {String} routeAfterInvalidation - Passed through from ember-simple-auth
+     * @void
+     */
+    handleInvalidation(routeAfterInvalidation) {
+        // 1. Always call super first — this performs the actual post-logout
+        //    redirect/reload that ember-simple-auth is responsible for.
+        super.handleInvalidation(routeAfterInvalidation);
+ 
+        const durationSeconds = this._sessionStartedAt ? Math.round((Date.now() - this._sessionStartedAt) / 1000) : null;
+ 
+        // 2. Fire session lifecycle events for integrations (Intercom, PostHog, etc.)
+        if (this.events) {
+            this.events.trackSessionTerminated(durationSeconds);
+        }
+ 
+        // 3. Fire user.deauthenticated directly on universe for framework-level
+        //    uniformity — engines can listen without needing the events service.
+        this._fireSessionEvent('user.deauthenticated', { session_duration: durationSeconds });
+ 
+        // Reset session start time
+        this._sessionStartedAt = null;
+    }
+ 
+    /**
+     * Loads the current authenticated user
+     *
+     * @void
+     */
+    async loadCurrentUser() {
+        try {
+            const user = await this.currentUser.load();
+ 
+            if (!user) {
+                return this.invalidateWithLoader('Session authentication failed...');
+            }
+ 
+            return user;
+        } catch (error) {
+            await this.invalidateWithLoader(getWithDefault(error, 'message', 'Session authentication failed...'));
+        }
+    }
+ 
+    /**
+     * Loads the current authenticated user
+     *
+     * @param {Transition} transition
+     * @void
+     */
+    async promiseCurrentUser(transition = null) {
+        const invalidateWithLoader = this.invalidateWithLoader.bind(this);
+ 
+        // Abort and invalidate once, whichever way authentication failed.
+        const fail = async (error) => {
+            if (transition) {
+                transition.abort();
+            }
+ 
+            await invalidateWithLoader(error.message ?? 'Session authentication failed...');
+            throw error;
+        };
+ 
+        let user;
+        try {
+            user = await this.currentUser.promiseUser();
+        } catch (error) {
+            return fail(error);
+        }
+ 
+        if (!user) {
+            return fail(new Error('Session authentication failed...'));
+        }
+ 
+        return user;
+    }
+ 
+    /**
+     * Creates an HTML element node for a loading overlay with a message.
+     *
+     * @param {String} loadingMessage
+     * @return {HTMLElement} loader
+     */
+    showLoader(loadingMessage) {
+        const loader = document.createElement('div');
+        loader.classList.add('overloader');
+        loader.innerHTML = `<div class="flex items-center justify-center">
+            <div>
+                <svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" role="img" focusable="false" aria-hidden="true" data-icon="spinner-third" data-prefix="fad" id="ember240" class="svg-inline--fa fa-spinner-third fa-w-16 fa-spin ember-view text-sky-500 fa-spin-800ms mr-3"><g class="fa-group"><path class="fa-secondary" fill="currentColor" d="M478.71 364.58zm-22 6.11l-27.83-15.9a15.92 15.92 0 0 1-6.94-19.2A184 184 0 1 1 256 72c5.89 0 11.71.29 17.46.83-.74-.07-1.48-.15-2.23-.21-8.49-.69-15.23-7.31-15.23-15.83v-32a16 16 0 0 1 15.34-16C266.24 8.46 261.18 8 256 8 119 8 8 119 8 256s111 248 248 248c98 0 182.42-56.95 222.71-139.42-4.13 7.86-14.23 10.55-22 6.11z"></path><path class="fa-primary" fill="currentColor" d="M271.23 72.62c-8.49-.69-15.23-7.31-15.23-15.83V24.73c0-9.11 7.67-16.78 16.77-16.17C401.92 17.18 504 124.67 504 256a246 246 0 0 1-25 108.24c-4 8.17-14.37 11-22.26 6.45l-27.84-15.9c-7.41-4.23-9.83-13.35-6.2-21.07A182.53 182.53 0 0 0 440 256c0-96.49-74.27-175.63-168.77-183.38z"></path></g>
+                </svg>
+            </div>
+ 
+            <span class="font-semibold text-gray-700 dark:text-gray-100 test-sm">${loadingMessage}</span>
+        </div>`;
+        document.body.appendChild(loader);
+ 
+        return loader;
+    }
+ 
+    /**
+     * Invalidates the current session while displaying a loading message on the page.
+     *
+     * @param {String} loadingMessage
+     * @return {Promise}
+     */
+    invalidateWithLoader(loadingMessage = 'Ending session...') {
+        // if loader node is open already just invalidate
+        if (this.isLoaderNodeOpen === true) {
+            return this.session.invalidate();
+        }
+ 
+        const loaderNode = this.showLoader(loadingMessage);
+        this.isLoaderNodeOpen = true;
+ 
+        return this.session.invalidate().then(() => {
+            later(
+                this,
+                () => {
+                    document.body.removeChild(loaderNode);
+                    this.isLoaderNodeOpen = false;
+                },
+                600
+            );
+        });
+    }
+ 
+    /**
+     * Set the redirect route after authentication
+     *
+     * @void
+     */
+    setRedirect(whereTo = 'console') {
+        this.redirectTo = whereTo;
+    }
+ 
+    /**
+     * Get session time expiry date in moment
+     *
+     * @return {Date}
+     */
+    getExpiresAtDate() {
+        return new Date(this.data.authenticated.expires_at);
+    }
+ 
+    /**
+     * Get session time expiry in seconds
+     *
+     * @return {Integer}
+     */
+    getSessionSecondsRemaining() {
+        const date = this.getExpiresAtDate();
+        const now = new Date();
+ 
+        return Math.round((date - now) / 1000);
+    }
+ 
+    /**
+     * Checks for the presence of two-factor authentication for a given user identity.
+     *
+     * @param {String} identity
+     * @return {Promise}
+     * @throws {Error}
+     */
+    checkForTwoFactor(identity) {
+        return this.fetch.get('two-fa/check', { identity }).catch((error) => {
+            throw new Error(error.message);
+        });
+    }
+ 
+    // =========================================================================
+    // Private helpers
+    // =========================================================================
+ 
+    /**
+     * Fires a named session lifecycle event on both the events service and
+     * directly on the universe service.
+     *
+     * Firing on universe directly (in addition to via events service) ensures
+     * that all engines and extensions receive the event on the shared framework-
+     * level bus regardless of whether the events service has fully initialised.
+     *
+     * Listeners can subscribe via:
+     *   this.universe.on('session.authenticated', handler)
+     *   this.universe.on('user.deauthenticated', handler)
+     *   this.events.on('session.authenticated', handler)
+     *
+     * @private
+     * @param {String} eventName
+     * @param {Object} [extraProps={}]
+     */
+    _fireSessionEvent(eventName, extraProps = {}) {
+        // 1. Fire through the events service (dual-broadcasts on events + universe bus)
+        if (this.events) {
+            this.events.trackEvent(eventName, extraProps);
+        }
+ 
+        // 2. Also trigger directly on universe for framework-level uniformity —
+        //    ensures the event reaches all engines even if events service is
+        //    not yet available or not injected in a given engine context.
+        if (this.universe) {
+            this.universe.trigger(eventName, extraProps);
+        }
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe.js.html new file mode 100644 index 0000000..40cf883 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe.js.html @@ -0,0 +1,2848 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services universe.js

+
+ +
+ 0% + Statements + 0/139 +
+ + +
+ 0% + Branches + 0/112 +
+ + +
+ 0% + Functions + 0/63 +
+ + +
+ 0% + Lines + 0/130 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service from '@ember/service';
+import Evented from '@ember/object/evented';
+import { inject as service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { action } from '@ember/object';
+import { getOwner } from '@ember/application';
+import { A } from '@ember/array';
+import MenuItem from '../contracts/menu-item';
+ 
+/**
+ * UniverseService (Refactored)
+ *
+ * This is the new UniverseService that acts as a facade to the specialized sub-services.
+ * It maintains backward compatibility with the old API while delegating to the new architecture.
+ *
+ * The service decomposition provides:
+ * - ExtensionManager: Handles lazy loading of engines
+ * - RegistryService: Manages all registries using Ember's container
+ * - MenuService: Manages menu items and panels
+ * - WidgetService: Manages dashboard widgets
+ * - HookService: Manages application hooks
+ *
+ * @class UniverseService
+ * @extends Service
+ */
+export default class UniverseService extends Service.extend(Evented) {
+    // Inject specialized services
+    @service('universe/extension-manager') extensionManager;
+    @service('universe/registry-service') registryService;
+    @service('universe/menu-service') menuService;
+    @service('universe/widget-service') widgetService;
+    @service('universe/hook-service') hookService;
+    @service('universe/resource-view-service') resourceViewService;
+    @service router;
+    @service intl;
+    @service urlSearchParams;
+    @tracked applicationInstance;
+    @tracked initialLocation = { ...window.location };
+    @tracked bootCallbacks = A([]);
+ 
+    /**
+     * Set the application instance on this service and cascade to RegistryService
+     * Called by the instance initializer to ensure both services have access
+     * to the root application container
+     *
+     * @method setApplicationInstance
+     * @param {Application} application The root application instance
+     */
+    setApplicationInstance(application) {
+        this.applicationInstance = application;
+ 
+        // Cascade to all child services
+        if (this.registryService) {
+            this.registryService.setApplicationInstance(application);
+        }
+        if (this.extensionManager) {
+            this.extensionManager.setApplicationInstance(application);
+        }
+        if (this.menuService) {
+            this.menuService.setApplicationInstance(application);
+        }
+        if (this.widgetService) {
+            this.widgetService.setApplicationInstance(application);
+        }
+        if (this.hookService) {
+            this.hookService.setApplicationInstance(application);
+        }
+    }
+ 
+    /**
+     * Get a service by name
+     * Convenience method for extensions to access specialized services
+     *
+     * Supports multiple naming patterns:
+     * - "universe/menu-service" -> universe/menu-service
+     * - "menu-service" -> universe/menu-service
+     * - "menuService" -> universe/menu-service
+     * - "menu" -> universe/menu-service
+     * - "hooks" or "hook" -> universe/hook-service
+     * - "widgets" or "widget" -> universe/widget-service
+     * - "registry" -> universe/registry-service
+     * - "resource-view" or "resourceView" -> universe/resource-view-service
+     *
+     * @method getService
+     * @param {String} serviceName Service name in various formats
+     * @returns {Service} The service instance
+     */
+    getService(serviceName) {
+        const owner = getOwner(this);
+        let resolvedName = serviceName;
+ 
+        // Normalize the service name
+        if (!/\//.test(serviceName)) {
+            // No slash, might be camelCase or short name
+            const kebabCase = serviceName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
+ 
+            // Map short names and variations to full service names
+            const nameMapping = {
+                hook: 'hook-service',
+                hooks: 'hook-service',
+                'hook-service': 'hook-service',
+                menu: 'menu-service',
+                'menu-service': 'menu-service',
+                widget: 'widget-service',
+                widgets: 'widget-service',
+                'widget-service': 'widget-service',
+                registry: 'registry-service',
+                'registry-service': 'registry-service',
+                'resource-view': 'resource-view-service',
+                'resource-views': 'resource-view-service',
+                'resource-view-service': 'resource-view-service',
+            };
+ 
+            const mappedName = nameMapping[kebabCase] || kebabCase;
+            resolvedName = `universe/${mappedName}`;
+        } else if (serviceName.startsWith('universe/')) {
+            // Already has universe/ prefix, ensure it's using -service naming
+            resolvedName = serviceName;
+        }
+ 
+        return owner.lookup(`service:${resolvedName}`);
+    }
+ 
+    // ============================================================================
+    // Extension Management (delegates to ExtensionManager)
+    // ============================================================================
+ 
+    /**
+     * Ensure an engine is loaded
+     *
+     * @method ensureEngineLoaded
+     * @param {String} engineName Engine name
+     * @returns {Promise<EngineInstance>} Engine instance
+     */
+    async ensureEngineLoaded(engineName) {
+        return this.extensionManager.ensureEngineLoaded(engineName);
+    }
+ 
+    /**
+     * Get an engine instance
+     *
+     * @method getEngineInstance
+     * @param {String} engineName Engine name
+     * @returns {EngineInstance|null} Engine instance or null
+     */
+    getEngineInstance(engineName) {
+        return this.extensionManager.getEngineInstance(engineName);
+    }
+ 
+    /**
+     * Register an extension
+     *
+     * @method registerExtension
+     * @param {String} name Extension name
+     * @param {Object} metadata Extension metadata
+     */
+    registerExtension(name, metadata = {}) {
+        this.extensionManager.registerExtension(name, metadata);
+    }
+ 
+    /**
+     * Listen for a specific engine to be loaded
+     * Note: This uses event listeners and will NOT run if the engine is already loaded.
+     * Use whenEngineLoaded() if you want to handle both cases.
+     *
+     * @method onEngineLoaded
+     * @param {String} engineName The engine name to listen for
+     * @param {Function} callback Function to call when the engine loads, receives engineInstance as parameter
+     * @example
+     * universe.onEngineLoaded('@fleetbase/fleetops-engine', (engineInstance) => {
+     *     console.log('FleetOps engine loaded!', engineInstance);
+     * });
+     */
+    onEngineLoaded(engineName, callback) {
+        this.extensionManager.on('engine.loaded', (name, instance) => {
+            if (name === engineName) {
+                callback(instance);
+            }
+        });
+    }
+ 
+    /**
+     * Execute a callback when an engine is loaded
+     * If the engine is already loaded, the callback runs immediately
+     * Otherwise, it's stored and runs when the engine loads
+     *
+     * This is the recommended way to handle engine-dependent setup.
+     *
+     * @method whenEngineLoaded
+     * @param {String} engineName The engine name
+     * @param {Function} callback Function to call, receives (engineInstance, universe, appInstance)
+     * @example
+     * // Replaces this pattern:
+     * if (universe.extensionManager.isEngineLoaded('@fleetbase/fleetops-engine')) {
+     *     const engine = universe.extensionManager.getEngineInstance('@fleetbase/fleetops-engine');
+     *     doSomething(engine);
+     * } else {
+     *     universe.onEngineLoaded('@fleetbase/fleetops-engine', (engine) => {
+     *         doSomething(engine);
+     *     });
+     * }
+     *
+     * // With this simpler pattern:
+     * universe.whenEngineLoaded('@fleetbase/fleetops-engine', (engine) => {
+     *     doSomething(engine);
+     * });
+     */
+    whenEngineLoaded(engineName, callback) {
+        return this.extensionManager.whenEngineLoaded(engineName, callback);
+    }
+ 
+    /**
+     * Get the application instance
+     *
+     * @method getApplicationInstance
+     * @returns {ApplicationInstance} The application instance
+     */
+    getApplicationInstance() {
+        return this.applicationInstance;
+    }
+ 
+    /**
+     * Get a service from a specific engine
+     *
+     * @method getServiceFromEngine
+     * @param {String} engineName The engine name
+     * @param {String} serviceName The service name
+     * @param {Object} options Optional options
+     * @param {Object} options.inject Properties to inject into the service
+     * @returns {Service|null} The service instance or null
+     * @example
+     * const userService = universe.getServiceFromEngine('user-engine', 'user');
+     * if (userService) {
+     *     userService.doSomething();
+     * }
+     */
+    getServiceFromEngine(engineName, serviceName, options = {}) {
+        const engineInstance = this.getEngineInstance(engineName);
+ 
+        if (engineInstance && typeof serviceName === 'string') {
+            const serviceInstance = engineInstance.lookup(`service:${serviceName}`);
+            if (options && options.inject) {
+                for (let injectionName in options.inject) {
+                    serviceInstance[injectionName] = options.inject[injectionName];
+                }
+            }
+            return serviceInstance;
+        }
+ 
+        return null;
+    }
+ 
+    // ============================================================================
+    // Registry Management (delegates to RegistryService)
+    // ============================================================================
+ 
+    /**
+     * Create a new registry
+     *
+     * @method createRegistry
+     * @param {String} name Registry name
+     * @returns {Array} The created registry
+     */
+    createRegistry(name) {
+        return this.registryService.createRegistry(name);
+    }
+ 
+    /**
+     * Create multiple registries
+     *
+     * @method createRegistries
+     * @param {Array} names Array of registry names
+     */
+    createRegistries(names) {
+        this.registryService.createRegistries(names);
+    }
+ 
+    /**
+     * Get a registry
+     *
+     * @method getRegistry
+     * @param {String} name Registry name
+     * @param {String} listName The list within the registry (default 'menu-item', as createRegistry)
+     * @returns {Array} Registry items
+     */
+    getRegistry(name, listName = 'menu-item') {
+        return this.registryService.getRegistry(name, listName);
+    }
+ 
+    /**
+     * Register an item to a registry
+     *
+     * @method registerInRegistry
+     * @param {String} registryName Registry name
+     * @param {String} key Item key
+     * @param {*} value Item value
+     * @param {String} listName The list within the registry (default 'menu-item', as createRegistry)
+     */
+    registerInRegistry(registryName, key, value, listName = 'menu-item') {
+        this.registryService.register(registryName, listName, key, value);
+    }
+ 
+    /**
+     * Lookup an item from a registry
+     *
+     * @method lookupFromRegistry
+     * @param {String} registryName Registry name
+     * @param {String} key Item key
+     * @param {String} listName The list within the registry (default 'menu-item', as createRegistry)
+     * @returns {*} The registered item
+     */
+    lookupFromRegistry(registryName, key, listName = 'menu-item') {
+        return this.registryService.lookup(registryName, listName, key);
+    }
+ 
+    // ============================================================================
+    // Resource View Registries (delegates to ResourceViewService)
+    // ============================================================================
+ 
+    /**
+     * Register columns, actions or buttons into a table or details view slot.
+     *
+     * @method registerInResourceView
+     * @param {String} registryName e.g. 'fleet-ops:driver:table:columns'
+     * @param {Object|Array} items TableColumn, ResourceAction or ActionButton contracts
+     * @returns {Boolean}
+     *
+     * @example
+     * universe.registerInResourceView('ledger:invoice:details:menu', new ResourceAction({ id: 'acme-print', label: 'Print', fn: (invoice) => … }));
+     */
+    registerInResourceView(registryName, items) {
+        return this.resourceViewService.register(registryName, items);
+    }
+ 
+    registerTableColumn(extension, resource, column) {
+        return this.resourceViewService.registerTableColumn(extension, resource, column);
+    }
+ 
+    registerRowAction(extension, resource, action) {
+        return this.resourceViewService.registerRowAction(extension, resource, action);
+    }
+ 
+    registerBulkAction(extension, resource, action) {
+        return this.resourceViewService.registerBulkAction(extension, resource, action);
+    }
+ 
+    registerTableAction(extension, resource, button) {
+        return this.resourceViewService.registerTableAction(extension, resource, button);
+    }
+ 
+    registerDetailsAction(extension, resource, button) {
+        return this.resourceViewService.registerDetailsAction(extension, resource, button);
+    }
+ 
+    registerDetailsMenuItem(extension, resource, action) {
+        return this.resourceViewService.registerDetailsMenuItem(extension, resource, action);
+    }
+ 
+    // ============================================================================
+    // Application Container Registration (delegates to RegistryService)
+    // ============================================================================
+ 
+    /**
+     * Registers a component to the root application container.
+     * This ensures the component is available to all engines and the host app.
+     * @method registerComponent
+     * @param {String} name The component name (e.g., 'my-component')
+     * @param {Class} componentClass The component class
+     * @param {Object} options Registration options
+     */
+    registerComponent(name, componentClass, options = {}) {
+        this.registryService.registerComponent(name, componentClass, options);
+    }
+ 
+    /**
+     * Registers a service to the root application container.
+     * This ensures the service is available to all engines and the host app.
+     * @method registerService
+     * @param {String} name The service name (e.g., 'my-service')
+     * @param {Class} serviceClass The service class
+     * @param {Object} options Registration options
+     */
+    registerService(name, serviceClass, options = {}) {
+        this.registryService.registerService(name, serviceClass, options);
+    }
+ 
+    // ============================================================================
+    // Menu Management (delegates to MenuService)
+    // ============================================================================
+ 
+    /**
+     * Register a header menu item
+     *
+     * @method registerHeaderMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {String} route Optional route
+     * @param {Object} options Optional options
+     */
+    registerHeaderMenuItem(menuItemOrTitle, route = null, options = {}) {
+        this.menuService.registerHeaderMenuItem(menuItemOrTitle, route, options);
+    }
+ 
+    /**
+     * Register an organization menu item
+     *
+     * @method registerOrganizationMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {Object} options Optional options
+     */
+    registerOrganizationMenuItem(menuItemOrTitle, options = {}) {
+        this.menuService.registerOrganizationMenuItem(menuItemOrTitle, options);
+    }
+ 
+    /**
+     * Register a user menu item
+     *
+     * @method registerUserMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {Object} options Optional options
+     */
+    registerUserMenuItem(menuItemOrTitle, options = {}) {
+        this.menuService.registerUserMenuItem(menuItemOrTitle, options);
+    }
+ 
+    /**
+     * Register an admin menu panel
+     *
+     * @method registerAdminMenuPanel
+     * @param {MenuPanel|String} panelOrTitle MenuPanel instance or title
+     * @param {Array} items Optional items
+     * @param {Object} options Optional options
+     */
+    registerAdminMenuPanel(panelOrTitle, items = [], options = {}) {
+        this.menuService.registerAdminMenuPanel(panelOrTitle, items, options);
+    }
+ 
+    /**
+     * Register a settings menu item
+     *
+     * @method registerSettingsMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {Object} options Optional options
+     */
+    registerSettingsMenuItem(menuItemOrTitle, options = {}) {
+        this.menuService.registerSettingsMenuItem(menuItemOrTitle, options);
+    }
+ 
+    /**
+     * Register a menu item to a custom registry
+     *
+     * @method registerMenuItem
+     * @param {String} registryName Registry name
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {String|Object} routeOrOptions Route or options
+     * @param {Object} options Optional options
+     */
+    registerMenuItem(registryName, menuItemOrTitle, routeOrOptions = {}, options = {}) {
+        this.menuService.registerMenuItem(registryName, menuItemOrTitle, routeOrOptions, options);
+    }
+ 
+    /**
+     * Get header menu items
+     *
+     * @computed headerMenuItems
+     * @returns {Array} Header menu items
+     */
+    get headerMenuItems() {
+        return this.menuService.getHeaderMenuItems();
+    }
+ 
+    /**
+     * Get organization menu items
+     *
+     * @computed organizationMenuItems
+     * @returns {Array} Organization menu items
+     */
+    get organizationMenuItems() {
+        return this.menuService.getOrganizationMenuItems();
+    }
+ 
+    /**
+     * Get user menu items
+     *
+     * @computed userMenuItems
+     * @returns {Array} User menu items
+     */
+    get userMenuItems() {
+        return this.menuService.getUserMenuItems();
+    }
+ 
+    /**
+     * Get admin menu items
+     *
+     * @computed adminMenuItems
+     * @returns {Array} Admin menu items
+     */
+    get adminMenuItems() {
+        return this.menuService.getAdminMenuItems();
+    }
+ 
+    /**
+     * Get admin menu panels
+     *
+     * @computed adminMenuPanels
+     * @returns {Array} Admin menu panels
+     */
+    get adminMenuPanels() {
+        return this.menuService.getAdminMenuPanels();
+    }
+ 
+    // ============================================================================
+    // Widget Management (delegates to WidgetService)
+    // ============================================================================
+ 
+    /**
+     * Register default dashboard widgets
+     *
+     * @method registerDefaultDashboardWidgets
+     * @param {Array<Widget>} widgets Array of widgets
+     */
+    registerDefaultDashboardWidgets(widgets) {
+        this.widgetService.registerDefaultDashboardWidgets(widgets);
+    }
+ 
+    /**
+     * Register dashboard widgets
+     *
+     * @method registerDashboardWidgets
+     * @param {Array<Widget>} widgets Array of widgets
+     */
+    registerDashboardWidgets(widgets) {
+        this.widgetService.registerDashboardWidgets(widgets);
+    }
+ 
+    /**
+     * Register a dashboard
+     *
+     * @method registerDashboard
+     * @param {String} name Dashboard name
+     * @param {Object} options Dashboard options
+     */
+    registerDashboard(name, options = {}) {
+        this.widgetService.registerDashboard(name, options);
+    }
+ 
+    /**
+     * Register a dashboard render slot.
+     *
+     * @method registerDashboardSlot
+     * @param {String} slotId Dashboard slot identifier
+     * @param {Object} options Slot options
+     */
+    registerDashboardSlot(slotId, options = {}) {
+        this.widgetService.registerDashboardSlot(slotId, options);
+    }
+ 
+    /**
+     * Register a system dashboard for a render slot.
+     *
+     * @method registerDashboardForSlot
+     * @param {String} slotId Dashboard slot identifier
+     * @param {String} dashboardName Dashboard widget namespace
+     * @param {Object} options Dashboard slot options
+     */
+    registerDashboardForSlot(slotId, dashboardName, options = {}) {
+        this.widgetService.registerDashboardForSlot(slotId, dashboardName, options);
+    }
+ 
+    /**
+     * Set the first system dashboard for a render slot.
+     *
+     * @method setDefaultDashboardForSlot
+     * @param {String} slotId Dashboard slot identifier
+     * @param {String} dashboardName Dashboard widget namespace
+     */
+    setDefaultDashboardForSlot(slotId, dashboardName) {
+        this.widgetService.setDefaultDashboardForSlot(slotId, dashboardName);
+    }
+ 
+    /**
+     * Set the dashboard that should load first on the console home.
+     *
+     * @method setConsoleDashboard
+     * @param {String} dashboardName Dashboard widget namespace
+     */
+    setConsoleDashboard(dashboardName) {
+        this.widgetService.setConsoleDashboard(dashboardName);
+    }
+ 
+    /**
+     * Get dashboard widgets
+     *
+     * @computed dashboardWidgets
+     * @returns {Object} Dashboard widgets object
+     */
+    get dashboardWidgets() {
+        return {
+            // The dashboard the deprecated registerDashboardWidgets() and
+            // registerDefaultDashboardWidgets() register into.
+            defaultWidgets: this.widgetService.getDefaultWidgets('dashboard'),
+            widgets: this.widgetService.getWidgets('dashboard'),
+        };
+    }
+ 
+    // ============================================================================
+    // Hook Management (delegates to HookService)
+    // ============================================================================
+ 
+    /**
+     * Register a hook
+     *
+     * @method registerHook
+     * @param {Hook|String} hookOrName Hook instance or name
+     * @param {Function} handler Optional handler
+     * @param {Object} options Optional options
+     */
+    registerHook(hookOrName, handler = null, options = {}) {
+        this.hookService.registerHook(hookOrName, handler, options);
+    }
+ 
+    /**
+     * Execute hooks
+     *
+     * @method executeHook
+     * @param {String} hookName Hook name
+     * @param {...*} args Arguments to pass to hooks
+     * @returns {Promise<Array>} Array of hook results
+     */
+    async executeHook(hookName, ...args) {
+        return this.hookService.execute(hookName, ...args);
+    }
+ 
+    /**
+     * Get hooks
+     *
+     * @computed hooks
+     * @returns {Object} Hooks object
+     */
+    get hooks() {
+        return this.hookService.hooks;
+    }
+ 
+    // ============================================================================
+    // Utility Methods
+    // ============================================================================
+ 
+    /**
+     * Get view from transition
+     *
+     * @method getViewFromTransition
+     * @param {Object} transition Transition object
+     * @returns {String|null} View parameter
+     */
+    getViewFromTransition(transition) {
+        const queryParams = transition.to?.queryParams ?? { view: null };
+        return queryParams.view;
+    }
+ 
+    /**
+     * Virtual route redirect
+     * Handles redirecting to menu items based on URL slug
+     *
+     * @method virtualRouteRedirect
+     * @param {Object} transition Transition object
+     * @param {String} registryName Registry name
+     * @param {String} route Route name
+     * @param {Object} options Options
+     * @returns {Promise} Transition promise
+     */
+    async virtualRouteRedirect(transition, registryName, route, options = {}) {
+        const view = this.getViewFromTransition(transition);
+        const slug = window.location.pathname.replace('/', '');
+        const queryParams = this.urlSearchParams.all();
+        const menuItem = this.lookupMenuItemFromRegistry(registryName, slug, view);
+ 
+        if (menuItem && transition.from === null) {
+            return this.transitionMenuItem(route, menuItem, { queryParams }).then((transition) => {
+                if (options && options.restoreQueryParams === true) {
+                    this.urlSearchParams.setParamsToCurrentUrl(queryParams);
+                }
+                return transition;
+            });
+        }
+    }
+ 
+    /**
+     * Transition to a menu item
+     * Handles section, slug, and view parameters for virtual routes
+     *
+     * @method transitionMenuItem
+     * @param {String} route Route name
+     * @param {Object} menuItem Menu item object with slug, view, and optional section
+     * @param {Object} options
+     * @param {Object} options.queryParams Query params to carry into the transition
+     * @returns {Transition} The router transition
+     */
+    @action
+    transitionMenuItem(route, menuItem, options = {}) {
+        const { slug, view, section } = menuItem;
+        const params = section && slug ? [section, slug] : [slug];
+        // The menu item's own view wins over a `view` among the carried query params.
+        const queryParams = { ...options.queryParams, ...(view ? { view } : {}) };
+ 
+        if (Object.keys(queryParams).length) {
+            return this.router.transitionTo(route, ...params, { queryParams });
+        }
+ 
+        return this.router.transitionTo(route, ...params);
+    }
+ 
+    /**
+     * Register a boot callback
+     *
+     * @method onBoot
+     * @param {Function} callback Callback function
+     */
+    onBoot(callback) {
+        if (typeof callback === 'function') {
+            this.bootCallbacks.pushObject(callback);
+        }
+    }
+ 
+    /**
+     * Execute boot callbacks
+     *
+     * @method executeBootCallbacks
+     */
+    async executeBootCallbacks() {
+        for (const callback of this.bootCallbacks) {
+            try {
+                await callback(this);
+            } catch (error) {
+                console.error('Error executing boot callback:', error);
+            }
+        }
+ 
+        // Mark boot as complete
+        this.extensionManager.finishBoot();
+    }
+ 
+    // ============================================================================
+    // Backward Compatibility Methods
+    // ============================================================================
+ 
+    /**
+     * Get menu items from a registry
+     * Backward compatibility facade
+     *
+     * @method getMenuItemsFromRegistry
+     * @param {String} registryName Registry name
+     * @returns {Array} Menu items
+     */
+    getMenuItemsFromRegistry(registryName) {
+        return this.menuService.getMenuItems(registryName) || A([]);
+    }
+ 
+    /**
+     * Get menu panels from a registry
+     * Backward compatibility facade
+     *
+     * @method getMenuPanelsFromRegistry
+     * @param {String} registryName Registry name
+     * @returns {Array} Menu panels
+     */
+    getMenuPanelsFromRegistry(registryName) {
+        return this.menuService.getMenuPanels(registryName) || A([]);
+    }
+ 
+    /**
+     * Lookup a menu item from a registry
+     * Backward compatibility facade
+     *
+     * @method lookupMenuItemFromRegistry
+     * @param {String} registryName Registry name
+     * @param {String} slug Menu item slug
+     * @param {String} view Optional view
+     * @param {String} section Optional section
+     * @returns {Object|null} Menu item or null
+     */
+    lookupMenuItemFromRegistry(registryName, slug, view = null, section = null) {
+        const items = this.getMenuItemsFromRegistry(registryName);
+        return items.find((item) => {
+            const slugMatch = item.slug === slug;
+            const viewMatch = !view || item.view === view;
+            const sectionMatch = !section || item.section === section;
+            return slugMatch && viewMatch && sectionMatch;
+        });
+    }
+ 
+    /**
+     * Create a registry event
+     * Backward compatibility facade
+     *
+     * @method createRegistryEvent
+     * @param {String} registryName Registry name
+     * @param {String} eventName Event name
+     * @param {...*} args Event arguments
+     */
+    createRegistryEvent(registryName, eventName, ...args) {
+        this.trigger(`${registryName}:${eventName}`, ...args);
+    }
+ 
+    /**
+     * Register after boot callback
+     * Backward compatibility facade
+     *
+     * @method afterBoot
+     * @param {Function} callback Callback function
+     */
+    afterBoot(callback) {
+        this.extensionManager.afterBoot(callback);
+    }
+ 
+    /**
+     * Create a menu item (internal helper)
+     * Backward compatibility helper
+     *
+     * @method _createMenuItem
+     * @param {String} title Menu item title
+     * @param {String} route Menu item route
+     * @param {Object} options Menu item options
+     * @returns {Object} Menu item object
+     */
+    _createMenuItem(title, route = null, options = {}) {
+        const menuItem = new MenuItem(title, route);
+ 
+        if (options.icon) menuItem.withIcon(options.icon);
+        if (options.component) menuItem.withComponent(options.component);
+        if (options.slug) menuItem.withSlug(options.slug);
+        if (options.section) menuItem.inSection(options.section);
+        if (options.priority) menuItem.withPriority(options.priority);
+        if (options.type) menuItem.withType(options.type);
+        if (options.wrapperClass) menuItem.withWrapperClass(options.wrapperClass);
+        if (options.queryParams) menuItem.withQueryParams(options.queryParams);
+        if (options.onClick) menuItem.withOnClick(options.onClick);
+ 
+        return menuItem.toObject();
+    }
+ 
+    /**
+     * Register a renderable component for cross-engine rendering
+     * Facade method - delegates to RegistryService
+     *
+     * @method registerRenderableComponent
+     * @param {String} registryName Registry name (slot identifier)
+     * @param {Object|Class|Array} component ExtensionComponent definition, component class, or array of either
+     * @param {Object} options Optional configuration
+     *
+     * @example
+     * // ExtensionComponent definition with path (lazy loading)
+     * universe.registerRenderableComponent(
+     *     'fleet-ops:component:order:details',
+     *     new ExtensionComponent('@fleetbase/storefront-engine', 'storefront-order-summary')
+     * );
+     */
+    registerRenderableComponent(registryName, component, options = {}) {
+        return this.registryService.registerRenderableComponent(registryName, component, options);
+    }
+ 
+    /**
+     * Get renderable components from a registry
+     * Backward compatibility method - delegates to RegistryService
+     *
+     * @method getRenderableComponentsFromRegistry
+     * @param {String} registryName Registry name
+     * @returns {Array} Array of component definitions/classes
+     */
+    getRenderableComponentsFromRegistry(registryName) {
+        return this.registryService.getRenderableComponents(registryName);
+    }
+ 
+    /**
+     * Register a helper to the application container
+     * Makes the helper available globally to all engines and the host app
+     * Facade method - delegates to RegistryService
+     *
+     * @method registerHelper
+     * @param {String} helperName The helper name (e.g., 'calculate-delivery-fee')
+     * @param {Function|Class|TemplateHelper} helperClassOrTemplateHelper Helper function, class, or TemplateHelper instance
+     * @param {Object} options Registration options
+     * @returns {Promise<void>}
+     *
+     * @example
+     * // Direct function registration
+     * await universe.registerHelper('calculate-delivery-fee', calculateDeliveryFeeHelper);
+     *
+     * @example
+     * // Lazy loading from engine (ensures engine is loaded first)
+     * import TemplateHelper from '@fleetbase/ember-core/contracts/template-helper';
+     * await universe.registerHelper(
+     *     'calculate-delivery-fee',
+     *     new TemplateHelper('@fleetbase/storefront-engine', 'helpers/calculate-delivery-fee')
+     * );
+     */
+    async registerHelper(helperName, helperClassOrTemplateHelper, options = {}) {
+        return await this.registryService.registerHelper(helperName, helperClassOrTemplateHelper, options);
+    }
+ 
+    /**
+     * Legacy method for registering components in engines
+     * Maintained for backward compatibility
+     *
+     * @method registerComponentInEngine
+     * @param {String} engineName Engine name
+     * @param {*} componentClass Component class
+     * @param {Object} options Options
+     */
+    async registerComponentInEngine(engineName, componentClass, options = {}) {
+        const engineInstance = await this.ensureEngineLoaded(engineName);
+ 
+        if (engineInstance && componentClass && typeof componentClass.name === 'string') {
+            const dasherized = componentClass.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
+            engineInstance.register(`component:${componentClass.name}`, componentClass);
+            engineInstance.register(`component:${dasherized}`, componentClass);
+ 
+            if (options.registerAs) {
+                engineInstance.register(`component:${options.registerAs}`, componentClass);
+            }
+        }
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/hook-service.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/hook-service.js.html new file mode 100644 index 0000000..1b38f0f --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/hook-service.js.html @@ -0,0 +1,1099 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/hook-service.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe hook-service.js

+
+ +
+ 0% + Statements + 0/84 +
+ + +
+ 0% + Branches + 0/60 +
+ + +
+ 0% + Functions + 0/16 +
+ + +
+ 0% + Lines + 0/77 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service, { inject as service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { getOwner } from '@ember/application';
+import Hook from '../../contracts/hook';
+import HookRegistry from '../../contracts/hook-registry';
+ 
+/**
+ * HookService
+ *
+ * Manages application lifecycle hooks and custom event hooks.
+ * Allows extensions to inject logic at specific points in the application.
+ *
+ * @class HookService
+ * @extends Service
+ */
+export default class HookService extends Service {
+    @service universe;
+ 
+    /**
+     * Reference to the root Ember Application Instance.
+     * Used for registering components/services to the application container
+     * for cross-engine sharing.
+     */
+    @tracked applicationInstance = null;
+ 
+    #hookRegistry = null;
+ 
+    /**
+     * The shared hook registry, resolved on first use.
+     *
+     * This is deliberately lazy rather than resolved in the constructor:
+     * #getApplication prefers an explicitly set applicationInstance over the
+     * owner, and setApplicationInstance can only run after construction. Doing
+     * the lookup here means that preference is actually honoured.
+     *
+     * Read-only. It was a writable field before, but nothing in the addon, its
+     * tests or the console app ever assigned it, and replacing a memoized
+     * container lookup from outside is not a meaningful operation.
+     *
+     * @type {HookRegistry}
+     */
+    get hookRegistry() {
+        return this.#resolveHookRegistry();
+    }
+ 
+    /**
+     * Memoized resolution, kept out of the getter body so a property read is not
+     * itself an assignment.
+     *
+     * @private
+     * @returns {HookRegistry}
+     */
+    #resolveHookRegistry() {
+        if (!this.#hookRegistry) {
+            this.#hookRegistry = this.#initializeHookRegistry();
+        }
+ 
+        return this.#hookRegistry;
+    }
+ 
+    /**
+     * Set the application instance
+     *
+     * @method setApplicationInstance
+     * @param {Application} application The root application instance
+     */
+    setApplicationInstance(application) {
+        this.applicationInstance = application;
+    }
+ 
+    /**
+     * Initialize shared hook registry singleton
+     * Ensures all HookService instances share the same hooks
+     *
+     * @private
+     * @returns {HookRegistry}
+     */
+    #initializeHookRegistry() {
+        const registryKey = 'registry:hooks';
+        const application = this.#getApplication();
+ 
+        if (!application.hasRegistration(registryKey)) {
+            application.register(registryKey, new HookRegistry(), {
+                instantiate: false,
+            });
+        }
+ 
+        return application.resolveRegistration(registryKey);
+    }
+ 
+    /**
+     * Get the application instance
+     * Tries multiple fallback methods to find the root application
+     *
+     * @private
+     * @returns {Application}
+     */
+    #getApplication() {
+        // First priority use the universe application instance
+        if (this.universe.applicationInstance) {
+            return this.universe.applicationInstance;
+        }
+ 
+        // Second priority: use applicationInstance if set
+        if (this.applicationInstance) {
+            return this.applicationInstance;
+        }
+ 
+        // Third priority: try to get application from owner
+        const owner = getOwner(this);
+        if (owner && owner.application) {
+            return owner.application;
+        }
+ 
+        // Last resort: return owner itself (might be EngineInstance)
+        return owner;
+    }
+ 
+    /**
+     * Getter and setter for hooks property
+     * Delegates to the shared hookRegistry object
+     */
+    get hooks() {
+        return this.hookRegistry.hooks;
+    }
+ 
+    set hooks(value) {
+        this.hookRegistry.hooks = value;
+    }
+ 
+    /**
+     * Find a specific hook
+     *
+     * @private
+     * @method #findHook
+     * @param {String} hookName Hook name
+     * @param {String} hookId Hook ID
+     * @returns {Object|null} Hook or null
+     */
+    #findHook(hookName, hookId) {
+        const hookList = this.hooks[hookName] || [];
+        return hookList.find((h) => h.id === hookId) || null;
+    }
+ 
+    /**
+     * Normalize a hook input to a plain object
+     *
+     * @private
+     * @method #normalizeHook
+     * @param {Hook|String} input Hook instance or hook name
+     * @param {Function} handler Optional handler
+     * @param {Object} options Optional options
+     * @returns {Object} Normalized hook object
+     */
+    #normalizeHook(input, handler, options) {
+        if (input instanceof Hook) {
+            return input.toObject();
+        }
+ 
+        if (typeof input === 'string') {
+            const hook = new Hook(input, handler);
+ 
+            if (options.priority !== undefined) hook.withPriority(options.priority);
+            if (options.once) hook.once();
+            if (options.id) hook.withId(options.id);
+            if (options.enabled !== undefined) hook.setEnabled(options.enabled);
+ 
+            return hook.toObject();
+        }
+ 
+        return input;
+    }
+ 
+    /**
+     * Register a hook
+     *
+     * @method registerHook
+     * @param {Hook|String} hookOrName Hook instance or hook name
+     * @param {Function} handler Optional handler (if first param is string)
+     * @param {Object} options Optional options
+     */
+    registerHook(hookOrName, handler = null, options = {}) {
+        const hook = this.#normalizeHook(hookOrName, handler, options);
+ 
+        if (!this.hooks[hook.name]) {
+            this.hooks[hook.name] = [];
+        }
+ 
+        this.hooks[hook.name].push(hook);
+ 
+        // Sort by priority (lower numbers first)
+        this.hooks[hook.name].sort((a, b) => a.priority - b.priority);
+    }
+ 
+    /**
+     * Execute all hooks for a given name
+     *
+     * @method execute
+     * @param {String} hookName Hook name
+     * @param {...*} args Arguments to pass to hook handlers
+     * @returns {Promise<Array>} Array of hook results
+     */
+    async execute(hookName, ...args) {
+        const hookList = this.hooks[hookName] || [];
+        const results = [];
+ 
+        for (const hook of hookList) {
+            if (!hook.enabled) {
+                continue;
+            }
+ 
+            if (typeof hook.handler === 'function') {
+                try {
+                    const result = await hook.handler(...args);
+                    results.push(result);
+ 
+                    // Remove hook if it should only run once
+                    if (hook.once) {
+                        this.removeHook(hookName, hook.id);
+                    }
+                } catch (error) {
+                    console.error(`Error executing hook '${hookName}' (${hook.id}):`, error);
+                }
+            }
+        }
+ 
+        return results;
+    }
+ 
+    /**
+     * Execute hooks synchronously
+     *
+     * @method executeSync
+     * @param {String} hookName Hook name
+     * @param {...*} args Arguments to pass to hook handlers
+     * @returns {Array} Array of hook results
+     */
+    executeSync(hookName, ...args) {
+        const hookList = this.hooks[hookName] || [];
+        const results = [];
+ 
+        for (const hook of hookList) {
+            if (!hook.enabled) {
+                continue;
+            }
+ 
+            if (typeof hook.handler === 'function') {
+                try {
+                    const result = hook.handler(...args);
+                    results.push(result);
+ 
+                    if (hook.once) {
+                        this.removeHook(hookName, hook.id);
+                    }
+                } catch (error) {
+                    console.error(`Error executing hook '${hookName}' (${hook.id}):`, error);
+                }
+            }
+        }
+ 
+        return results;
+    }
+ 
+    /**
+     * Remove a specific hook
+     *
+     * @method removeHook
+     * @param {String} hookName Hook name
+     * @param {String} hookId Hook ID
+     */
+    removeHook(hookName, hookId) {
+        if (this.hooks[hookName]) {
+            this.hooks[hookName] = this.hooks[hookName].filter((h) => h.id !== hookId);
+        }
+    }
+ 
+    /**
+     * Remove all hooks for a given name
+     *
+     * @method removeAllHooks
+     * @param {String} hookName Hook name
+     */
+    removeAllHooks(hookName) {
+        if (this.hooks[hookName]) {
+            this.hooks[hookName] = [];
+        }
+    }
+ 
+    /**
+     * Get all hooks for a given name
+     *
+     * @method getHooks
+     * @param {String} hookName Hook name
+     * @returns {Array} Array of hooks
+     */
+    getHooks(hookName) {
+        return this.hooks[hookName] || [];
+    }
+ 
+    /**
+     * Check if a hook exists
+     *
+     * @method hasHook
+     * @param {String} hookName Hook name
+     * @returns {Boolean} True if hook exists
+     */
+    hasHook(hookName) {
+        return this.hooks[hookName] && this.hooks[hookName].length > 0;
+    }
+ 
+    /**
+     * Enable a hook
+     *
+     * @method enableHook
+     * @param {String} hookName Hook name
+     * @param {String} hookId Hook ID
+     */
+    enableHook(hookName, hookId) {
+        const hook = this.#findHook(hookName, hookId);
+        if (hook) {
+            hook.enabled = true;
+        }
+    }
+ 
+    /**
+     * Disable a hook
+     *
+     * @method disableHook
+     * @param {String} hookName Hook name
+     * @param {String} hookId Hook ID
+     */
+    disableHook(hookName, hookId) {
+        const hook = this.#findHook(hookName, hookId);
+        if (hook) {
+            hook.enabled = false;
+        }
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/index.html new file mode 100644 index 0000000..ff67650 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe

+
+ +
+ 0% + Statements + 0/489 +
+ + +
+ 0% + Branches + 0/442 +
+ + +
+ 0% + Functions + 0/135 +
+ + +
+ 0% + Lines + 0/458 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
hook-service.js +
+
0%0/840%0/600%0/160%0/77
menu-service.js +
+
0%0/1370%0/1610%0/360%0/120
registry-service.js +
+
0%0/1190%0/1050%0/350%0/116
resource-view-service.js +
+
0%0/850%0/680%0/240%0/83
widget-service.js +
+
0%0/640%0/480%0/240%0/62
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/menu-service.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/menu-service.js.html new file mode 100644 index 0000000..713a4d8 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/menu-service.js.html @@ -0,0 +1,1951 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/menu-service.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe menu-service.js

+
+ +
+ 0% + Statements + 0/137 +
+ + +
+ 0% + Branches + 0/161 +
+ + +
+ 0% + Functions + 0/36 +
+ + +
+ 0% + Lines + 0/120 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service from '@ember/service';
+import Evented from '@ember/object/evented';
+import { tracked } from '@glimmer/tracking';
+import { inject as service } from '@ember/service';
+import { dasherize } from '@ember/string';
+import { A, isArray } from '@ember/array';
+import MenuItem from '../../contracts/menu-item';
+import MenuPanel from '../../contracts/menu-panel';
+ 
+/**
+ * MenuManagerService
+ *
+ * Manages all menu items and panels in the application.
+ * Uses RegistryService for storage, providing cross-engine access.
+ *
+ * @class MenuService
+ * @extends Service
+ */
+export default class MenuService extends Service.extend(Evented) {
+    @service('universe/registry-service') registry;
+    @service universe;
+ 
+    /**
+     * Reference to the root Ember Application Instance.
+     * Used for registering components/services to the application container
+     * for cross-engine sharing.
+     */
+    @tracked applicationInstance = null;
+ 
+    /**
+     * Set the application instance (for consistency with other services)
+     *
+     * @method setApplicationInstance
+     * @param {Application} application The root application instance
+     */
+    setApplicationInstance(application) {
+        this.applicationInstance = application;
+    }
+ 
+    /**
+     * Wrap an onClick handler to automatically pass menuItem and universe as parameters
+     *
+     * @private
+     * @method #wrapOnClickHandler
+     * @param {Function} onClick The original onClick function
+     * @param {Object} menuItem The menu item object
+     * @returns {Function} Wrapped onClick function
+     */
+    #wrapOnClickHandler(onClick, menuItem) {
+        const universe = this.universe;
+        return function () {
+            return onClick(menuItem, universe);
+        };
+    }
+ 
+    /**
+     * Normalize a menu item input to a plain object
+     *
+     * @private
+     * @method #normalizeMenuItem
+     * @param {MenuItem|String|Object} input MenuItem instance, title, or object
+     * @param {String} route Optional route
+     * @param {Object} options Optional options
+     * @returns {Object} Normalized menu item object
+     */
+    #normalizeMenuItem(input, route = null, options = {}) {
+        let menuItemObj;
+ 
+        if (input instanceof MenuItem) {
+            menuItemObj = input.toObject();
+        } else if (typeof input === 'object' && input !== null && !input.title) {
+            menuItemObj = input;
+        } else if (typeof input === 'string') {
+            const menuItem = new MenuItem(input, route);
+ 
+            // Apply options
+            Object.keys(options).forEach((key) => {
+                if (key === 'icon') menuItem.withIcon(options[key]);
+                else if (key === 'priority') menuItem.withPriority(options[key]);
+                else if (key === 'component') menuItem.withComponent(options[key]);
+                else if (key === 'slug') menuItem.withSlug(options[key]);
+                else if (key === 'section') menuItem.inSection(options[key]);
+                else if (key === 'index') menuItem.atIndex(options[key]);
+                else if (key === 'type') menuItem.withType(options[key]);
+                else if (key === 'wrapperClass') menuItem.withWrapperClass(options[key]);
+                else if (key === 'queryParams') menuItem.withQueryParams(options[key]);
+                else if (key === 'onClick') menuItem.withOnClick(options[key]);
+                else menuItem.setOption(key, options[key]);
+            });
+ 
+            menuItemObj = menuItem.toObject();
+        } else {
+            menuItemObj = input;
+        }
+ 
+        // Wrap onClick handler to automatically pass menuItem and universe
+        if (menuItemObj && typeof menuItemObj.onClick === 'function') {
+            menuItemObj.onClick = this.#wrapOnClickHandler(menuItemObj.onClick, menuItemObj);
+        }
+ 
+        return menuItemObj;
+    }
+ 
+    /**
+     * Normalize a menu panel input to a plain object
+     *
+     * @private
+     * @method #normalizeMenuPanel
+     * @param {MenuPanel|String|Object} input MenuPanel instance, title, or object
+     * @param {Array} items Optional items
+     * @param {Object} options Optional options
+     * @returns {Object} Normalized menu panel object
+     */
+    #normalizeMenuPanel(input, items, options) {
+        if (input instanceof MenuPanel) {
+            return input.toObject();
+        }
+ 
+        if (typeof input === 'object' && input !== null && !input.title) {
+            return input;
+        }
+ 
+        if (typeof input === 'string') {
+            const panel = new MenuPanel(input, items);
+ 
+            if (options.slug) panel.withSlug(options.slug);
+            if (options.icon) panel.withIcon(options.icon);
+            if (options.priority) panel.withPriority(options.priority);
+ 
+            return panel.toObject();
+        }
+ 
+        return input;
+    }
+ 
+    // ============================================================================
+    // Registration Methods
+    // ============================================================================
+ 
+    /**
+     * Register a header menu item
+     *
+     * @method registerHeaderMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {String} route Optional route (if first param is string)
+     * @param {Object} options Optional options (if first param is string)
+     */
+    registerHeaderMenuItem(itemOrTitle, route = null, options = {}) {
+        const menuItem = this.#normalizeMenuItem(itemOrTitle, route, options);
+        this.registry.register('header', 'menu-item', menuItem.slug, menuItem);
+ 
+        // Auto-register each shortcut as a first-class header menu item so that
+        // they appear in the customiser's "All Extensions" list and can be found
+        // by id in allItems when pinned to the bar.
+        if (isArray(menuItem.shortcuts)) {
+            for (const sc of menuItem.shortcuts) {
+                const scId = sc.id ?? dasherize(menuItem.id + '-sc-' + sc.title);
+                const scSlug = sc.slug ?? scId;
+ 
+                // Build a first-class item that supports the full MenuItem
+                // property surface.  Each property falls back to the parent's
+                // value so shortcuts inherit sensible defaults without the
+                // consumer having to repeat them.
+                const scItem = {
+                    // ── Identity ──────────────────────────────────────────────
+                    id: scId,
+                    slug: scSlug,
+                    title: sc.title,
+                    text: sc.text ?? sc.title,
+                    label: sc.label ?? sc.title,
+                    view: sc.view ?? scId,
+ 
+                    // ── Routing ───────────────────────────────────────────────
+                    route: sc.route ?? menuItem.route,
+                    section: sc.section ?? null,
+                    queryParams: sc.queryParams ?? {},
+                    routeParams: sc.routeParams ?? [],
+ 
+                    // ── Icons (full surface) ──────────────────────────────────
+                    icon: sc.icon ?? menuItem.icon,
+                    iconPrefix: sc.iconPrefix ?? menuItem.iconPrefix,
+                    iconSize: sc.iconSize ?? menuItem.iconSize ?? null,
+                    iconClass: sc.iconClass ?? menuItem.iconClass ?? null,
+                    iconComponent: sc.iconComponent ?? null,
+                    iconComponentOptions: sc.iconComponentOptions ?? {},
+ 
+                    // ── Metadata ──────────────────────────────────────────────
+                    description: sc.description ?? null,
+                    // Shortcuts inherit parent tags so they surface under the
+                    // same search terms; shortcut-specific tags take precedence.
+                    tags: isArray(sc.tags) ? sc.tags : isArray(menuItem.tags) ? menuItem.tags : null,
+ 
+                    // ── Behaviour ─────────────────────────────────────────────
+                    onClick: sc.onClick ?? null,
+                    disabled: sc.disabled ?? false,
+                    // Shortcuts without their own permission inherit the parent's,
+                    // so the header never offers a module the user cannot open.
+                    permission: sc.permission ?? menuItem.permission ?? null,
+                    type: sc.type ?? 'default',
+                    buttonType: sc.buttonType ?? null,
+ 
+                    // ── Styling ───────────────────────────────────────────────
+                    class: sc.class ?? null,
+                    inlineClass: sc.inlineClass ?? null,
+                    wrapperClass: sc.wrapperClass ?? null,
+ 
+                    // ── Internal flags ────────────────────────────────────────
+                    _isShortcut: true,
+                    _parentTitle: menuItem.title,
+                    _parentId: menuItem.id,
+                    priority: (menuItem.priority ?? 0) + 1,
+                    _isMenuItem: true,
+                };
+                this.registry.register('header', 'menu-item', scSlug, scItem);
+                this.trigger('menuItem.registered', scItem, 'header');
+            }
+        }
+ 
+        // Trigger event for backward compatibility
+        this.trigger('menuItem.registered', menuItem, 'header');
+    }
+ 
+    /**
+     * Register an admin menu item
+     *
+     * @method registerAdminMenuItem
+     * @param {MenuItem|String} itemOrTitle MenuItem instance or title
+     * @param {String} route Optional route (if first param is string)
+     * @param {Object} options Optional options (if first param is string)
+     */
+    registerAdminMenuItem(itemOrTitle, route = null, options = {}) {
+        const menuItem = this.#normalizeMenuItem(itemOrTitle, route, options);
+        this.registry.register('console:admin', 'menu-item', menuItem.slug, menuItem);
+ 
+        // Trigger event for backward compatibility
+        this.trigger('menuItem.registered', menuItem, 'console:admin');
+    }
+ 
+    /**
+     * Register an organization menu item
+     *
+     * @method registerOrganizationMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {Object} options Optional options
+     */
+    registerOrganizationMenuItem(menuItemOrTitle, options = {}) {
+        const menuItem = this.#normalizeMenuItem(menuItemOrTitle, options.route || 'console.virtual', options);
+ 
+        if (!menuItem.section) {
+            menuItem.section = 'settings';
+        }
+ 
+        this.registry.register('console:account', 'menu-item', `organization:${menuItem.slug}`, menuItem);
+    }
+ 
+    /**
+     * Register a user menu item
+     *
+     * @method registerUserMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {Object} options Optional options
+     */
+    registerUserMenuItem(menuItemOrTitle, options = {}) {
+        const menuItem = this.#normalizeMenuItem(menuItemOrTitle, options.route || 'console.virtual', options);
+ 
+        if (!menuItem.section) {
+            menuItem.section = 'account';
+        }
+ 
+        this.registry.register('console:account', 'menu-item', `user:${menuItem.slug}`, menuItem);
+    }
+ 
+    /**
+     * Register an admin menu panel
+     *
+     * @method registerAdminMenuPanel
+     * @param {MenuPanel|String} panelOrTitle MenuPanel instance or title
+     * @param {Array} items Optional items array (if first param is string)
+     * @param {Object} options Optional options (if first param is string)
+     */
+    registerAdminMenuPanel(panelOrTitle, items = [], options = {}) {
+        const panel = this.#normalizeMenuPanel(panelOrTitle, items, options);
+        this.registry.register('console:admin', 'menu-panel', panel.slug, panel);
+ 
+        // The PDF states: "Additionally registering menu panels should also register there items."
+        // We assume the items are passed in the panel object or items array.
+        if (panel.items && panel.items.length) {
+            panel.items = panel.items.map((item) => {
+                const menuItem = this.#normalizeMenuItem(item);
+ 
+                // CRITICAL: Original behavior for panel items:
+                // - slug = panel slug (e.g., 'fleet-ops') ← Used in URL
+                // - view = item slug (e.g., 'navigator-app') ← Used in query param
+                // - section = null (not used for panel items)
+                // Result: /admin/fleet-ops?view=navigator-app
+ 
+                const itemSlug = menuItem.slug; // Save the original item slug
+                menuItem.slug = panel.slug; // Set slug to panel slug for URL
+                menuItem.view = itemSlug; // Set view to item slug for query param
+                menuItem.section = null; // Panel items don't use section
+ 
+                // Mark as panel item to prevent duplication in main menu
+                menuItem._isPanelItem = true;
+                menuItem._panelSlug = panel.slug;
+ 
+                // Register with the item slug as key (for lookup)
+                this.registry.register('console:admin', 'menu-item', itemSlug, menuItem);
+ 
+                // Trigger event for backward compatibility
+                this.trigger('menuItem.registered', menuItem, 'console:admin');
+ 
+                // Return the modified menu item so panel.items gets updated
+                return menuItem;
+            });
+        }
+ 
+        // Trigger event for backward compatibility
+        this.trigger('menuPanel.registered', panel, 'console:admin');
+    }
+ 
+    /**
+     * Register a settings menu item
+     *
+     * @method registerSettingsMenuItem
+     * @param {MenuItem|String} menuItemOrTitle MenuItem instance or title
+     * @param {Object} options Optional options
+     */
+    registerSettingsMenuItem(menuItemOrTitle, options = {}) {
+        const menuItem = this.#normalizeMenuItem(menuItemOrTitle, options.route || 'console.settings.virtual', options);
+ 
+        this.registry.register('console:settings', 'menu-item', menuItem.slug, menuItem);
+    }
+ 
+    /**
+     * Register a menu item to a custom registry
+     *
+     * Supports two patterns:
+     * 1. Original: registerMenuItem(registryName, title, options)
+     * 2. New: registerMenuItem(registryName, menuItemInstance)
+     *
+     * @method registerMenuItem
+     * @param {String} registryName Registry name (e.g., 'auth:login', 'engine:fleet-ops')
+     * @param {String|MenuItem} titleOrMenuItem Menu item title string or MenuItem instance
+     * @param {Object} options Optional options (only used with title string)
+     */
+    registerMenuItem(registryName, titleOrMenuItem, options = {}) {
+        let menuItem;
+ 
+        // Normalize the menu item first (handles both MenuItem instances and string titles)
+        if (titleOrMenuItem instanceof MenuItem) {
+            menuItem = this.#normalizeMenuItem(titleOrMenuItem);
+        } else {
+            // Original pattern: title string + options
+            const title = titleOrMenuItem;
+            const route = options.route || `console.${dasherize(registryName)}.virtual`;
+ 
+            // Set defaults matching original behavior
+            const slug = options.slug || '~';
+ 
+            menuItem = this.#normalizeMenuItem(title, route, {
+                ...options,
+                slug,
+            });
+        }
+ 
+        // Apply finalView normalization consistently for ALL menu items
+        // If slug === view, set view to null to prevent redundant query params
+        // This matches the legacy behavior: const finalView = (slug === view) ? null : view;
+        if (menuItem.slug && menuItem.view && menuItem.slug === menuItem.view) {
+            menuItem.view = null;
+        }
+ 
+        // Register the menu item. Items registered by title share the '~' slug (their URL is
+        // `virtual/~?view=<view>`), so they are keyed by view or title instead; keyed by '~'
+        // each would replace the one before it.
+        const key = menuItem.slug && menuItem.slug !== '~' ? menuItem.slug : (menuItem.view ?? menuItem.title);
+        this.registry.register(registryName, 'menu-item', key, menuItem);
+ 
+        // Trigger event
+        this.trigger('menuItem.registered', menuItem, registryName);
+    }
+ 
+    // ============================================================================
+    // Getter Methods (Improved DX)
+    // ============================================================================
+ 
+    /**
+     * Get menu items from a registry
+     *
+     * @method getMenuItems
+     * @param {String} registryName Registry name (e.g., 'engine:fleet-ops')
+     * @returns {Array} Menu items
+     */
+    getMenuItems(registryName) {
+        return this.registry.getRegistry(registryName, 'menu-item');
+    }
+ 
+    /**
+     * Get menu panels from a registry
+     *
+     * @method getMenuPanels
+     * @param {String} registryName Registry name (e.g., 'engine:fleet-ops')
+     * @returns {Array} Menu panels
+     */
+    getMenuPanels(registryName) {
+        return this.registry.getRegistry(registryName, 'menu-panel');
+    }
+ 
+    /**
+     * Lookup a menu item from a registry
+     *
+     * @method lookupMenuItem
+     * @param {String} registryName Registry name
+     * @param {String} slug Menu item slug
+     * @param {String} view Optional view
+     * @param {String} section Optional section
+     * @returns {Object|null} Menu item or null
+     */
+    lookupMenuItem(registryName, slug, view = null, section = null) {
+        const items = this.getMenuItems(registryName);
+        return items.find((item) => {
+            const slugMatch = item.slug === slug;
+            const viewMatch = !view || item.view === view;
+            const sectionMatch = !section || item.section === section;
+            return slugMatch && viewMatch && sectionMatch;
+        });
+    }
+ 
+    /**
+     * Alias for lookupMenuItem
+     *
+     * @method getMenuItem
+     * @param {String} registryName Registry name
+     * @param {String} slug Menu item slug
+     * @param {String} view Optional view
+     * @param {String} section Optional section
+     * @returns {Object|null} Menu item or null
+     */
+    getMenuItem(registryName, slug, view = null, section = null) {
+        return this.lookupMenuItem(registryName, slug, view, section);
+    }
+ 
+    /**
+     * Get header menu items
+     *
+     * @method getHeaderMenuItems
+     * @returns {Array} Header menu items sorted by priority
+     */
+    getHeaderMenuItems() {
+        const items = this.registry.getRegistry('header', 'menu-item');
+        // Sort extensions by priority first, then append shortcuts at the end.
+        // Shortcuts must never interleave with extensions in the sorted list
+        // because the default bar is built by slicing the first N items — if
+        // shortcuts sort between extensions (e.g. priority 1.1 between 1 and 2)
+        // they would displace real extensions from the default pinned bar.
+        // A(...).filter() returns a plain array, which has no sortBy, so the
+        // result has to be re-wrapped before sorting.
+        const extensions = A(A(items).filter((i) => !i._isShortcut)).sortBy('priority');
+        const shortcuts = A(items).filter((i) => i._isShortcut);
+        return A([...extensions, ...shortcuts]);
+    }
+ 
+    /**
+     * Get organization menu items
+     *
+     * @method getOrganizationMenuItems
+     * @returns {Array} Organization menu items
+     */
+    getOrganizationMenuItems() {
+        return this.registry.getAllFromPrefix('console:account', 'menu-item', 'organization:');
+    }
+ 
+    /**
+     * Get user menu items
+     *
+     * @method getUserMenuItems
+     * @returns {Array} User menu items
+     */
+    getUserMenuItems() {
+        return this.registry.getAllFromPrefix('console:account', 'menu-item', 'user:');
+    }
+ 
+    /**
+     * Get admin menu panels
+     *
+     * @method getAdminMenuPanels
+     * @returns {Array} Admin panels sorted by priority
+     */
+    getAdminMenuPanels() {
+        const panels = this.registry.getRegistry('console:admin', 'menu-panel');
+        return A(panels).sortBy('priority');
+    }
+ 
+    /**
+     * Alias for getAdminMenuPanels
+     *
+     * @method getAdminPanels
+     * @returns {Array} Admin panels
+     */
+    getAdminPanels() {
+        return this.getAdminMenuPanels();
+    }
+ 
+    /**
+     * Get admin menu items
+     * Excludes items that belong to panels (to prevent duplication)
+     *
+     * @method getAdminMenuItems
+     * @returns {Array} Admin menu items (excluding panel items)
+     */
+    getAdminMenuItems() {
+        const items = this.registry.getRegistry('console:admin', 'menu-item');
+        // Filter out panel items to prevent duplication in the UI
+        return items.filter((item) => !item._isPanelItem);
+    }
+ 
+    /**
+     * Get menu items from a specific panel
+     *
+     * @method getMenuItemsFromPanel
+     * @param {String} panelSlug Panel slug
+     * @returns {Array} Menu items belonging to the panel
+     */
+    getMenuItemsFromPanel(panelSlug) {
+        const items = this.registry.getRegistry('console:admin', 'menu-item');
+        return items.filter((item) => item._panelSlug === panelSlug);
+    }
+ 
+    /**
+     * Get settings menu items
+     *
+     * @method getSettingsMenuItems
+     * @returns {Array} Settings menu items
+     */
+    getSettingsMenuItems() {
+        return this.registry.getRegistry('console:settings', 'menu-item');
+    }
+ 
+    /**
+     * Get settings menu panels
+     *
+     * @method getSettingsMenuPanels
+     * @returns {Array} Settings menu panels
+     */
+    getSettingsMenuPanels() {
+        const panels = this.registry.getRegistry('console:settings', 'menu-panel');
+        return A(panels).sortBy('priority');
+    }
+    // ============================================================================
+    // Computed Getters (for template access)
+    // ============================================================================
+ 
+    /**
+     * Get header menu items (computed getter)
+     *
+     * @computed headerMenuItems
+     * @returns {Array} Header menu items
+     */
+    get headerMenuItems() {
+        return this.getHeaderMenuItems();
+    }
+ 
+    /**
+     * Get organization menu items (computed getter)
+     *
+     * @computed organizationMenuItems
+     * @returns {Array} Organization menu items
+     */
+    get organizationMenuItems() {
+        return this.getOrganizationMenuItems();
+    }
+ 
+    /**
+     * Get user menu items (computed getter)
+     *
+     * @computed userMenuItems
+     * @returns {Array} User menu items
+     */
+    get userMenuItems() {
+        return this.getUserMenuItems();
+    }
+ 
+    /**
+     * Get admin menu items (computed getter)
+     *
+     * @computed adminMenuItems
+     * @returns {Array} Admin menu items
+     */
+    get adminMenuItems() {
+        return this.getAdminMenuItems();
+    }
+ 
+    /**
+     * Get admin menu panels (computed getter)
+     *
+     * @computed adminMenuPanels
+     * @returns {Array} Admin menu panels
+     */
+    get adminMenuPanels() {
+        return this.getAdminMenuPanels();
+    }
+ 
+    /**
+     * Get settings menu items (computed getter)
+     *
+     * @computed settingsMenuItems
+     * @returns {Array} Settings menu items
+     */
+    get settingsMenuItems() {
+        return this.getSettingsMenuItems();
+    }
+ 
+    /**
+     * Get settings menu panels (computed getter)
+     *
+     * @computed settingsMenuPanels
+     * @returns {Array} Settings menu panels
+     */
+    get settingsMenuPanels() {
+        return this.getSettingsMenuPanels();
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/registry-service.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/registry-service.js.html new file mode 100644 index 0000000..3ac6859 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/registry-service.js.html @@ -0,0 +1,1924 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/registry-service.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe registry-service.js

+
+ +
+ 0% + Statements + 0/119 +
+ + +
+ 0% + Branches + 0/105 +
+ + +
+ 0% + Functions + 0/35 +
+ + +
+ 0% + Lines + 0/116 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service, { inject as service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { warn } from '@ember/debug';
+import { A, isArray } from '@ember/array';
+import { TrackedObject } from 'tracked-built-ins';
+import { getOwner } from '@ember/application';
+import TemplateHelper from '../../contracts/template-helper';
+import UniverseRegistry from '../../contracts/universe-registry';
+ 
+/**
+ * RegistryService
+ *
+ * Fully dynamic, Map-based registry for storing categorized items.
+ * Supports grouped registries with multiple list types per section.
+ *
+ * Structure:
+ * registries (TrackedMap) → section name → TrackedObject { list-name: A([]), ... }
+ *
+ * Usage:
+ * ```javascript
+ * // Register an item to a specific list within a section
+ * registryService.register('console:admin', 'menu-panels', 'fleet-ops', panelObject);
+ *
+ * // Get all items from a list
+ * const panels = registryService.getRegistry('console:admin', 'menu-panels');
+ *
+ * // Lookup specific item
+ * const panel = registryService.lookup('console:admin', 'menu-panels', 'fleet-ops');
+ * ```
+ *
+ * @class RegistryService
+ * @extends Service
+ */
+/**
+ * Whether a helper is a class to instantiate (a `Helper` subclass) rather than a function.
+ */
+function isHelperClass(value) {
+    return typeof value.prototype?.compute === 'function';
+}
+ 
+/**
+ * Whether a registry item can carry a `_registryKey`: any object, or a class.
+ */
+function isKeyable(value) {
+    return (typeof value === 'object' && value !== null) || typeof value === 'function';
+}
+ 
+export default class RegistryService extends Service {
+    @service('universe/extension-manager') extensionManager;
+ 
+    /**
+     * Reference to the root Ember Application Instance.
+     * Used for registering components/services to the application container
+     * for cross-engine sharing.
+     */
+    @tracked applicationInstance = null;
+ 
+    #registry = null;
+ 
+    /**
+     * The singleton UniverseRegistry instance, resolved on first use.
+     * Initialized once and shared across the app and all engines.
+     *
+     * This is deliberately lazy. It was a field initializer, which runs during
+     * construction — before setApplicationInstance can possibly have been
+     * called — so #initializeRegistry's documented first choice, an explicitly
+     * set applicationInstance, could never be taken and every service fell back
+     * to the owner. HookService's registry was made lazy for the same reason.
+     *
+     * @type {UniverseRegistry}
+     */
+    get registry() {
+        return this.#resolveRegistry();
+    }
+ 
+    /**
+     * Memoized resolution, kept out of the getter body so a property read is not
+     * itself an assignment.
+     *
+     * @private
+     * @returns {UniverseRegistry}
+     */
+    #resolveRegistry() {
+        if (!this.#registry) {
+            this.#registry = this.#initializeRegistry();
+        }
+ 
+        return this.#registry;
+    }
+ 
+    /**
+     * Getter for the registries TrackedMap.
+     * Provides access to the shared registry data.
+     * @type {TrackedMap<string, TrackedObject>}
+     */
+    get registries() {
+        return this.registry.registries;
+    }
+ 
+    /**
+     * Sets the root Ember Application Instance.
+     * Called by an initializer to enable cross-engine registration.
+     * @method setApplicationInstance
+     * @param {Object} appInstance
+     */
+    setApplicationInstance(application) {
+        this.applicationInstance = application;
+    }
+ 
+    /**
+     * Initializes the UniverseRegistry singleton.
+     * Registers it to the application container if not already registered.
+     * This ensures all service instances (app and engines) share the same registry.
+     * @private
+     * @method #initializeRegistry
+     * @returns {UniverseRegistry} The singleton registry instance
+     */
+    #initializeRegistry() {
+        const registryKey = 'registry:universe';
+ 
+        // First priority: use applicationInstance if set
+        let application = this.applicationInstance;
+ 
+        if (!application) {
+            // Second priority: try to get from owner
+            const owner = getOwner(this);
+            if (owner && owner.application) {
+                application = owner.application;
+            } else {
+                warn('[RegistryService] Could not find application instance for registry initialization', {
+                    id: 'registry-service.no-application',
+                });
+                // Return a new instance as fallback (won't be shared)
+                return new UniverseRegistry();
+            }
+        }
+ 
+        // Register the singleton if not already registered
+        if (!application.hasRegistration(registryKey)) {
+            application.register(registryKey, new UniverseRegistry(), {
+                instantiate: false,
+            });
+        }
+ 
+        // Resolve and return the singleton instance
+        return application.resolveRegistration(registryKey);
+    }
+ 
+    /**
+     * Get or create a registry section.
+     * Returns a TrackedObject containing dynamic lists.
+     *
+     * @method getOrCreateSection
+     * @param {String} sectionName Section name (e.g., 'console:admin', 'dashboard:widgets')
+     * @returns {TrackedObject} The section object
+     */
+    getOrCreateSection(sectionName) {
+        if (!this.registries.has(sectionName)) {
+            this.registries.set(sectionName, new TrackedObject({}));
+        }
+        return this.registries.get(sectionName);
+    }
+ 
+    /**
+     * Get or create a list within a section.
+     * Returns an Ember Array for the specified list.
+     *
+     * @method getOrCreateList
+     * @param {String} sectionName Section name
+     * @param {String} listName List name (e.g., 'menu-items', 'menu-panels')
+     * @returns {Array} The Ember Array for the list
+     */
+    getOrCreateList(sectionName, listName) {
+        const section = this.getOrCreateSection(sectionName);
+ 
+        if (!section[listName]) {
+            section[listName] = A([]);
+        }
+ 
+        return section[listName];
+    }
+ 
+    /**
+     * Register an item in a specific list within a registry section.
+     *
+     * @method register
+     * @param {String} sectionName Section name (e.g., 'console:admin')
+     * @param {String} listName List name within the section (e.g., 'menu-items', 'menu-panels')
+     * @param {String} key Unique identifier for the item
+     * @param {Object} value The item to register
+     */
+    register(sectionName, listName, key, value) {
+        const registry = this.getOrCreateList(sectionName, listName);
+ 
+        // Store the key with the value for lookups. A class (a renderable component) is
+        // keyed too, or nothing could look it up by name.
+        if (isKeyable(value)) {
+            value._registryKey = key;
+        }
+ 
+        // Check if already exists
+        const existing = registry.find((item) => {
+            if (isKeyable(item)) {
+                return item._registryKey === key || item.slug === key || item.id === key || item.widgetId === key;
+            }
+            return false;
+        });
+ 
+        if (existing) {
+            // Update existing item
+            const index = registry.indexOf(existing);
+            registry.replace(index, 1, [value]);
+        } else {
+            // Add new item
+            registry.pushObject(value);
+        }
+    }
+ 
+    /**
+     * Get all items from a specific list within a registry section.
+     *
+     * @method getRegistry
+     * @param {String} sectionName Section name (e.g., 'console:admin')
+     * @param {String} listName List name within the section (e.g., 'menu-items', 'menu-panels')
+     * @returns {Array} Array of items in the list
+     */
+    getRegistry(sectionName, listName) {
+        const section = this.registries.get(sectionName);
+ 
+        if (!section || !section[listName]) {
+            return A([]);
+        }
+ 
+        return section[listName];
+    }
+ 
+    /**
+     * Get the entire section object (all lists within a section).
+     *
+     * @method getSection
+     * @param {String} sectionName Section name
+     * @returns {TrackedObject|null} The section object or null
+     */
+    getSection(sectionName) {
+        return this.registries.get(sectionName) || null;
+    }
+ 
+    /**
+     * Lookup a specific item by key
+     *
+     * @method lookup
+     * @param {String} sectionName Section name (e.g., 'console:admin')
+     * @param {String} listName List name within the section (e.g., 'menu-items', 'menu-panels')
+     * @param {String} key Item key
+     * @returns {Object|null} The item or null if not found
+     */
+    lookup(sectionName, listName, key) {
+        const registry = this.getRegistry(sectionName, listName);
+        return (
+            registry.find((item) => {
+                if (isKeyable(item)) {
+                    return item._registryKey === key || item.slug === key || item.id === key || item.widgetId === key;
+                }
+                return false;
+            }) || null
+        );
+    }
+ 
+    /**
+     * Get items matching a key prefix
+     *
+     * @method getAllFromPrefix
+     * @param {String} sectionName Section name (e.g., 'console:admin')
+     * @param {String} listName List name within the section (e.g., 'menu-items')
+     * @param {String} prefix Key prefix to match
+     * @returns {Array} Matching items
+     */
+    getAllFromPrefix(sectionName, listName, prefix) {
+        const registry = this.getRegistry(sectionName, listName);
+        return registry.filter((item) => {
+            if (typeof item === 'object' && item !== null && item._registryKey) {
+                return item._registryKey.startsWith(prefix);
+            }
+            return false;
+        });
+    }
+ 
+    /**
+     * Register a renderable component for cross-engine rendering
+     * Supports both ExtensionComponent definitions and raw component classes
+     *
+     * @method registerRenderableComponent
+     * @param {String} registryName Registry name (slot identifier)
+     * @param {Object|Class|Array} component ExtensionComponent definition, component class, or array of either
+     * @param {Object} options Optional configuration
+     * @param {String} options.engineName Engine name (required for raw component classes)
+     *
+     * @example
+     * // ExtensionComponent definition with path (lazy loading)
+     * registryService.registerRenderableComponent(
+     *     'fleet-ops:component:order:details',
+     *     new ExtensionComponent('@fleetbase/storefront-engine', 'storefront-order-summary')
+     * );
+     *
+     * @example
+     * // ExtensionComponent definition with class (immediate)
+     * import MyComponent from './components/my-component';
+     * registryService.registerRenderableComponent(
+     *     'fleet-ops:component:order:details',
+     *     new ExtensionComponent('@fleetbase/fleetops-engine', MyComponent)
+     * );
+     *
+     * @example
+     * // Raw component class (requires engineName in options)
+     * registryService.registerRenderableComponent(
+     *     'fleet-ops:component:order:details',
+     *     MyComponent,
+     *     { engineName: '@fleetbase/fleetops-engine' }
+     * );
+     */
+    registerRenderableComponent(registryName, component, options = {}) {
+        // Handle arrays
+        if (isArray(component)) {
+            component.forEach((comp) => this.registerRenderableComponent(registryName, comp, options));
+            return;
+        }
+ 
+        // Generate unique key for the component
+        const key = component._registryKey || component.name || component.path || `component-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+ 
+        // Register to RegistryService using map-based structure
+        // Structure: registries.get(registryName).components = [component1, component2, ...]
+        this.register(registryName, 'components', key, component);
+    }
+ 
+    /**
+     * Get renderable components from a registry
+     *
+     * @method getRenderableComponents
+     * @param {String} registryName Registry name
+     * @returns {Array} Array of component definitions/classes
+     */
+    getRenderableComponents(registryName) {
+        return this.getRegistry(registryName, 'components');
+    }
+ 
+    /**
+     * Create a registry (section with default list).
+     * For backward compatibility with existing code.
+     * Creates a section with a 'menu-item' list by default.
+     *
+     * @method createRegistry
+     * @param {String} sectionName Section name
+     * @param {String} type Type of registry
+     * @returns {Array} The default list array
+     */
+    createRegistry(sectionName, type = 'menu-item') {
+        return this.getOrCreateList(sectionName, type);
+    }
+ 
+    /**
+     * Create multiple registries
+     *
+     * @method createRegistries
+     * @param {Array} sectionNames Array of section names
+     */
+    createRegistries(sectionNames) {
+        if (isArray(sectionNames)) {
+            sectionNames.forEach((sectionName) => this.createRegistry(sectionName));
+        }
+    }
+ 
+    /**
+     * Create a registry section (or get existing).
+     * This is a convenience method for explicitly creating sections.
+     *
+     * @method createSection
+     * @param {String} sectionName Section name
+     * @returns {TrackedObject} The section object
+     */
+    createSection(sectionName) {
+        return this.getOrCreateSection(sectionName);
+    }
+ 
+    /**
+     * Create multiple registry sections
+     *
+     * @method createSections
+     * @param {Array} sectionNames Array of section names
+     */
+    createSections(sectionNames) {
+        if (isArray(sectionNames)) {
+            sectionNames.forEach((sectionName) => this.createSection(sectionName));
+        }
+    }
+ 
+    /**
+     * Check if a section exists
+     *
+     * @method hasSection
+     * @param {String} sectionName Section name
+     * @returns {Boolean} True if section exists
+     */
+    hasSection(sectionName) {
+        return this.registries.has(sectionName);
+    }
+ 
+    /**
+     * Check if a list exists within a section
+     *
+     * @method hasList
+     * @param {String} sectionName Section name
+     * @param {String} listName List name
+     * @returns {Boolean} True if list exists
+     */
+    hasList(sectionName, listName) {
+        const section = this.registries.get(sectionName);
+        return !!(section && section[listName]);
+    }
+ 
+    /**
+     * Clear a specific list within a section
+     *
+     * @method clearList
+     * @param {String} sectionName Section name
+     * @param {String} listName List name
+     */
+    clearList(sectionName, listName) {
+        const section = this.registries.get(sectionName);
+        if (section && section[listName]) {
+            section[listName].clear();
+        }
+    }
+ 
+    /**
+     * Clear an entire section (all lists)
+     *
+     * @method clearSection
+     * @param {String} sectionName Section name
+     */
+    clearSection(sectionName) {
+        const section = this.registries.get(sectionName);
+        if (section) {
+            Object.keys(section).forEach((listName) => {
+                if (section[listName] && typeof section[listName].clear === 'function') {
+                    section[listName].clear();
+                }
+            });
+            this.registries.delete(sectionName);
+        }
+    }
+ 
+    /**
+     * Clear all registries
+     *
+     * @method clearAll
+     */
+    clearAll() {
+        this.registries.forEach((section) => {
+            Object.keys(section).forEach((listName) => {
+                if (section[listName] && typeof section[listName].clear === 'function') {
+                    section[listName].clear();
+                }
+            });
+        });
+        this.registries.clear();
+    }
+ 
+    /**
+     * Registers a component to the root application container.
+     * This ensures the component is available to all engines and the host app.
+     * @method registerComponent
+     * @param {String} name The component name (e.g., 'my-component')
+     * @param {Class} componentClass The component class
+     * @param {Object} options Registration options (e.g., { singleton: true })
+     */
+    registerComponent(name, componentClass, options = {}) {
+        if (this.applicationInstance) {
+            this.applicationInstance.register(`component:${name}`, componentClass, options);
+        } else {
+            warn('Application instance not set on RegistryService. Cannot register component.', { id: 'registry-service.no-app-instance' });
+        }
+    }
+ 
+    /**
+     * Registers a service to the root application container.
+     * This ensures the service is available to all engines and the host app.
+     * @method registerService
+     * @param {String} name The service name (e.g., 'my-service')
+     * @param {Class} serviceClass The service class
+     * @param {Object} options Registration options (e.g., { singleton: true })
+     */
+    registerService(name, serviceClass, options = {}) {
+        if (this.applicationInstance) {
+            this.applicationInstance.register(`service:${name}`, serviceClass, options);
+        } else {
+            warn('Application instance not set on RegistryService. Cannot register service.', { id: 'registry-service.no-app-instance' });
+        }
+    }
+ 
+    /**
+     * Registers a helper to the root application container.
+     * This makes the helper available globally to all engines and the host app.
+     * Supports both direct helper functions/classes and lazy loading via TemplateHelper.
+     *
+     * @method registerHelper
+     * @param {String} helperName The helper name (e.g., 'calculate-delivery-fee')
+     * @param {Function|Class|TemplateHelper} helperClassOrTemplateHelper Helper function, class, or TemplateHelper instance
+     * @param {Object} options Registration options
+     * @param {Boolean} options.instantiate Whether to instantiate the helper (default: false for functions)
+     * @returns {Promise<void>}
+     *
+     * @example
+     * // Direct function registration
+     * await registryService.registerHelper('calculate-delivery-fee', calculateDeliveryFeeHelper);
+     *
+     * @example
+     * // Direct class registration
+     * await registryService.registerHelper('format-currency', FormatCurrencyHelper);
+     *
+     * @example
+     * // Lazy loading from engine (ensures engine is loaded first)
+     * await registryService.registerHelper(
+     *     'calculate-delivery-fee',
+     *     new TemplateHelper('@fleetbase/storefront-engine', 'helpers/calculate-delivery-fee')
+     * );
+     */
+    async registerHelper(helperName, helperClassOrTemplateHelper, options = {}) {
+        const owner = this.applicationInstance || getOwner(this);
+ 
+        if (!owner) {
+            warn('No owner available for helper registration. Cannot register helper.', {
+                id: 'registry-service.no-owner',
+            });
+            return;
+        }
+ 
+        // Check if it's a TemplateHelper instance
+        if (helperClassOrTemplateHelper instanceof TemplateHelper) {
+            const templateHelper = helperClassOrTemplateHelper;
+ 
+            if (templateHelper.isClass) {
+                // Direct class registration from TemplateHelper
+                owner.register(`helper:${helperName}`, templateHelper.class, {
+                    instantiate: options.instantiate !== undefined ? options.instantiate : true,
+                });
+            } else {
+                // Lazy loading from engine (async - ensures engine is loaded)
+                const helper = await this.#loadHelperFromEngine(templateHelper);
+                if (helper) {
+                    owner.register(`helper:${helperName}`, helper, {
+                        instantiate: options.instantiate !== undefined ? options.instantiate : true,
+                    });
+                } else {
+                    warn(`Failed to load helper from engine: ${templateHelper.engineName}/${templateHelper.path}`, {
+                        id: 'registry-service.helper-load-failed',
+                    });
+                }
+            }
+        } else {
+            // Direct function or class registration
+            // A Helper subclass is instantiated; a plain function helper is not, however it was written.
+            const instantiate = options.instantiate !== undefined ? options.instantiate : typeof helperClassOrTemplateHelper !== 'function' || isHelperClass(helperClassOrTemplateHelper);
+ 
+            owner.register(`helper:${helperName}`, helperClassOrTemplateHelper, {
+                instantiate,
+            });
+        }
+    }
+ 
+    /**
+     * Loads a helper from an engine using TemplateHelper definition.
+     * Ensures the engine is loaded before attempting to resolve the helper.
+     * @private
+     * @method #loadHelperFromEngine
+     * @param {TemplateHelper} templateHelper The TemplateHelper instance
+     * @returns {Promise<Function|Class|null>} The loaded helper or null if failed
+     */
+    async #loadHelperFromEngine(templateHelper) {
+        // No owner check here: registerHelper is the only caller and has already
+        // returned on the identical expression, so a second one could never fire.
+        try {
+            // Ensure the engine is loaded (will load if not already loaded)
+            const engineInstance = await this.extensionManager.ensureEngineLoaded(templateHelper.engineName);
+ 
+            if (!engineInstance) {
+                warn(`Engine could not be loaded: ${templateHelper.engineName}`, {
+                    id: 'registry-service.engine-not-loaded',
+                });
+                return null;
+            }
+ 
+            // Try to resolve the helper from the engine
+            const helperPath = templateHelper.path.startsWith('helper:') ? templateHelper.path : `helper:${templateHelper.path}`;
+ 
+            const helper = engineInstance.resolveRegistration(helperPath);
+ 
+            if (!helper) {
+                warn(`Helper not found in engine: ${helperPath}`, {
+                    id: 'registry-service.helper-not-found',
+                });
+                return null;
+            }
+ 
+            return helper;
+        } catch (error) {
+            warn(`Error loading helper from engine: ${error.message}`, {
+                id: 'registry-service.helper-load-error',
+            });
+            return null;
+        }
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/resource-view-service.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/resource-view-service.js.html new file mode 100644 index 0000000..1337271 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/resource-view-service.js.html @@ -0,0 +1,1177 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/resource-view-service.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe resource-view-service.js

+
+ +
+ 0% + Statements + 0/85 +
+ + +
+ 0% + Branches + 0/68 +
+ + +
+ 0% + Functions + 0/24 +
+ + +
+ 0% + Lines + 0/83 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service, { inject as service } from '@ember/service';
+import { warn } from '@ember/debug';
+import { isArray } from '@ember/array';
+import { getOwner } from '@ember/application';
+import mergeRegisteredItems from '../../utils/merge-registered-items';
+import isObject from '../../utils/is-object';
+import { parseRegistryName, parseRegistryPrefix, buildRegistryName, resourceRegistryNames } from '../../utils/resource-view-registry';
+ 
+/**
+ * ResourceViewService
+ *
+ * Lets extensions add columns, actions and buttons to the table and details
+ * views of any engine, and lets those views merge them in.
+ *
+ * Registry names follow `<extension>:<resource>:<surface>:<slot>`:
+ *
+ * | surface   | slot           | adds                                     | contract         |
+ * |-----------|----------------|------------------------------------------|------------------|
+ * | `table`   | `columns`      | table columns                            | `TableColumn`    |
+ * | `table`   | `row-actions`  | items in each row's dropdown             | `ResourceAction` |
+ * | `table`   | `bulk-actions` | items in the Bulk Actions dropdown       | `ResourceAction` |
+ * | `table`   | `actions`      | toolbar buttons                          | `ActionButton`   |
+ * | `details` | `actions`      | details header buttons                   | `ActionButton`   |
+ * | `details` | `menu`         | items in the details header's "…" menu   | `ResourceAction` |
+ * | `details` | `tabs`         | details tabs                             | `MenuItem`       |
+ *
+ * Items are stored in the shared universe registry: the section is the full
+ * registry name and the list is the contract type, e.g.
+ * `registryService.getRegistry('fleet-ops:driver:table:columns', 'table-column')`.
+ * Tabs are the exception: `<extension>:<resource>:details:tabs` is an alias for the
+ * legacy tab registry, `<extension>:component:<resource>:details`.
+ *
+ * @example
+ * const views = universe.getService('resource-view');
+ * views.register('fleet-ops:driver:table:row-actions', new ResourceAction({ id: 'acme-sync', label: 'Sync', fn: (driver) => … }));
+ * views.registerTableColumn('ledger', 'invoice', new TableColumn({ id: 'po', label: 'PO', valuePath: 'meta.po' }));
+ *
+ * @class ResourceViewService
+ * @extends Service
+ */
+export default class ResourceViewService extends Service {
+    @service('universe/registry-service') registryService;
+    @service('universe/menu-service') menuService;
+ 
+    /**
+     * Views whose query params were already read by a controller. A filter
+     * column registered for one of these afterwards cannot get its param.
+     */
+    #resolvedQueryParams = new Set();
+ 
+    // ========================================================================
+    // Naming
+    // ========================================================================
+ 
+    /**
+     * Every registry name for one resource's table and details views.
+     *
+     * @param {String} extension
+     * @param {String} resource
+     * @returns {Array<String>}
+     */
+    resourceRegistryNames(extension, resource) {
+        return resourceRegistryNames(extension, resource);
+    }
+ 
+    /**
+     * Declare the registries for resources an engine owns, so they exist (and
+     * show up in the registry) before anything registers into them.
+     *
+     * @param {String} extension The owning engine's mount segment
+     * @param {Array<String>} resources
+     * @returns {Array<String>} The declared names
+     */
+    declare(extension, resources = []) {
+        const names = resources.flatMap((resource) => resourceRegistryNames(extension, resource));
+        names.forEach((name) => {
+            const { section, list } = parseRegistryName(name);
+            this.registryService.getOrCreateList(section, list);
+        });
+        return names;
+    }
+ 
+    // ========================================================================
+    // Registering
+    // ========================================================================
+ 
+    /**
+     * Register one or more items into a view slot.
+     *
+     * Rejects, with a warning, a name that does not follow the convention, an
+     * item without an `id`, and a contract that does not belong in the slot.
+     *
+     * @param {String} registryName e.g. `fleet-ops:driver:table:columns`
+     * @param {Object|Array} items Contracts or plain objects
+     * @returns {Boolean} Whether every item was registered
+     */
+    register(registryName, items) {
+        const parsed = parseRegistryName(registryName);
+        if (!parsed) {
+            warn(`[resource-view] "${registryName}" is not a valid registry name. Use <extension>:<resource>:<table|details>:<slot>.`, false, {
+                id: 'resource-view.invalid-name',
+            });
+            return false;
+        }
+ 
+        return (isArray(items) ? items : [items]).map((item) => this.#registerItem(parsed, item)).every(Boolean);
+    }
+ 
+    registerTableColumn(extension, resource, column) {
+        return this.register(buildRegistryName(extension, resource, 'table', 'columns'), column);
+    }
+ 
+    registerRowAction(extension, resource, action) {
+        return this.register(buildRegistryName(extension, resource, 'table', 'row-actions'), action);
+    }
+ 
+    registerBulkAction(extension, resource, action) {
+        return this.register(buildRegistryName(extension, resource, 'table', 'bulk-actions'), action);
+    }
+ 
+    registerTableAction(extension, resource, button) {
+        return this.register(buildRegistryName(extension, resource, 'table', 'actions'), button);
+    }
+ 
+    registerDetailsAction(extension, resource, button) {
+        return this.register(buildRegistryName(extension, resource, 'details', 'actions'), button);
+    }
+ 
+    registerDetailsMenuItem(extension, resource, action) {
+        return this.register(buildRegistryName(extension, resource, 'details', 'menu'), action);
+    }
+ 
+    /**
+     * Register a details tab, through `<extension>:<resource>:details:tabs`, the
+     * alias for `<extension>:component:<resource>:details`.
+     *
+     * @returns {Boolean}
+     */
+    registerDetailsTab(extension, resource, menuItem) {
+        return this.register(buildRegistryName(extension, resource, 'details', 'tabs'), menuItem);
+    }
+ 
+    /**
+     * Remove a registered item.
+     *
+     * @param {String} registryName
+     * @param {String} id
+     * @returns {Boolean} Whether an item was removed
+     */
+    unregister(registryName, id) {
+        const parsed = parseRegistryName(registryName);
+        if (!parsed) {
+            return false;
+        }
+ 
+        const item = this.registryService.lookup(parsed.section, parsed.list, id);
+        if (!item) {
+            return false;
+        }
+ 
+        this.registryService.getRegistry(parsed.section, parsed.list).removeObject(item);
+        return true;
+    }
+ 
+    // ========================================================================
+    // Reading
+    // ========================================================================
+ 
+    /**
+     * The items registered into a slot, in registration order.
+     *
+     * @param {String} registryName
+     * @returns {Array}
+     */
+    get(registryName) {
+        const parsed = parseRegistryName(registryName);
+        return parsed ? this.registryService.getRegistry(parsed.section, parsed.list) : [];
+    }
+ 
+    /**
+     * Merge a slot's registered items into a view's built-in items. See
+     * `utils/merge-registered-items` for placement and handler binding.
+     *
+     * @param {String} registryName
+     * @param {Array} baseItems
+     * @param {Object} context Extra view context (`resource`, `controller`, `table`, …)
+     * @returns {Array}
+     */
+    merge(registryName, baseItems = [], context = {}) {
+        const parsed = parseRegistryName(registryName);
+        if (!parsed) {
+            return toPlainArray(baseItems);
+        }
+ 
+        const registered = this.get(registryName);
+        if (!registered.length) {
+            return toPlainArray(baseItems);
+        }
+ 
+        return mergeRegisteredItems(baseItems, registered, {
+            slot: parsed.slot,
+            context: this.#buildContext(parsed, context),
+            canAccess: (permission) => this.#can(permission),
+        });
+    }
+ 
+    /**
+     * Merge one slot of a surface, addressed by the `@registry` prefix a layout
+     * component receives (`fleet-ops:driver:table`) plus the slot name.
+     *
+     * @param {String} prefix
+     * @param {String} slot
+     * @param {Array} baseItems
+     * @param {Object} context
+     * @returns {Array}
+     */
+    mergeSlot(prefix, slot, baseItems = [], context = {}) {
+        const parsed = parseRegistryPrefix(prefix);
+        if (!parsed) {
+            return toPlainArray(baseItems);
+        }
+ 
+        return this.merge(`${prefix}:${slot}`, baseItems, context);
+    }
+ 
+    /**
+     * A table's row-actions column with registered row actions merged into its
+     * `actions`. Returns the column list unchanged when nothing is registered.
+     *
+     * @param {String} prefix A table prefix, e.g. `fleet-ops:driver:table`
+     * @param {Array} columns
+     * @param {Object} context
+     * @returns {Array}
+     */
+    mergeRowActions(prefix, columns = [], context = {}) {
+        const registryName = `${prefix}:row-actions`;
+        if (!parseRegistryName(registryName) || !this.get(registryName).length) {
+            return toPlainArray(columns);
+        }
+ 
+        return toPlainArray(columns).map((column) => {
+            if (column?.cellComponent !== 'table/cell/dropdown') {
+                return column;
+            }
+ 
+            return { ...column, actions: this.merge(registryName, column.actions ?? [], context) };
+        });
+    }
+ 
+    // ========================================================================
+    // Filters
+    // ========================================================================
+ 
+    /**
+     * The query params an index controller must declare: its own, plus the
+     * `filterParam` of every filterable registered column.
+     *
+     * Call it from the controller's `queryParams` class field. Filter columns
+     * must therefore be registered in `setupExtension`, before the controller
+     * is first created.
+     *
+     * @param {String} extension
+     * @param {String} resource
+     * @param {Array} baseQueryParams
+     * @returns {Array}
+     */
+    queryParamsFor(extension, resource, baseQueryParams = []) {
+        const registryName = buildRegistryName(extension, resource, 'table', 'columns');
+        this.#resolvedQueryParams.add(registryName);
+ 
+        const queryParams = [...baseQueryParams];
+        for (const param of this.get(registryName).map(filterParamOf).filter(Boolean)) {
+            if (!queryParams.includes(param)) {
+                queryParams.push(param);
+            }
+        }
+ 
+        return queryParams;
+    }
+ 
+    // ========================================================================
+    // Internals
+    // ========================================================================
+ 
+    #registerItem(parsed, item) {
+        // Tabs go to the legacy tab registry, through the menu service, which keys and
+        // wraps menu items as every other tab registration expects.
+        if (parsed.slot === 'tabs') {
+            if (!isObject(item)) {
+                warn(`[resource-view] Tabs registered into "${parsed.name}" must be MenuItems or menu item objects.`, false, { id: 'resource-view.invalid-tab' });
+                return false;
+            }
+ 
+            this.menuService.registerMenuItem(parsed.section, item);
+            return true;
+        }
+ 
+        const value = normalizeItem(item);
+ 
+        if (!value || !value.id) {
+            warn(`[resource-view] Items registered into "${parsed.name}" need an id.`, false, { id: 'resource-view.missing-id' });
+            return false;
+        }
+ 
+        if (value._contractType && value._contractType !== parsed.list) {
+            warn(`[resource-view] "${value.id}" is a ${value._contractType}, but "${parsed.name}" holds ${parsed.list} items.`, false, {
+                id: 'resource-view.wrong-contract',
+            });
+            return false;
+        }
+ 
+        if (parsed.slot === 'columns' && filterParamOf(value) && this.#resolvedQueryParams.has(parsed.name)) {
+            warn(
+                `[resource-view] The filter "${value.filterParam}" on "${value.id}" was registered after its table was set up, so it is not a query param. Register filter columns in setupExtension.`,
+                false,
+                { id: 'resource-view.late-filter' }
+            );
+        }
+ 
+        this.registryService.register(parsed.section, parsed.list, value.id, { ...value, _contractType: parsed.list });
+        return true;
+    }
+ 
+    #buildContext(parsed, context) {
+        const base = {
+            registry: parsed.name,
+            extension: parsed.extension,
+            surface: parsed.surface,
+            resourceName: parsed.resource,
+            slot: parsed.slot,
+            owner: getOwner(this),
+        };
+ 
+        // Copy descriptors rather than spreading: a view may pass lazy getters
+        // (its table is set up after first render), and a spread would read
+        // them now, during the render that merges.
+        return Object.defineProperties(base, Object.getOwnPropertyDescriptors(context));
+    }
+ 
+    #can(permission) {
+        const abilities = getOwner(this)?.lookup('service:abilities');
+        return !abilities || typeof abilities.can !== 'function' || abilities.can(permission);
+    }
+}
+ 
+function normalizeItem(item) {
+    if (item && typeof item.toObject === 'function') {
+        return item.toObject();
+    }
+ 
+    return isObject(item) ? { ...item } : null;
+}
+ 
+function filterParamOf(column) {
+    return column?.filterable && typeof column.filterParam === 'string' ? column.filterParam : null;
+}
+ 
+function toPlainArray(items) {
+    if (!items) {
+        return [];
+    }
+ 
+    return typeof items.toArray === 'function' ? items.toArray() : Array.from(items);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/widget-service.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/widget-service.js.html new file mode 100644 index 0000000..12a8471 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/widget-service.js.html @@ -0,0 +1,1201 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe/widget-service.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/universe widget-service.js

+
+ +
+ 0% + Statements + 0/64 +
+ + +
+ 0% + Branches + 0/48 +
+ + +
+ 0% + Functions + 0/24 +
+ + +
+ 0% + Lines + 0/62 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service from '@ember/service';
+import { inject as service } from '@ember/service';
+import { warn } from '@ember/debug';
+import { isArray } from '@ember/array';
+import { tracked } from '@glimmer/tracking';
+import Widget from '../../contracts/widget';
+import isObject from '../../utils/is-object';
+ 
+/**
+ * WidgetManagerService
+ *
+ * Manages dashboard widgets and widget registrations.
+ *
+ * Widgets are registered per-dashboard:
+ * - registerWidgets(dashboardName, widgets) - Makes widgets available for selection on a dashboard
+ * - registerDefaultWidgets(dashboardName, widgets) - Auto-loads specific widgets on a dashboard
+ *
+ * Registry Structure:
+ * - Dashboards: 'dashboards' section, 'dashboard' list
+ * - Widgets: 'dashboard:widgets' section, 'widget' list
+ * - Default Widgets: 'dashboard:widgets' section, 'default-widget' list
+ *
+ * @class WidgetService
+ * @extends Service
+ */
+export default class WidgetService extends Service {
+    @service('universe/registry-service') registry;
+ 
+    /**
+     * Reference to the root Ember Application Instance.
+     * Used for registering components/services to the application container
+     * for cross-engine sharing.
+     */
+    @tracked applicationInstance = null;
+ 
+    /**
+     * Set the application instance (for consistency with other services)
+     *
+     * @method setApplicationInstance
+     * @param {Application} application The root application instance
+     */
+    setApplicationInstance(application) {
+        this.applicationInstance = application;
+    }
+ 
+    /**
+     * Normalize a widget input to a plain object
+     *
+     * @private
+     * @method #normalizeWidget
+     * @param {Widget|Object} input Widget instance or object
+     * @returns {Object} Normalized widget object
+     */
+    #normalizeWidget(input) {
+        if (input instanceof Widget) {
+            return input.toObject();
+        }
+ 
+        // Handle plain objects - ensure id property exists
+        if (isObject(input)) {
+            // Support both id and widgetId for backward compatibility
+            const id = input.id || input.widgetId;
+ 
+            if (!id) {
+                warn('[WidgetService] Widget definition is missing id or widgetId', { id: 'widget-service.missing-id' });
+            }
+ 
+            return {
+                ...input,
+                id, // Ensure id property is set
+            };
+        }
+ 
+        return input;
+    }
+ 
+    /**
+     * Register a dashboard
+     *
+     * @method registerDashboard
+     * @param {String} name Dashboard name/ID
+     * @param {Object} options Dashboard options
+     */
+    registerDashboard(name, options = {}) {
+        const dashboard = {
+            name,
+            ...options,
+        };
+ 
+        // Register to 'dashboards' section, 'dashboard' list
+        this.registry.register('dashboards', 'dashboard', name, dashboard);
+    }
+ 
+    /**
+     * Register a dashboard render slot.
+     *
+     * Slots describe where dashboards render (for example `console.home`),
+     * while dashboard names continue to describe widget namespaces.
+     *
+     * @method registerDashboardSlot
+     * @param {String} slotId Slot identifier
+     * @param {Object} options Slot options
+     */
+    registerDashboardSlot(slotId, options = {}) {
+        const slot = {
+            id: slotId,
+            ...options,
+        };
+ 
+        this.registry.register('dashboard:slots', 'slot', slotId, slot);
+    }
+ 
+    /**
+     * Register a system dashboard that should be available in a render slot.
+     *
+     * @method registerDashboardForSlot
+     * @param {String} slotId Slot identifier
+     * @param {String} dashboardName Dashboard widget namespace
+     * @param {Object} options Dashboard slot options
+     */
+    registerDashboardForSlot(slotId, dashboardName, options = {}) {
+        this.registerDashboardSlot(slotId);
+        this.registerDashboard(dashboardName, options);
+ 
+        const dashboard = {
+            id: dashboardName,
+            dashboardId: dashboardName,
+            name: options.name ?? options.defaultDashboardName ?? dashboardName,
+            extension: options.extension ?? 'core',
+            priority: options.priority ?? 0,
+            ...options,
+        };
+ 
+        this.registry.register('dashboard:slots', 'dashboard', `${slotId}#${dashboardName}`, {
+            slotId,
+            ...dashboard,
+        });
+    }
+ 
+    /**
+     * Set the system dashboard that should load first for a render slot.
+     *
+     * @method setDefaultDashboardForSlot
+     * @param {String} slotId Slot identifier
+     * @param {String} dashboardName Dashboard widget namespace
+     */
+    setDefaultDashboardForSlot(slotId, dashboardName) {
+        this.registerDashboardSlot(slotId);
+        this.registry.register('dashboard:slots', 'default', slotId, {
+            slotId,
+            dashboardId: dashboardName,
+        });
+    }
+ 
+    /**
+     * Convenience alias for setting the console home dashboard.
+     *
+     * @method setConsoleDashboard
+     * @param {String} dashboardName Dashboard widget namespace
+     */
+    setConsoleDashboard(dashboardName) {
+        this.setDefaultDashboardForSlot('console.home', dashboardName);
+    }
+ 
+    /**
+     * Get system dashboards registered for a slot.
+     *
+     * @method getDashboardsForSlot
+     * @param {String} slotId Slot identifier
+     * @returns {Array} Slot dashboard registrations
+     */
+    getDashboardsForSlot(slotId) {
+        if (!slotId) {
+            return [];
+        }
+ 
+        const prefix = `${slotId}#`;
+        return [...this.registry.getRegistry('dashboard:slots', 'dashboard')]
+            .filter((dashboard) => dashboard?._registryKey?.startsWith(prefix))
+            .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
+    }
+ 
+    /**
+     * Get the dashboard namespace that should load first for a slot.
+     *
+     * @method getDefaultDashboardForSlot
+     * @param {String} slotId Slot identifier
+     * @returns {String|null} Dashboard namespace
+     */
+    getDefaultDashboardForSlot(slotId) {
+        return this.registry.lookup('dashboard:slots', 'default', slotId)?.dashboardId ?? null;
+    }
+ 
+    /**
+     * Register widgets to a specific dashboard
+     * Makes these widgets available for selection on the dashboard
+     * If a widget has `default: true`, it's also registered as a default widget
+     *
+     * @method registerWidgets
+     * @param {String} dashboardName Dashboard name/ID
+     * @param {Array<Widget>} widgets Array of widget instances or objects
+     */
+    registerWidgets(dashboardName, widgets) {
+        if (!isArray(widgets)) {
+            widgets = [widgets];
+        }
+ 
+        widgets.forEach((widget) => {
+            const normalized = this.#normalizeWidget(widget);
+ 
+            // Register widget to 'dashboard:widgets' section, 'widget' list
+            // Key format: dashboardName#widgetId
+            this.registry.register('dashboard:widgets', 'widget', `${dashboardName}#${normalized.id}`, normalized);
+ 
+            // If marked as default, also register to default widget list
+            if (normalized.default === true) {
+                this.registry.register('dashboard:widgets', 'default-widget', `${dashboardName}#${normalized.id}`, normalized);
+            }
+        });
+    }
+ 
+    /**
+     * Register default widgets for a specific dashboard
+     * These widgets are automatically loaded on the dashboard
+     *
+     * @method registerDefaultWidgets
+     * @param {String} dashboardName Dashboard name/ID
+     * @param {Array<Widget>} widgets Array of widget instances or objects
+     */
+    registerDefaultWidgets(dashboardName, widgets) {
+        if (!isArray(widgets)) {
+            widgets = [widgets];
+        }
+ 
+        widgets.forEach((widget) => {
+            const normalized = this.#normalizeWidget(widget);
+ 
+            // Register to 'dashboard:widgets' section, 'default-widget' list
+            // Key format: dashboardName#widgetId
+            this.registry.register('dashboard:widgets', 'default-widget', `${dashboardName}#${normalized.id}`, normalized);
+        });
+    }
+ 
+    /**
+     * Get widgets for a specific dashboard
+     * Returns all widgets available for selection on that dashboard
+     *
+     * @method getWidgets
+     * @param {String} dashboardName Dashboard name/ID
+     * @returns {Array} Widgets available for the dashboard
+     */
+    getWidgets(dashboardName) {
+        if (!dashboardName) {
+            return [];
+        }
+ 
+        // Get all widgets from 'dashboard:widgets' section, 'widget' list
+        const registry = this.registry.getRegistry('dashboard:widgets', 'widget');
+ 
+        // Filter widgets by registration key prefix
+        const prefix = `${dashboardName}#`;
+ 
+        return registry.filter((widget) => {
+            if (!widget || typeof widget !== 'object') return false;
+ 
+            // Match widgets registered for this dashboard
+            return widget._registryKey && widget._registryKey.startsWith(prefix);
+        });
+    }
+ 
+    /**
+     * Get default widgets for a specific dashboard
+     * Returns widgets that should be auto-loaded
+     *
+     * @method getDefaultWidgets
+     * @param {String} dashboardName Dashboard name/ID
+     * @returns {Array} Default widgets for the dashboard
+     */
+    getDefaultWidgets(dashboardName) {
+        if (!dashboardName) {
+            return [];
+        }
+ 
+        // Get all default widgets from 'dashboard:widgets' section, 'default-widget' list
+        const registry = this.registry.getRegistry('dashboard:widgets', 'default-widget');
+ 
+        // Filter widgets by registration key prefix
+        const prefix = `${dashboardName}#`;
+ 
+        return registry.filter((widget) => {
+            if (!widget || typeof widget !== 'object') return false;
+ 
+            // Match default widgets registered for this dashboard
+            return widget._registryKey && widget._registryKey.startsWith(prefix);
+        });
+    }
+ 
+    /**
+     * Get a specific widget by ID from a dashboard
+     *
+     * @method getWidget
+     * @param {String} dashboardName Dashboard name/ID
+     * @param {String} widgetId Widget ID
+     * @returns {Object|null} Widget or null
+     */
+    getWidget(dashboardName, widgetId) {
+        return this.registry.lookup('dashboard:widgets', 'widget', `${dashboardName}#${widgetId}`);
+    }
+ 
+    /**
+     * Get all dashboards
+     *
+     * @method getDashboards
+     * @returns {Array} All dashboards
+     */
+    getDashboards() {
+        return this.registry.getRegistry('dashboards', 'dashboard');
+    }
+ 
+    /**
+     * Get a specific dashboard
+     *
+     * @method getDashboard
+     * @param {String} name Dashboard name
+     * @returns {Object|null} Dashboard or null
+     */
+    getDashboard(name) {
+        return this.registry.lookup('dashboards', 'dashboard', name);
+    }
+ 
+    /**
+     * Get registry for a specific dashboard
+     * Used by dashboard models to get their widget registry
+     *
+     * @method getRegistry
+     * @param {String} dashboardId Dashboard ID
+     * @returns {Array} Widget registry for the dashboard
+     */
+    getRegistry(dashboardId) {
+        return this.getWidgets(dashboardId);
+    }
+ 
+    // ============================================================================
+    // DEPRECATED METHODS (for backward compatibility)
+    // ============================================================================
+ 
+    /**
+     * Register default dashboard widgets
+     * DEPRECATED: Use registerDefaultWidgets(dashboardName, widgets) instead
+     *
+     * @method registerDefaultDashboardWidgets
+     * @param {Array<Widget>} widgets Array of widget instances or objects
+     * @deprecated Use registerDefaultWidgets('dashboard', widgets) instead
+     */
+    registerDefaultDashboardWidgets(widgets) {
+        warn('[WidgetService] registerDefaultDashboardWidgets is deprecated. Use registerDefaultWidgets(dashboardName, widgets) instead.', { id: 'widget-service.deprecated-method' });
+        this.registerDefaultWidgets('dashboard', widgets);
+    }
+ 
+    /**
+     * Register dashboard widgets
+     * DEPRECATED: Use registerWidgets(dashboardName, widgets) instead
+     *
+     * @method registerDashboardWidgets
+     * @param {Array<Widget>} widgets Array of widget instances or objects
+     * @deprecated Use registerWidgets('dashboard', widgets) instead
+     */
+    registerDashboardWidgets(widgets) {
+        warn('[WidgetService] registerDashboardWidgets is deprecated. Use registerWidgets(dashboardName, widgets) instead.', { id: 'widget-service.deprecated-method' });
+        this.registerWidgets('dashboard', widgets);
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/url-search-params.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/url-search-params.js.html new file mode 100644 index 0000000..f2216e3 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/url-search-params.js.html @@ -0,0 +1,919 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services/url-search-params.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/services url-search-params.js

+
+ +
+ 0% + Statements + 0/52 +
+ + +
+ 0% + Branches + 0/9 +
+ + +
+ 0% + Functions + 0/20 +
+ + +
+ 0% + Lines + 0/52 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Service from '@ember/service';
+import { debounce } from '@ember/runloop';
+import hasJsonStructure from '../utils/has-json-structure';
+ 
+/**
+ * Service for manipulating URL search parameters.
+ *
+ * This service provides methods to get, set, remove, and check URL query parameters.
+ * It also allows updating the browser's URL without reloading the page.
+ *
+ * @extends Service
+ */
+export default class UrlSearchParamsService extends Service {
+    /**
+     * The working set of query parameters.
+     *
+     * Rebuilt from `window.location.search` whenever the browser's search string
+     * differs from the one these params were built from, so a navigation is
+     * picked up; otherwise the same instance is handed back, so `setParam`,
+     * `addParam` and `removeParam` actually persist until `updateUrl` publishes
+     * them.
+     *
+     * @type {URLSearchParams}
+     * @private
+     */
+    #params = null;
+    #builtFrom = null;
+ 
+    get urlParams() {
+        return this.#currentParams();
+    }
+ 
+    /**
+     * Rebuild-if-stale, kept out of the getter body so a property read is not
+     * itself an assignment.
+     *
+     * @private
+     * @returns {URLSearchParams}
+     */
+    #currentParams() {
+        const search = window.location.search;
+ 
+        if (this.#params === null || this.#builtFrom !== search) {
+            this.#params = new URLSearchParams(search);
+            this.#builtFrom = search;
+        }
+ 
+        return this.#params;
+    }
+ 
+    set urlParams(value) {
+        this.#params = value;
+        this.#builtFrom = window.location.search;
+    }
+ 
+    /**
+     * Retrieves the value of a specific query parameter.
+     *
+     * If the parameter value is a JSON string, it will be parsed into an object or array.
+     *
+     * @param {string} key - The name of the query parameter to retrieve.
+     * @returns {*} The value of the query parameter, parsed from JSON if applicable, or null if not found.
+     */
+    getParam(key) {
+        let value = this.urlParams.get(key);
+ 
+        if (hasJsonStructure(value)) {
+            value = JSON.parse(value);
+        }
+ 
+        return value;
+    }
+ 
+    /**
+     * Sets or updates a query parameter in the URL search parameters.
+     *
+     * If the value is an object or array, it will be stringified to JSON.
+     *
+     * @param {string} key - The name of the query parameter to set.
+     * @param {*} value - The value of the query parameter.
+     * @returns {this} Returns the service instance for chaining.
+     */
+    setParam(key, value) {
+        if (typeof value === 'object') {
+            value = JSON.stringify(value);
+        } else {
+            value = encodeURIComponent(value);
+        }
+ 
+        this.urlParams.set(key, value);
+ 
+        return this;
+    }
+ 
+    /**
+     * Alias for `getParam`.
+     *
+     * @param {string} key - The name of the query parameter to retrieve.
+     * @returns {*} The value of the query parameter.
+     */
+    get(key) {
+        return this.getParam(key);
+    }
+ 
+    /**
+     * Sets or updates a query parameter with multiple values.
+     *
+     * @param {string} key - The name of the query parameter to set.
+     * @param {Array} values - An array of values for the parameter.
+     * @returns {this} Returns the service instance for chaining.
+     */
+    setParamArray(key, values) {
+        this.urlParams.delete(key);
+        values.forEach((value) => {
+            this.urlParams.append(key, value);
+        });
+ 
+        return this;
+    }
+ 
+    /**
+     * Retrieves all values of a specific query parameter.
+     *
+     * @param {string} key - The name of the query parameter.
+     * @returns {Array} An array of values for the parameter.
+     */
+    getParamArray(key) {
+        return this.urlParams.getAll(key);
+    }
+ 
+    /**
+     * Checks if a specific query parameter exists in the URL.
+     *
+     * @param {string} key - The name of the query parameter to check.
+     * @returns {boolean} True if the parameter exists, false otherwise.
+     */
+    exists(key) {
+        return this.urlParams.has(key);
+    }
+ 
+    /**
+     * Checks if a specific query parameter has in the URL.
+     *
+     * @param {string} key - The name of the query parameter to check.
+     * @returns {boolean} True if the parameter exists, false otherwise.
+     */
+    has(key) {
+        return this.urlParams.has(key);
+    }
+ 
+    /**
+     * Removes a specific query parameter from the URL search parameters.
+     *
+     * @param {string} key - The name of the query parameter to remove.
+     * @returns {this} Returns the service instance for chaining.
+     */
+    remove(key) {
+        this.urlParams.delete(key);
+ 
+        return this;
+    }
+ 
+    /**
+     * Retrieves all query parameters as an object.
+     *
+     * Each parameter value is processed by `getParam`, which parses JSON values if applicable.
+     *
+     * @returns {Object} An object containing all query parameters and their values.
+     */
+    all() {
+        const all = {};
+ 
+        for (let key of this.urlParams.keys()) {
+            all[key] = this.getParam(key);
+        }
+ 
+        return all;
+    }
+ 
+    /**
+     * Updates the browser's URL with the current `urlParams` without reloading the page.
+     *
+     * @returns {void}
+     */
+    updateUrl() {
+        const url = new URL(window.location.href);
+        url.search = this.urlParams.toString();
+        window.history.pushState({ path: url.href }, '', url.href);
+    }
+ 
+    /**
+     * Updates the browser's URL with the current `urlParams`, debounced to prevent excessive calls.
+     *
+     * @returns {void}
+     */
+    updateUrlDebounced() {
+        debounce(this, this.updateUrl, 100);
+    }
+ 
+    /**
+     * Clears all query parameters from the URL search parameters.
+     *
+     * @returns {this} Returns the service instance for chaining.
+     */
+    clear() {
+        this.urlParams = new URLSearchParams();
+ 
+        return this;
+    }
+ 
+    /**
+     * Returns the full URL as a string with the current `urlParams`.
+     *
+     * @returns {string} The full URL with updated query parameters.
+     */
+    getFullUrl() {
+        const url = new URL(window.location.href);
+        url.search = this.urlParams.toString();
+ 
+        return url.toString();
+    }
+ 
+    /**
+     * Returns the current path with the updated query parameters.
+     *
+     * @returns {string} The path and search portion of the URL.
+     */
+    getPathWithParams() {
+        return `${window.location.pathname}?${this.urlParams.toString()}`;
+    }
+ 
+    /**
+     * Removes a query parameter from the current URL and updates the browser history.
+     *
+     * This method modifies the browser's URL by removing the specified parameter and uses the History API
+     * to update the URL without reloading the page.
+     *
+     * @param {string} paramToRemove - The name of the query parameter to remove from the URL.
+     * @returns {void}
+     */
+    removeParamFromCurrentUrl(paramToRemove) {
+        const url = new URL(window.location.href);
+        url.searchParams.delete(paramToRemove);
+        window.history.pushState({ path: url.href }, '', url.href);
+    }
+ 
+    /**
+     * Adds or updates a query parameter in the current URL and updates the browser history.
+     *
+     * This method modifies the browser's URL by adding or updating the specified parameter and uses the History API
+     * to update the URL without reloading the page.
+     *
+     * @param {string} paramName - The name of the query parameter to add or update.
+     * @param {string} paramValue - The value of the query parameter.
+     * @returns {void}
+     */
+    addParamToCurrentUrl(paramName, paramValue) {
+        const url = new URL(window.location.href);
+        url.searchParams.set(paramName, paramValue);
+        window.history.pushState({ path: url.href }, '', url.href);
+    }
+ 
+    /**
+     * Adds or updates a query parameters from a provided object into the current URL and updates the browser history.
+     *
+     * This method modifies the browser's URL by adding or updating the specified parameters and uses the History API
+     * to update the URL without reloading the page.
+     *
+     * @param {Object} params - The query parameters to add or update.
+     * @returns {void}
+     */
+    setParamsToCurrentUrl(params = {}) {
+        for (let param in params) {
+            const value = params[param];
+            this.addParamToCurrentUrl(param, value);
+        }
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/array.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/array.js.html new file mode 100644 index 0000000..b8cd5f9 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/array.js.html @@ -0,0 +1,172 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/array.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms array.js

+
+ +
+ 30% + Statements + 3/10 +
+ + +
+ 25% + Branches + 2/8 +
+ + +
+ 50% + Functions + 1/2 +
+ + +
+ 30% + Lines + 3/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +  +  +  +3x +3x +  +  +  +  +  + 
import Transform from '@ember-data/serializer/transform';
+import isIterable from '../utils/is-iterable';
+import { isArray } from '@ember/array';
+ 
+export default class ArrayTransform extends Transform {
+    deserialize(serialized) {
+        if (isArray(serialized)) {
+            return serialized;
+        }
+ 
+        if (!isIterable(serialized)) {
+            return [];
+        }
+ 
+        return Array.from(serialized);
+    }
+ 
+    serialize(deserialized) {
+        Iif (isArray(deserialized)) {
+            return deserialized;
+        }
+ 
+        Eif (!isIterable(deserialized)) {
+            return [];
+        }
+ 
+        return Array.from(deserialized);
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/index.html new file mode 100644 index 0000000..ec08e4b --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms

+
+ +
+ 42.85% + Statements + 6/14 +
+ + +
+ 25% + Branches + 2/8 +
+ + +
+ 66.66% + Functions + 4/6 +
+ + +
+ 42.85% + Lines + 6/14 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
array.js +
+
30%3/1025%2/850%1/230%3/10
object.js +
+
50%1/2100%0/050%1/250%1/2
raw.js +
+
100%2/2100%0/0100%2/2100%2/2
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/object.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/object.js.html new file mode 100644 index 0000000..e1a30a5 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/object.js.html @@ -0,0 +1,118 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/object.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms object.js

+
+ +
+ 50% + Statements + 1/2 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 50% + Functions + 1/2 +
+ + +
+ 50% + Lines + 1/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12  +  +  +  +  +  +  +  +39x +  +  + 
import Transform from '@ember-data/serializer/transform';
+ 
+export default class ObjectTransform extends Transform {
+    deserialize(serialized) {
+        return Object.assign({}, serialized);
+    }
+ 
+    serialize(deserialized) {
+        return Object.assign({}, deserialized);
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/raw.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/raw.js.html new file mode 100644 index 0000000..80489b9 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/raw.js.html @@ -0,0 +1,118 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms/raw.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/transforms raw.js

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12  +  +  +  +4x +  +  +  +148x +  +  + 
import Transform from '@ember-data/serializer/transform';
+ 
+export default class RawTransform extends Transform {
+    deserialize(serialized) {
+        return serialized;
+    }
+ 
+    serialize(deserialized) {
+        return deserialized;
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/corslite.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/corslite.js.html new file mode 100644 index 0000000..54039b6 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/corslite.js.html @@ -0,0 +1,385 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/corslite.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils corslite.js

+
+ +
+ 0% + Statements + 0/37 +
+ + +
+ 0% + Branches + 0/23 +
+ + +
+ 0% + Functions + 0/13 +
+ + +
+ 0% + Lines + 0/37 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { later } from '@ember/runloop';
+ 
+function corslite(url, callback, cors) {
+    var sent = false;
+ 
+    if (typeof window.XMLHttpRequest === 'undefined') {
+        return callback(Error('Browser not supported'));
+    }
+ 
+    if (typeof cors === 'undefined') {
+        var m = url.match(/^\s*https?:\/\/[^\/]*/);
+        /* istanbul ignore next -- the test server always runs on an explicit port, so the portless arm of the ternary is unreachable here */
+        cors = m && m[0] !== location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
+    }
+ 
+    var x = new window.XMLHttpRequest();
+ 
+    function isSuccessful(status) {
+        return (status >= 200 && status < 300) || status === 304;
+    }
+ 
+    if (cors && !('withCredentials' in x)) {
+        // IE8-9
+        x = new window.XDomainRequest();
+ 
+        // Ensure callback is never called synchronously, i.e., before
+        // x.send() returns (this has been observed in the wild).
+        // See https://github.com/mapbox/mapbox.js/issues/472
+        var original = callback;
+        callback = function () {
+            if (sent) {
+                original.apply(this, arguments);
+            } else {
+                later(
+                    this,
+                    function () {
+                        original.apply(this, arguments);
+                    },
+                    0
+                );
+            }
+        };
+    }
+ 
+    function loaded() {
+        if (
+            // XDomainRequest
+            x.status === undefined ||
+            // modern browsers
+            isSuccessful(x.status)
+        )
+            callback.call(x, null, x);
+        else callback.call(x, x, null);
+    }
+ 
+    // Both `onreadystatechange` and `onload` can fire. `onreadystatechange`
+    // has [been supported for longer](http://stackoverflow.com/a/9181508/229001).
+    if ('onload' in x) {
+        x.onload = loaded;
+    } else {
+        x.onreadystatechange = function readystate() {
+            if (x.readyState === 4) {
+                loaded();
+            }
+        };
+    }
+ 
+    // Call the callback with the XMLHttpRequest object as an error and prevent
+    // it from ever being called again by reassigning it to `noop`
+    x.onerror = function error(evt) {
+        // XDomainRequest provides no evt parameter
+        callback.call(this, evt || true, null);
+        callback = function () {};
+    };
+ 
+    // IE9 must have onprogress be set to a unique function.
+    x.onprogress = function () {};
+ 
+    x.ontimeout = function (evt) {
+        callback.call(this, evt, null);
+        callback = function () {};
+    };
+ 
+    x.onabort = function (evt) {
+        callback.call(this, evt, null);
+        callback = function () {};
+    };
+ 
+    // GET is the only supported HTTP Verb by XDomainRequest and is the
+    // only one supported here.
+    x.open('GET', url, true);
+ 
+    // Send the request. Sending data is not supported.
+    x.send(null);
+    sent = true;
+ 
+    return x;
+}
+ 
+export default corslite;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/download.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/download.js.html new file mode 100644 index 0000000..3fe542b --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/download.js.html @@ -0,0 +1,151 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/download.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils download.js

+
+ +
+ 0% + Statements + 0/89 +
+ + +
+ 0% + Branches + 0/55 +
+ + +
+ 0% + Functions + 0/10 +
+ + +
+ 0% + Lines + 0/86 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +230 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0
Cannot read properties of undefined (reading 'start')
+TypeError: Cannot read properties of undefined (reading 'start')
+    at /Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-reports@3.2.0/node_modules/istanbul-reports/lib/html/annotator.js:194:41
+    at Array.forEach ()
+    at annotateBranches (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-reports@3.2.0/node_modules/istanbul-reports/lib/html/annotator.js:128:33)
+    at annotateSourceCode (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-reports@3.2.0/node_modules/istanbul-reports/lib/html/annotator.js:269:9)
+    at HtmlReport.onDetail (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-reports@3.2.0/node_modules/istanbul-reports/lib/html/index.js:414:33)
+    at LcovReport. [as onDetail] (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-reports@3.2.0/node_modules/istanbul-reports/lib/lcov/index.js:28:23)
+    at Visitor.value (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/tree.js:38:38)
+    at ReportNode.visit (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/tree.js:88:21)
+    at /Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/tree.js:92:19
+    at Array.forEach ()
+    at ReportNode.visit (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/tree.js:91:28)
+    at /Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/tree.js:92:19
+    at Array.forEach ()
+    at ReportNode.visit (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/tree.js:91:28)
+    at ReportTree.visit (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/tree.js:127:24)
+    at LcovReport.execute (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report/lib/report-base.js:12:44)
+    at /Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/ember-cli-code-coverage@3.1.0/node_modules/ember-cli-code-coverage/lib/attach-middleware.js:226:12
+    at Array.forEach ()
+    at reportCoverage (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/ember-cli-code-coverage@3.1.0/node_modules/ember-cli-code-coverage/lib/attach-middleware.js:222:13)
+    at coverageHandler (/Users/ron/Development/fleetbase/oss/fleetbase-dev/console/node_modules/.pnpm/ember-cli-code-coverage@3.1.0/node_modules/ember-cli-code-coverage/lib/attach-middleware.js:232:3)
+    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-mime-type.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-mime-type.js.html new file mode 100644 index 0000000..ff4c83b --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-mime-type.js.html @@ -0,0 +1,154 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-mime-type.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils get-mime-type.js

+
+ +
+ 33.33% + Statements + 1/3 +
+ + +
+ 0% + Branches + 0/2 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 33.33% + Lines + 1/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +241x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
const MIME_TYPES = {
+    pdf: 'application/pdf',
+    zip: 'application/zip',
+    doc: 'application/msword',
+    xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+    docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+    png: 'image/png',
+    jpeg: 'image/jpeg',
+    jpg: 'image/jpeg',
+    csv: 'text/csv',
+};
+ 
+/**
+ * The mime type for a file name, from its extension, or null when it is not one we know.
+ *
+ * @param {String} fileName
+ * @returns {String|null}
+ */
+export default function getMimeType(fileName) {
+    const extension = String(fileName).split('.').pop().toLowerCase();
+ 
+    return MIME_TYPES[extension] ?? null;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-user-options.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-user-options.js.html new file mode 100644 index 0000000..925eab6 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-user-options.js.html @@ -0,0 +1,148 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-user-options.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils get-user-options.js

+
+ +
+ 80% + Statements + 4/5 +
+ + +
+ 50% + Branches + 1/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 80% + Lines + 4/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22  +  +  +  +  +  +  +  +  +  +86x +  +86x +86x +86x +  +  +  +  +  +  + 
/**
+ * Retrieves user options from local storage.
+ *
+ * Attempts to read the user options stored in local storage as a JSON string under the key
+ * USER_OPTIONS_STORAGE_KEY. If the JSON parsing fails, an empty object is returned.
+ *
+ * @returns {Object} The user options as an object, or an empty object if no valid user options were found.
+ * @throws Will not throw an error, even if JSON parsing fails. Errors are silently caught and ignored.
+ */
+export default function getUserOptions() {
+    const USER_OPTIONS_STORAGE_KEY = '@fleetbase/storage:user-options';
+ 
+    try {
+        const storedValue = window.localStorage.getItem(USER_OPTIONS_STORAGE_KEY);
+        return storedValue ? JSON.parse(storedValue) : {};
+    } catch (error) {
+        // For debugging purposes, you can log the error (optional)
+        // console.error('Failed to parse user options from local storage:', error);
+        return {};
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-with-default.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-with-default.js.html new file mode 100644 index 0000000..7a9e391 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-with-default.js.html @@ -0,0 +1,103 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/get-with-default.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils get-with-default.js

+
+ +
+ 0% + Statements + 0/2 +
+ + +
+ 0% + Branches + 0/2 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7  +  +  +  +  +  + 
import { get } from '@ember/object';
+ 
+export default function getWithDefault(obj, key, defaultValue) {
+    let value = get(obj, key);
+    return value === undefined ? defaultValue : value;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/has-json-structure.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/has-json-structure.js.html new file mode 100644 index 0000000..27af56e --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/has-json-structure.js.html @@ -0,0 +1,115 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/has-json-structure.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils has-json-structure.js

+
+ +
+ 0% + Statements + 0/7 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11  +  +  +  +  +  +  +  +  +  + 
export default function hasJsonStructure(str) {
+    if (typeof str !== 'string') return false;
+    try {
+        const result = JSON.parse(str);
+        const type = Object.prototype.toString.call(result);
+        return type === '[object Object]' || type === '[object Array]';
+    } catch (err) {
+        return false;
+    }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/index.html new file mode 100644 index 0000000..4afb4c5 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/index.html @@ -0,0 +1,356 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils

+
+ +
+ 6.45% + Statements + 22/341 +
+ + +
+ 6.61% + Branches + 17/257 +
+ + +
+ 9.33% + Functions + 7/75 +
+ + +
+ 6.81% + Lines + 22/323 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
corslite.js +
+
0%0/370%0/230%0/130%0/37
download.js +
+
0%0/890%0/550%0/100%0/86
get-mime-type.js +
+
33.33%1/30%0/20%0/133.33%1/3
get-user-options.js +
+
80%4/550%1/2100%1/180%4/5
get-with-default.js +
+
0%0/20%0/20%0/10%0/2
has-json-structure.js +
+
0%0/70%0/40%0/10%0/6
is-empty-object.js +
+
0%0/30%0/40%0/10%0/2
is-iterable.js +
+
66.66%2/350%1/2100%1/166.66%2/3
is-latitude.js +
+
100%1/1100%2/2100%1/1100%1/1
is-longitude.js +
+
100%1/1100%2/2100%1/1100%1/1
is-model.js +
+
100%1/1100%2/2100%1/1100%1/1
is-not-model.js +
+
100%1/1100%0/0100%1/1100%1/1
is-object.js +
+
0%0/10%0/30%0/10%0/1
is-valid-coordinates.js +
+
60%6/1050%9/18100%1/160%6/10
lookup-user-ip.js +
+
0%0/550%0/320%0/100%0/54
merge-registered-items.js +
+
0%0/950%0/920%0/230%0/85
resource-view-registry.js +
+
18.51%5/270%0/120%0/720%5/25
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-empty-object.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-empty-object.js.html new file mode 100644 index 0000000..1971fe1 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-empty-object.js.html @@ -0,0 +1,103 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-empty-object.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-empty-object.js

+
+ +
+ 0% + Statements + 0/3 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7  +  +  +  +  +  + 
import { isBlank } from '@ember/utils';
+ 
+export default function isEmptyObject(obj) {
+    if (isBlank(obj)) return true;
+    return obj.constructor === Object && Object.keys(obj).length === 0;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-iterable.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-iterable.js.html new file mode 100644 index 0000000..73cb358 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-iterable.js.html @@ -0,0 +1,106 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-iterable.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-iterable.js

+
+ +
+ 66.66% + Statements + 2/3 +
+ + +
+ 50% + Branches + 1/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 66.66% + Lines + 2/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8  +3x +3x +  +  +  +  + 
export default function isIterable(obj) {
+    Eif (obj == null) {
+        return false;
+    }
+ 
+    return typeof obj[Symbol.iterator] === 'function';
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-latitude.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-latitude.js.html new file mode 100644 index 0000000..0d7d702 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-latitude.js.html @@ -0,0 +1,94 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-latitude.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-latitude.js

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4  +21x +  + 
export default function isLatitude(coordinate) {
+    return isFinite(coordinate) && Math.abs(coordinate) <= 90;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-longitude.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-longitude.js.html new file mode 100644 index 0000000..b491f00 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-longitude.js.html @@ -0,0 +1,94 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-longitude.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-longitude.js

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4  +16x +  + 
export default function isLongitude(coordinate) {
+    return isFinite(coordinate) && Math.abs(coordinate) <= 180;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-model.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-model.js.html new file mode 100644 index 0000000..e8c5a5e --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-model.js.html @@ -0,0 +1,103 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-model.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-model.js

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7  +  +  +  +7x +  + 
import Model from '@ember-data/model';
+import ObjectProxy from '@ember/object/proxy';
+ 
+export default function isModel(mixed) {
+    return mixed instanceof Model || mixed instanceof ObjectProxy;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-not-model.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-not-model.js.html new file mode 100644 index 0000000..c434eb4 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-not-model.js.html @@ -0,0 +1,100 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-not-model.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-not-model.js

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6  +  +  +7x +  + 
import isModel from './is-model';
+ 
+export default function isNotModel(model) {
+    return !isModel(model);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-object.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-object.js.html new file mode 100644 index 0000000..34f8ab6 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-object.js.html @@ -0,0 +1,94 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-object.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-object.js

+
+ +
+ 0% + Statements + 0/1 +
+ + +
+ 0% + Branches + 0/3 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4  +  +  + 
export default function isObject(obj) {
+    return obj && typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Object]';
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-valid-coordinates.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-valid-coordinates.js.html new file mode 100644 index 0000000..a285804 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-valid-coordinates.js.html @@ -0,0 +1,199 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/is-valid-coordinates.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils is-valid-coordinates.js

+
+ +
+ 60% + Statements + 6/10 +
+ + +
+ 50% + Branches + 9/18 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 60% + Lines + 6/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +21x +21x +21x +  +  +21x +  +  +  +  +21x +  +  +  +  +21x +  + 
import { isArray } from '@ember/array';
+import isLatitude from './is-latitude';
+import isLongitude from './is-longitude';
+import isObject from './is-object';
+ 
+/**
+ * Validates coordinates
+ *
+ * ```
+ * isValidCoordinates([0, 0]);
+ * isValidCoordinates({lat: 0, lng: 0});
+ * isValidCoordinates({latitude: 0, longitude: 0});
+ * isValidCoordinates(0, 0);
+ * ```
+ *
+ * @param {Array|Object|Integer} latitude
+ * @param {Array|Object|Integer} longitude
+ */
+export default function isValidCoordinates(latitude, longitude = null) {
+    let testLatitude, testLongitude;
+ 
+    Eif (isArray(latitude) && longitude === null) {
+        testLatitude = latitude[0];
+        testLongitude = latitude[1];
+    }
+ 
+    Iif (!isArray(latitude) && isObject(latitude) && longitude === null) {
+        testLatitude = latitude.lat || latitude.latitude;
+        testLongitude = latitude.lng || latitude.longitude;
+    }
+ 
+    Iif (longitude !== null) {
+        testLatitude = latitude;
+        testLongitude = longitude;
+    }
+ 
+    return isLatitude(testLatitude) && isLongitude(testLongitude);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/lookup-user-ip.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/lookup-user-ip.js.html new file mode 100644 index 0000000..9d87ccc --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/lookup-user-ip.js.html @@ -0,0 +1,817 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/lookup-user-ip.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils lookup-user-ip.js

+
+ +
+ 0% + Statements + 0/55 +
+ + +
+ 0% + Branches + 0/32 +
+ + +
+ 0% + Functions + 0/10 +
+ + +
+ 0% + Lines + 0/54 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Lookup user's IP address and geolocation information using geoiplookup.io
+ *
+ * This method calls the geoiplookup.io API directly from the browser to get
+ * accurate user location data, avoiding the issue of server-side IP lookup
+ * returning the server's location instead of the user's location.
+ *
+ * @param {Object} options - Configuration options
+ * @param {number} options.timeout - Request timeout in milliseconds (default: 5000)
+ * @param {boolean} options.cache - Whether to cache the result (default: true)
+ * @returns {Promise<Object>} User's IP and geolocation data
+ *
+ * @example
+ * const whois = await lookupUserIp();
+ * console.log(whois.city); // "New York"
+ * console.log(whois.country_code); // "US"
+ */
+export default async function lookupUserIp(options = {}) {
+    const { timeout = 5000, cache = true } = options;
+ 
+    // Check cache first if enabled
+    if (cache) {
+        const cached = getCachedWhois();
+        if (cached) {
+            return cached;
+        }
+    }
+ 
+    // Try multiple APIs with fallback
+    const apis = [
+        {
+            url: 'https://json.geoiplookup.io/',
+            normalize: normalizeGeoIPLookup,
+        },
+        {
+            url: 'https://ipapi.co/json/',
+            normalize: normalizeIPApi,
+        },
+    ];
+ 
+    for (const api of apis) {
+        try {
+            const controller = new AbortController();
+            const timeoutId = setTimeout(() => controller.abort(), timeout);
+ 
+            const response = await fetch(api.url, {
+                signal: controller.signal,
+                headers: {
+                    Accept: 'application/json',
+                },
+            });
+ 
+            clearTimeout(timeoutId);
+ 
+            if (!response.ok) {
+                console.warn(`[lookupUserIp] ${api.url} returned ${response.status}`);
+                continue;
+            }
+ 
+            const data = await response.json();
+            const normalized = api.normalize(data);
+            const normalizedWithTimezone = ensureWhoisTimezone(normalized);
+ 
+            // Cache the result if enabled
+            if (cache) {
+                cacheWhois(normalizedWithTimezone);
+            }
+ 
+            return normalizedWithTimezone;
+        } catch (error) {
+            console.warn(`[lookupUserIp] ${api.url} failed:`, error.message);
+            // Continue to next API
+        }
+    }
+ 
+    // All APIs failed, return fallback
+    console.error('[lookupUserIp] All IP lookup APIs failed, using fallback data');
+    return getFallbackWhois();
+}
+ 
+/**
+ * Normalize geoiplookup.io response to match Fleetbase whois format
+ */
+function normalizeGeoIPLookup(data) {
+    return {
+        ip: data.ip,
+        city: data.city,
+        region: data.region,
+        country_code: data.country_code,
+        country_name: data.country_name,
+        continent_code: data.continent_code,
+        continent_name: data.continent_name,
+        latitude: data.latitude,
+        longitude: data.longitude,
+        postal_code: data.postal_code,
+        timezone: data.timezone_name || getBrowserTimezone(),
+        currency: {
+            code: data.currency_code,
+            name: data.currency_name,
+        },
+        languages: [
+            {
+                code: data.language_code,
+                name: data.language_name,
+            },
+        ],
+        isp: data.isp,
+        org: data.org,
+        asn: data.asn,
+        connection_type: data.connection_type,
+        // Metadata
+        _source: 'geoiplookup.io',
+        _timestamp: Date.now(),
+    };
+}
+ 
+/**
+ * Normalize ipapi.co response to match Fleetbase whois format
+ */
+function normalizeIPApi(data) {
+    return {
+        ip: data.ip,
+        city: data.city,
+        region: data.region,
+        country_code: data.country_code,
+        country_name: data.country_name,
+        continent_code: data.continent_code,
+        continent_name: null,
+        latitude: data.latitude,
+        longitude: data.longitude,
+        postal_code: data.postal,
+        timezone: data.timezone || getBrowserTimezone(),
+        currency: {
+            code: data.currency,
+            name: data.currency_name,
+        },
+        languages: data.languages ? data.languages.split(',').map((lang) => ({ code: lang.trim(), name: lang.trim() })) : [],
+        isp: data.org,
+        org: data.org,
+        asn: data.asn,
+        connection_type: null,
+        // Metadata
+        _source: 'ipapi.co',
+        _timestamp: Date.now(),
+    };
+}
+ 
+/**
+ * Get cached whois data from localStorage
+ */
+function getCachedWhois() {
+    try {
+        const cached = localStorage.getItem('fleetbase:whois');
+        if (!cached) {
+            return null;
+        }
+ 
+        const data = JSON.parse(cached);
+        const age = Date.now() - data._timestamp;
+        const maxAge = 60 * 60 * 1000; // 1 hour
+ 
+        if (age > maxAge) {
+            localStorage.removeItem('fleetbase:whois');
+            return null;
+        }
+ 
+        const normalized = ensureWhoisTimezone(data);
+        if (normalized.timezone !== data.timezone) {
+            cacheWhois(normalized);
+        }
+ 
+        return normalized;
+    } catch (error) {
+        console.error('[getCachedWhois] Error reading cache:', error);
+        return null;
+    }
+}
+ 
+/**
+ * Cache whois data to localStorage
+ */
+function cacheWhois(data) {
+    try {
+        localStorage.setItem('fleetbase:whois', JSON.stringify(data));
+    } catch (error) {
+        console.error('[cacheWhois] Error writing cache:', error);
+    }
+}
+ 
+/**
+ * Get fallback whois data when all APIs fail
+ */
+function getFallbackWhois() {
+    // Try to get browser language and timezone as fallback
+    const browserLang = navigator.language || 'en-US';
+    const timezone = getBrowserTimezone();
+ 
+    return {
+        ip: null,
+        city: null,
+        region: null,
+        country_code: null,
+        country_name: null,
+        continent_code: null,
+        continent_name: null,
+        latitude: null,
+        longitude: null,
+        postal_code: null,
+        timezone: timezone,
+        currency: {
+            code: null,
+            name: null,
+        },
+        languages: [
+            {
+                code: browserLang.split('-')[0],
+                name: browserLang,
+            },
+        ],
+        isp: null,
+        org: null,
+        asn: null,
+        connection_type: null,
+        _source: 'fallback',
+        _timestamp: Date.now(),
+    };
+}
+ 
+export function getBrowserTimezone() {
+    try {
+        return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
+    } catch (error) {
+        return null;
+    }
+}
+ 
+export function ensureWhoisTimezone(whois = {}) {
+    whois = whois || {};
+ 
+    return {
+        ...whois,
+        timezone: whois.timezone || getBrowserTimezone(),
+    };
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/merge-registered-items.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/merge-registered-items.js.html new file mode 100644 index 0000000..9fd8be9 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/merge-registered-items.js.html @@ -0,0 +1,676 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/merge-registered-items.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils merge-registered-items.js

+
+ +
+ 0% + Statements + 0/95 +
+ + +
+ 0% + Branches + 0/92 +
+ + +
+ 0% + Functions + 0/23 +
+ + +
+ 0% + Lines + 0/85 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { debug } from '@ember/debug';
+ 
+/**
+ * Merge the items extensions registered into a view slot with the view's own
+ * built-in items.
+ *
+ * - Built-ins win: a registered item whose `id` matches a built-in is dropped.
+ * - Registered items are placed in `priority` order (lowest first), each at its
+ *   `before`/`after` anchor, else at `index`, else at the slot's default spot:
+ *   columns go just before the row-actions column, `row-actions` and `menu`
+ *   items go before the delete action, and everything else is appended.
+ * - Every registered item is a fresh copy, so a consumer that mutates what it
+ *   renders (the column picker sets `hidden`) never touches the registry.
+ * - Handlers are bound to the view context: an action's `fn(target)` and
+ *   `isVisible(target)` become `fn(target, ctx)` and `isVisible(target, ctx)`, a
+ *   bulk action's target is the selection at click time (`ctx.getSelectedRows()`), a
+ *   button's `onClick()` becomes `onClick(ctx)`, and a button's `isVisible(ctx)`
+ *   is evaluated here, so hidden buttons are left out entirely.
+ *
+ * Pure: neither input is modified.
+ *
+ * @param {Array} baseItems The view's built-in items
+ * @param {Array} registeredItems The items registered for this slot
+ * @param {Object} options
+ * @param {String} options.slot The slot being merged (`columns`, `row-actions`, …)
+ * @param {Object} options.context The view context passed to handlers
+ * @param {Function} options.canAccess `(permission) => Boolean`; registered columns failing it are left out
+ * @returns {Array}
+ */
+export default function mergeRegisteredItems(baseItems = [], registeredItems = [], { slot = null, context = {}, canAccess = null } = {}) {
+    const result = [...toArray(baseItems)];
+    const takenIds = new Set(result.map((item) => item?.id).filter(Boolean));
+ 
+    const registered = toArray(registeredItems)
+        .map((item, order) => ({ item, order }))
+        .sort((a, b) => priorityOf(a.item) - priorityOf(b.item) || a.order - b.order)
+        .map(({ item }) => item);
+ 
+    for (const item of registered) {
+        if (!item || !item.id) {
+            continue;
+        }
+ 
+        if (takenIds.has(item.id)) {
+            debug(`[resource-view] "${item.id}" is already in the ${slot ?? 'view'} slot; the registered item was skipped.`);
+            continue;
+        }
+ 
+        if (slot === 'columns' && item.permission && typeof canAccess === 'function' && !canAccess(item.permission)) {
+            continue;
+        }
+ 
+        const prepared = prepareItem(item, slot, context);
+        if (!prepared) {
+            continue;
+        }
+ 
+        result.splice(placementIndex(result, prepared, slot), 0, prepared);
+        takenIds.add(item.id);
+    }
+ 
+    return result;
+}
+ 
+function toArray(items) {
+    if (!items) {
+        return [];
+    }
+ 
+    return typeof items.toArray === 'function' ? items.toArray() : Array.from(items);
+}
+ 
+function priorityOf(item) {
+    const priority = Number(item?.priority);
+    return Number.isFinite(priority) ? priority : 10;
+}
+ 
+function bindAction(action, context) {
+    const bound = { ...action };
+ 
+    if (typeof action.fn === 'function') {
+        bound.fn = (target, ...rest) => action.fn(target, context, ...rest);
+    }
+ 
+    if (typeof action.isVisible === 'function') {
+        bound.isVisible = (target) => action.isVisible(target, context);
+    }
+ 
+    return bound;
+}
+ 
+function prepareItem(item, slot, context) {
+    if (slot === 'actions') {
+        if (typeof item.isVisible === 'function' && !item.isVisible(context)) {
+            return null;
+        }
+ 
+        const button = { ...item };
+        delete button.isVisible;
+ 
+        if (typeof item.onClick === 'function') {
+            button.onClick = (...args) => item.onClick(context, ...args);
+        }
+ 
+        if (typeof item.fn === 'function') {
+            button.fn = (...args) => item.fn(context, ...args);
+        }
+ 
+        if (Array.isArray(item.items)) {
+            button.items = item.items
+                .filter((child) => typeof child?.isVisible !== 'function' || child.isVisible(context.resource, context))
+                .map((child) => {
+                    const bound = bindAction(child, context);
+                    if (typeof child.fn === 'function') {
+                        bound.fn = (...args) => child.fn(context.resource, context, ...args);
+                    }
+                    return bound;
+                });
+        }
+ 
+        return button;
+    }
+ 
+    if (slot === 'menu') {
+        if (typeof item.isVisible === 'function' && !item.isVisible(context.resource, context)) {
+            return null;
+        }
+ 
+        const bound = bindAction(item, context);
+        delete bound.isVisible;
+        if (typeof item.fn === 'function') {
+            bound.fn = (...args) => item.fn(context.resource, context, ...args);
+        }
+ 
+        return bound;
+    }
+ 
+    if (slot === 'row-actions') {
+        return bindAction(item, context);
+    }
+ 
+    if (slot === 'bulk-actions') {
+        const bound = bindAction(item, context);
+        if (typeof item.fn === 'function' && typeof context.getSelectedRows === 'function') {
+            bound.fn = (...args) => item.fn(context.getSelectedRows(), context, ...args);
+        }
+ 
+        return bound;
+    }
+ 
+    return { ...item };
+}
+ 
+function indexOfId(items, id) {
+    return items.findIndex((candidate) => candidate?.id === id);
+}
+ 
+function placementIndex(items, item, slot) {
+    if (item.before) {
+        const index = indexOfId(items, item.before);
+        if (index !== -1) {
+            return index;
+        }
+    }
+ 
+    if (item.after) {
+        const index = indexOfId(items, item.after);
+        if (index !== -1) {
+            return index + 1;
+        }
+    }
+ 
+    if (Number.isInteger(item.index)) {
+        return Math.min(Math.max(item.index, 0), items.length);
+    }
+ 
+    return defaultIndex(items, slot);
+}
+ 
+function defaultIndex(items, slot) {
+    if (slot === 'columns') {
+        const index = items.findIndex((column) => column?.cellComponent === 'table/cell/dropdown' || column?.sticky === 'right');
+        return index === -1 ? items.length : index;
+    }
+ 
+    if (slot === 'row-actions' || slot === 'menu') {
+        const index = indexOfId(items, 'delete');
+        if (index === -1) {
+            return items.length;
+        }
+ 
+        // Keep a separator that introduces the delete action attached to it.
+        return index > 0 && items[index - 1]?.separator ? index - 1 : index;
+    }
+ 
+    return items.length;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/resource-view-registry.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/resource-view-registry.js.html new file mode 100644 index 0000000..c348fb6 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/resource-view-registry.js.html @@ -0,0 +1,466 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/resource-view-registry.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils resource-view-registry.js

+
+ +
+ 18.51% + Statements + 5/27 +
+ + +
+ 0% + Branches + 0/12 +
+ + +
+ 0% + Functions + 0/7 +
+ + +
+ 20% + Lines + 5/25 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Naming rules for the resource view registries.
+ *
+ * Every table and details view slot an extension can add to is named
+ *
+ *     <extension>:<resource>:<surface>:<slot>
+ *
+ * - `extension` is the engine that owns the view, and always equals its
+ *   console mount segment (`console.fleet-ops` → `fleet-ops`).
+ * - `resource` is the singular, dasherized resource without an engine prefix
+ *   (`invoice`, not `ledger-invoice`).
+ * - `surface` is `table` or `details`.
+ * - `slot` comes from a fixed vocabulary per surface. `actions` always means
+ *   header buttons; dropdown items are `row-actions`, `bulk-actions` or `menu`.
+ *
+ * Details tabs predate this convention and live in
+ * `<extension>:component:<resource>:details`. `<extension>:<resource>:details:tabs`
+ * is an alias for that registry: both names reach the same tabs.
+ *
+ * @example
+ * 'fleet-ops:driver:table:columns'
+ * 'ledger:invoice:details:menu'
+ * 'fleet-ops:driver:details:tabs' // → fleet-ops:component:driver:details
+ */
+ 
+export const SURFACE_SLOTS = {
+    table: ['columns', 'row-actions', 'bulk-actions', 'actions'],
+    details: ['actions', 'menu', 'tabs'],
+};
+ 
+/** The registry list each slot stores into, named after the contract it holds. */
+export const SLOT_LISTS = {
+    columns: 'table-column',
+    'row-actions': 'action',
+    'bulk-actions': 'action',
+    menu: 'action',
+    actions: 'action-button',
+    tabs: 'menu-item',
+};
+ 
+const SEGMENT = '[a-z0-9]+(?:-[a-z0-9]+)*';
+const NAME_PATTERN = new RegExp(`^(${SEGMENT}):(${SEGMENT}):(table|details):([a-z]+(?:-[a-z]+)*)$`);
+const PREFIX_PATTERN = new RegExp(`^(${SEGMENT}):(${SEGMENT}):(table|details)$`);
+ 
+/**
+ * Parse a full registry name. Returns `null` for anything that does not follow
+ * the convention, including an unknown surface or a slot the surface lacks.
+ *
+ * `section` and `list` say where the slot's items are stored. For every slot
+ * but `tabs` that is the name itself; tabs are stored in the legacy tab registry.
+ *
+ * @param {String} name
+ * @returns {{name: String, prefix: String, extension: String, resource: String, surface: String, slot: String, section: String, list: String}|null}
+ */
+export function parseRegistryName(name) {
+    if (typeof name !== 'string') {
+        return null;
+    }
+ 
+    const match = name.match(NAME_PATTERN);
+    if (!match) {
+        return null;
+    }
+ 
+    const [, extension, resource, surface, slot] = match;
+    if (!SURFACE_SLOTS[surface].includes(slot)) {
+        return null;
+    }
+ 
+    const section = slot === 'tabs' ? detailsTabsRegistryName(extension, resource) : name;
+    return { name, prefix: `${extension}:${resource}:${surface}`, extension, resource, surface, slot, section, list: SLOT_LISTS[slot] };
+}
+ 
+/**
+ * Parse a surface prefix such as `fleet-ops:driver:table`, the form layout
+ * components take as `@registry`.
+ *
+ * @param {String} prefix
+ * @returns {{prefix: String, extension: String, resource: String, surface: String}|null}
+ */
+export function parseRegistryPrefix(prefix) {
+    if (typeof prefix !== 'string') {
+        return null;
+    }
+ 
+    const match = prefix.match(PREFIX_PATTERN);
+    if (!match) {
+        return null;
+    }
+ 
+    const [, extension, resource, surface] = match;
+    return { prefix, extension, resource, surface };
+}
+ 
+/**
+ * Build a full registry name from its segments, in the order they appear.
+ *
+ * @returns {String}
+ */
+export function buildRegistryName(extension, resource, surface, slot) {
+    return `${extension}:${resource}:${surface}:${slot}`;
+}
+ 
+/**
+ * Every registry name for one resource's table and details views, including
+ * the details tabs alias.
+ *
+ * @param {String} extension
+ * @param {String} resource
+ * @returns {Array<String>}
+ */
+export function resourceRegistryNames(extension, resource) {
+    return Object.entries(SURFACE_SLOTS).flatMap(([surface, slots]) => slots.map((slot) => buildRegistryName(extension, resource, surface, slot)));
+}
+ 
+/**
+ * The legacy details-tab registry for a resource. Tabs predate the convention
+ * and keep their original name so existing registrations keep working;
+ * `<extension>:<resource>:details:tabs` is an alias for it.
+ *
+ * @returns {String}
+ */
+export function detailsTabsRegistryName(extension, resource) {
+    return `${extension}:component:${resource}:details`;
+}
+ 
+export default parseRegistryName;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/index.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/index.html new file mode 100644 index 0000000..510a692 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize + + + + + + + + + +
+
+

All files ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize

+
+ +
+ 88.23% + Statements + 15/17 +
+ + +
+ 66.66% + Branches + 12/18 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 87.5% + Lines + 14/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
normalize-polymorphic-type-within-hash.js +
+
90.9%10/1176.92%10/13100%1/190.9%10/11
normalize-polymorphic-type.js +
+
83.33%5/640%2/5100%2/280%4/5
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type-within-hash.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type-within-hash.js.html new file mode 100644 index 0000000..2116255 --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type-within-hash.js.html @@ -0,0 +1,166 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type-within-hash.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize normalize-polymorphic-type-within-hash.js

+
+ +
+ 90.9% + Statements + 10/11 +
+ + +
+ 76.92% + Branches + 10/13 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 90.9% + Lines + 10/11 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28  +  +  +  +  +161x +  +  +  +161x +469x +7x +7x +  +7x +  +  +7x +7x +7x +  +  +  +  +  +161x +  + 
import normalizePolymorphicType from './normalize-polymorphic-type';
+import { isBlank } from '@ember/utils';
+import { set } from '@ember/object';
+ 
+export default function serializeNormalizePolymorphicTypeWithinHash(hash) {
+    Iif (typeof hash !== 'object') {
+        return hash;
+    }
+ 
+    for (let attr in hash) {
+        if (typeof attr === 'string' && attr.includes('_type') && encodeURI(hash[attr]).includes('%5C')) {
+            const emberPolymorphicType = normalizePolymorphicType(hash[attr]);
+            const polymorphicRelationType = attr.replace(`_type`, ``);
+ 
+            hash[attr] = emberPolymorphicType;
+ 
+            // set the type of relationship using the polymorphic relation attr
+            Eif (hash[polymorphicRelationType] && !isBlank(hash[polymorphicRelationType])) {
+                Eif (hash[polymorphicRelationType].type) {
+                    set(hash, `${polymorphicRelationType}._type`, hash[polymorphicRelationType].type);
+                }
+            }
+        }
+    }
+ 
+    return hash;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type.js.html b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type.js.html new file mode 100644 index 0000000..695538d --- /dev/null +++ b/console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type.js.html @@ -0,0 +1,124 @@ + + + + + + Code coverage report for ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize/normalize-polymorphic-type.js + + + + + + + + + +
+
+

All files / ../../console/node_modules/.pnpm/@fleetbase+ember-core@0.3.25_@ember+string@3.1.1_@ember+test-helpers@3.3.1_@babel+core@_cb240661001c0e719c8b5c498e670661/node_modules/@fleetbase/ember-core/addon/utils/serialize normalize-polymorphic-type.js

+
+ +
+ 83.33% + Statements + 5/6 +
+ + +
+ 40% + Branches + 2/5 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 80% + Lines + 4/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +147x +  +  +  +7x +  +7x +  +  +  +  +7x +  + 
const lowercase = (string) => string.toLowerCase();
+ 
+export default function serializeNormalizePolymorphicType(polymorphicTypeValue, defaultValue = null) {
+    // encode first
+    const encodedPolymorphicTypeValue = encodeURI(polymorphicTypeValue);
+    // if encoded is invalid send the default value or the original value back
+    Iif (!encodedPolymorphicTypeValue.includes('%5C')) {
+        return defaultValue || polymorphicTypeValue;
+    }
+    // from fleetbase api should be something like Fleetbase\Models\Model
+    // change value to ember-data polymorphic type
+    return lowercase(encodedPolymorphicTypeValue.split('%5C').reverse()[0]);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 365edaf..986b75c 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "fleetops-data", "fleetbase-fleet-ops", "fleetbase", - "ember-addon", - "ember-engine" + "ember-addon" ], "repository": "https://github.com/fleetbase/fleetops-data", "license": "AGPL-3.0-or-later", diff --git a/tests/unit/utils/identity-stub-test.js b/tests/unit/utils/identity-stub-test.js new file mode 100644 index 0000000..ce5bdbb --- /dev/null +++ b/tests/unit/utils/identity-stub-test.js @@ -0,0 +1,58 @@ +import { module, test } from 'qunit'; +import buildIdentityStub from '@fleetbase/fleetops-data/utils/identity-stub'; + +module('Unit | Utility | identity-stub', function () { + test('it names the stub from the row, an explicit name, or not at all', function (assert) { + const stub = buildIdentityStub({ driver_name: 'Ada' }, { type: 'driver' }); + + assert.strictEqual(stub.name, 'Ada'); + assert.strictEqual(stub.displayName, 'Ada'); + assert.strictEqual(stub.display_name, 'Ada'); + assert.strictEqual(stub.resourceType, 'driver'); + assert.true(stub.isIdentityStub); + + assert.strictEqual(buildIdentityStub({ pickupName: 'Depot' }, { type: 'place', nameKey: 'pickupName' }).name, 'Depot'); + assert.strictEqual(buildIdentityStub(null, { type: 'place', name: 'Depot', extra: { city: 'SG' } }).city, 'SG'); + assert.strictEqual(buildIdentityStub({}, { type: 'driver' }), null, 'a row without the name has no stub'); + assert.strictEqual(buildIdentityStub(null, { type: 'driver' }), null, 'no row, no stub'); + assert.strictEqual(buildIdentityStub({ driver_name: 'Ada' }), null, 'no options means no type to name the stub after'); + }); + + test('loadResource resolves to null without a loader', async function (assert) { + const stub = buildIdentityStub({ driver_name: 'Ada' }, { type: 'driver' }); + + assert.strictEqual(await stub.loadResource(), null); + }); + + test('concurrent loads of one row share a request, a failed load resolves to null, and a later load runs again', async function (assert) { + const row = { driver_name: 'Ada' }; + const record = { id: 'driver_1' }; + let calls = 0; + const stub = buildIdentityStub(row, { + type: 'driver', + load: () => { + calls++; + return calls === 2 ? Promise.reject(new Error('offline')) : record; + }, + }); + + const [first, second] = await Promise.all([stub.loadResource(), stub.loadResource()]); + assert.strictEqual(first, record); + assert.strictEqual(second, record); + assert.strictEqual(calls, 1, 'one request for both callers'); + + assert.strictEqual(await stub.loadResource(), null, 'the failed second load resolves to null'); + assert.strictEqual(calls, 2); + + assert.strictEqual(await stub.loadResource(), record, 'the third load runs afresh'); + assert.strictEqual(calls, 3); + }); + + test('two stubs of different types on one row load independently', async function (assert) { + const row = { driver_name: 'Ada', vehicle_name: 'Truck' }; + const driver = buildIdentityStub(row, { type: 'driver', load: () => 'D' }); + const vehicle = buildIdentityStub(row, { type: 'vehicle', load: () => 'V' }); + + assert.deepEqual(await Promise.all([driver.loadResource(), vehicle.loadResource()]), ['D', 'V']); + }); +}); diff --git a/tests/unit/utils/resource-descriptors/helpers-test.js b/tests/unit/utils/resource-descriptors/helpers-test.js index b633d50..8ccb2b4 100644 --- a/tests/unit/utils/resource-descriptors/helpers-test.js +++ b/tests/unit/utils/resource-descriptors/helpers-test.js @@ -109,7 +109,9 @@ module('Unit | Utility | resource-descriptors/helpers', function (hooks) { assert.strictEqual(typeLabel(''), null); assert.strictEqual(typeLabel('customer-fleet-ops:contact'), 'Customer'); assert.strictEqual(typeLabel('facilitator_vendor'), 'Facilitator vendor'); - assert.strictEqual(typeLabel(':contact'), null, 'nothing readable before the colon'); + assert.strictEqual(typeLabel('fleet-ops:contact'), 'Contact', 'a bare extension prefix reads as the type after it'); + assert.strictEqual(typeLabel(':contact'), 'Contact'); + assert.strictEqual(typeLabel(':'), null, 'nothing readable on either side'); }); test('money and join leave out what is missing', function (assert) { @@ -140,6 +142,20 @@ module('Unit | Utility | resource-descriptors/helpers', function (hooks) { assert.strictEqual(extensionManager({ lookup: () => null }), null); assert.strictEqual(extensionManager({ lookup: (name) => (name === 'service:universe/extension-manager' ? manager : null) }), manager); assert.strictEqual(extensionManager({ lookup: (name) => (name === 'service:universe' ? { extensionManager: manager } : null) }), manager); + assert.strictEqual( + extensionManager({ + lookup: (name) => + name === 'service:universe' + ? { + get extensionManager() { + throw new Error('no manager on this host'); + }, + } + : null, + }), + null, + 'a universe whose manager lookup throws counts as no manager' + ); }); test('engineInstalled is false without a manager, when not installed, or when the check throws', function (assert) {