diff --git a/ui/desktop/app/router.js b/ui/desktop/app/router.js
index 32dc22624..335c69db4 100644
--- a/ui/desktop/app/router.js
+++ b/ui/desktop/app/router.js
@@ -7,5 +7,7 @@ export default class Router extends EmberRouter {
}
Router.map(function () {
- this.route('scopes');
+ this.route('scopes', function() {
+ this.route('scope', { path: ':scope_id' }, function() {});
+ });
});
diff --git a/ui/desktop/app/routes/index.js b/ui/desktop/app/routes/index.js
new file mode 100644
index 000000000..c8728d037
--- /dev/null
+++ b/ui/desktop/app/routes/index.js
@@ -0,0 +1,12 @@
+import Route from '@ember/routing/route';
+
+export default class IndexRoute extends Route {
+ // =methods
+
+ /**
+ * Redirects to scopes route for further processing.
+ */
+ redirect() {
+ this.transitionTo('scopes');
+ }
+}
diff --git a/ui/desktop/app/routes/scopes/scope.js b/ui/desktop/app/routes/scopes/scope.js
new file mode 100644
index 000000000..53ab105d3
--- /dev/null
+++ b/ui/desktop/app/routes/scopes/scope.js
@@ -0,0 +1,79 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+import { A } from '@ember/array';
+
+export default class ScopesScopeRoute extends Route {
+ // =services
+
+ @service scope;
+
+ // =methods
+
+ /**
+ * Attempt to load the specified scope from the API. This is allowed
+ * to fail, since in some cases the user may not have permission to read a
+ * scope directly, but may have permission to read resources under it.
+ * If the scope fails to load, we still proceed using a temporary scope object
+ * consisting of only the specified ID.
+ * @param {object} params
+ * @param {string} params.scope_id
+ * @return {Promise{ScopeModel}}
+ */
+ model({ scope_id: id }) {
+ // Since only global and org scopes are authenticatable, we can infer type
+ // from ID because global has a fixed ID.
+ const type = id === 'global' ? 'global' : 'org';
+ return this.store.findRecord('scope', id).catch(() => {
+ console.error('caught error');
+ const maybeExistingScope = this.store.peekRecord('scope', id);
+
+ const scopeOptions = { id, type };
+ return (
+ maybeExistingScope || this.store.createRecord('scope', scopeOptions)
+ );
+ });
+ }
+
+
+ /**
+ * Load all scopes within the current scope context. Always attempt to load
+ * org scopes. Only attempt to load project scopes if the current scope is
+ * a project. These are used for scope navigation.
+ */
+ async afterModel(model) {
+ // Load all orgs
+ let orgs;
+ orgs = await this.store.query('scope', { scope_id: 'global' })
+ .catch(() => A([]));
+ // Then pull out the "selected" scopes, if relevant
+ let selectedOrg;
+ if (model.isGlobal || model.isOrg) selectedOrg = model;
+ // Update the scope service with the current scope(s);
+ this.scope.org = selectedOrg;
+ this.scopes = { orgs, selectedOrg };
+ }
+
+ /**
+ * Adds the scopes hash to the controller context (see `afterModel`).
+ * @param {Controller} controller
+ */
+ setupController(controller) {
+ super.setupController(...arguments);
+ const scopes = this.scopes;
+ controller.setProperties({ scopes });
+ }
+
+ /**
+ * Renders the scope-specific sidebar template.
+ * @override
+ * @param {object} controller
+ * @param {object} model
+ */
+ renderTemplate() {
+ super.renderTemplate(...arguments);
+ this.render('scopes/scope/-header-nav', {
+ into: 'application',
+ outlet: 'header-nav',
+ });
+ }
+}
diff --git a/ui/desktop/app/services/scope.js b/ui/desktop/app/services/scope.js
new file mode 100644
index 000000000..37037eb23
--- /dev/null
+++ b/ui/desktop/app/services/scope.js
@@ -0,0 +1,17 @@
+import Service from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+
+/**
+ * This simple non-functional service is used to retain references to
+ * the currently active scope or scopes.
+ */
+export default class ScopeService extends Service {
+
+ // =attributes
+
+ /**
+ * @type {ScopeModel}
+ */
+ @tracked org;
+
+}
diff --git a/ui/desktop/app/templates/application.hbs b/ui/desktop/app/templates/application.hbs
index 23f96480a..bab0cd57b 100644
--- a/ui/desktop/app/templates/application.hbs
+++ b/ui/desktop/app/templates/application.hbs
@@ -5,6 +5,10 @@
+
+
+ {{outlet "header-nav"}}
+
diff --git a/ui/desktop/app/templates/index.hbs b/ui/desktop/app/templates/index.hbs
new file mode 100644
index 000000000..e2147cab0
--- /dev/null
+++ b/ui/desktop/app/templates/index.hbs
@@ -0,0 +1 @@
+{{outlet}}
\ No newline at end of file
diff --git a/ui/desktop/app/templates/scopes.hbs b/ui/desktop/app/templates/scopes.hbs
index 257dd84b7..c24cd6895 100644
--- a/ui/desktop/app/templates/scopes.hbs
+++ b/ui/desktop/app/templates/scopes.hbs
@@ -1,4 +1 @@
-I'm in scopes
-{{@model}}
-
-{{outlet}}
\ No newline at end of file
+{{outlet}}
diff --git a/ui/desktop/app/templates/scopes/index.hbs b/ui/desktop/app/templates/scopes/index.hbs
new file mode 100644
index 000000000..cde799ad1
--- /dev/null
+++ b/ui/desktop/app/templates/scopes/index.hbs
@@ -0,0 +1,26 @@
+{{outlet}}
+
+
+
+
+ Scope
+
+
+
+ {{#each @model as |scope|}}
+
+
+
+ {{scope.name}}
+
+
+
+ {{scope.id}}
+
+
+ {{/each}}
+
+
diff --git a/ui/desktop/app/templates/scopes/scope.hbs b/ui/desktop/app/templates/scopes/scope.hbs
new file mode 100644
index 000000000..c24cd6895
--- /dev/null
+++ b/ui/desktop/app/templates/scopes/scope.hbs
@@ -0,0 +1 @@
+{{outlet}}
diff --git a/ui/desktop/app/templates/scopes/scope/-header-nav.hbs b/ui/desktop/app/templates/scopes/scope/-header-nav.hbs
new file mode 100644
index 000000000..4a383eb6d
--- /dev/null
+++ b/ui/desktop/app/templates/scopes/scope/-header-nav.hbs
@@ -0,0 +1,16 @@
+
+ {{!-- {{#if this.session.data.authenticated.isGlobal}} --}}
+
+ {{t "titles.global"}}
+
+
+ {{!-- {{/if}} --}}
+ {{#each this.scopes.orgs as |scope|}}
+
+ {{scope.displayName}}
+
+ {{/each}}
+
diff --git a/ui/desktop/app/templates/scopes/scope/index.hbs b/ui/desktop/app/templates/scopes/scope/index.hbs
new file mode 100644
index 000000000..c24cd6895
--- /dev/null
+++ b/ui/desktop/app/templates/scopes/scope/index.hbs
@@ -0,0 +1 @@
+{{outlet}}
diff --git a/ui/desktop/config/environment.js b/ui/desktop/config/environment.js
index 2de008f1c..dbd5328b7 100644
--- a/ui/desktop/config/environment.js
+++ b/ui/desktop/config/environment.js
@@ -7,8 +7,8 @@ module.exports = function (environment) {
let ENV = {
modulePrefix: 'desktop',
environment,
- rootURL: process.env.EMBER_CLI_ELECTRON ? '' : process.env.EMBER_CLI_ELECTRON ? '' : '/',
- locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : process.env.EMBER_CLI_ELECTRON ? 'hash' : 'auto',
+ rootURL: process.env.EMBER_CLI_ELECTRON ? '' : process.env.EMBER_CLI_ELECTRON ? '' : process.env.EMBER_CLI_ELECTRON ? '' : '/',
+ locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : process.env.EMBER_CLI_ELECTRON ? 'hash' : process.env.EMBER_CLI_ELECTRON ? 'hash' : 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
diff --git a/ui/desktop/package.json b/ui/desktop/package.json
index fe4414ee7..ee6ec558d 100644
--- a/ui/desktop/package.json
+++ b/ui/desktop/package.json
@@ -53,7 +53,7 @@
"ember-cli-template-lint": "^2.0.2",
"ember-cli-uglify": "^3.0.0",
"ember-data": "~3.20.0",
- "ember-data-model-fragments": "^4.0.0",
+ "ember-data-model-fragments": "5.0.0-beta.0",
"ember-electron": "https://github.com/adopted-ember-addons/ember-electron#v3.0.0-beta.4",
"ember-exam": "^6.0.1",
"ember-export-application-global": "^2.0.1",
@@ -62,6 +62,7 @@
"ember-intl": "^5.5.0",
"ember-load-initializers": "^2.1.1",
"ember-maybe-import-regenerator": "^0.1.6",
+ "ember-modifier": "^2.1.1",
"ember-page-title": "^6.0.3",
"ember-qunit": "^4.6.0",
"ember-resolver": "^8.0.0",
diff --git a/ui/desktop/public/app-icons/global.svg b/ui/desktop/public/app-icons/global.svg
new file mode 100644
index 000000000..0e5b84d47
--- /dev/null
+++ b/ui/desktop/public/app-icons/global.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/desktop/public/app-icons/org.svg b/ui/desktop/public/app-icons/org.svg
new file mode 100644
index 000000000..238cc8435
--- /dev/null
+++ b/ui/desktop/public/app-icons/org.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/desktop/tests/unit/routes/index-test.js b/ui/desktop/tests/unit/routes/index-test.js
new file mode 100644
index 000000000..eaaac17bc
--- /dev/null
+++ b/ui/desktop/tests/unit/routes/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | index', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:index');
+ assert.ok(route);
+ });
+});
diff --git a/ui/desktop/tests/unit/routes/scopes/scope-test.js b/ui/desktop/tests/unit/routes/scopes/scope-test.js
new file mode 100644
index 000000000..62c97d9f0
--- /dev/null
+++ b/ui/desktop/tests/unit/routes/scopes/scope-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope', function(hooks) {
+ setupTest(hooks);
+
+ test('it exists', function(assert) {
+ let route = this.owner.lookup('route:scopes/scope');
+ assert.ok(route);
+ });
+});
diff --git a/ui/desktop/tests/unit/services/scope-test.js b/ui/desktop/tests/unit/services/scope-test.js
new file mode 100644
index 000000000..95493c0b4
--- /dev/null
+++ b/ui/desktop/tests/unit/services/scope-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Service | scope', function(hooks) {
+ setupTest(hooks);
+
+ // TODO: Replace this with your real tests.
+ test('it exists', function(assert) {
+ let service = this.owner.lookup('service:scope');
+ assert.ok(service);
+ });
+});
diff --git a/ui/desktop/translations/en-us.yaml b/ui/desktop/translations/en-us.yaml
new file mode 100644
index 000000000..960b00647
--- /dev/null
+++ b/ui/desktop/translations/en-us.yaml
@@ -0,0 +1,2 @@
+titles:
+ global: Global
diff --git a/ui/desktop/vendor/.gitkeep b/ui/desktop/vendor/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/yarn.lock b/yarn.lock
index 1d5213a95..d410d9114 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8229,7 +8229,7 @@ ember-cli-babel@7.21.0:
rimraf "^3.0.1"
semver "^5.5.0"
-ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.2:
+ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.2:
version "6.18.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.18.0.tgz#3f6435fd275172edeff2b634ee7b29ce74318957"
integrity sha512-7ceC8joNYxY2wES16iIBlbPSxwKDBhYwC8drU3ZEvuPDMwVv1KzxCNu1fvxyFEBWhwaRNTUxSCsEVoTd9nosGA==
@@ -8775,19 +8775,6 @@ ember-data-model-fragments@5.0.0-beta.0:
git-repo-info "^2.0.0"
npm-git-info "^1.0.3"
-ember-data-model-fragments@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/ember-data-model-fragments/-/ember-data-model-fragments-4.0.0.tgz#8570d8b6bef12e8a3518279af01478ef8818a44f"
- integrity sha512-DcRRQAWrrfRQBW8+v6takQdS2hNJ9rKWU/uHjnPY6+Vg/y6TxyPU8mo0PF2z0DYAeXwoIQ05iYTJT0sqFPEo+w==
- dependencies:
- broccoli-file-creator "^2.1.1"
- broccoli-merge-trees "^3.0.0"
- calculate-cache-key-for-tree "^1.1.0"
- ember-cli-babel "^6.8.0"
- ember-copy "1.0.0"
- git-repo-info "^2.0.0"
- npm-git-info "^1.0.3"
-
ember-data@^3.16.0:
version "3.22.1"
resolved "https://registry.yarnpkg.com/ember-data/-/ember-data-3.22.1.tgz#5b075fc8a2c3f18dac2305037d34b88666631b1a"
@@ -8837,6 +8824,15 @@ ember-decorators-polyfill@^1.1.5:
ember-cli-version-checker "^3.1.3"
ember-compatibility-helpers "^1.2.0"
+ember-destroyable-polyfill@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/ember-destroyable-polyfill/-/ember-destroyable-polyfill-2.0.2.tgz#2cc7532bd3c00e351b4da9b7fc683f4daff79671"
+ integrity sha512-9t+ya+9c+FkNM5IAyJIv6ETG8jfZQaUnFCO5SeLlV0wkSw7TOexyb61jh5GVee0KmknfRhrRGGAyT4Y0TwkZ+w==
+ dependencies:
+ ember-cli-babel "^7.22.1"
+ ember-cli-version-checker "^5.1.1"
+ ember-compatibility-helpers "^1.2.1"
+
ember-disable-prototype-extensions@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/ember-disable-prototype-extensions/-/ember-disable-prototype-extensions-1.1.3.tgz#1969135217654b5e278f9fe2d9d4e49b5720329e"
@@ -9054,6 +9050,18 @@ ember-modifier@^1.0.3:
ember-compatibility-helpers "^1.2.1"
ember-modifier-manager-polyfill "^1.2.0"
+ember-modifier@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-2.1.1.tgz#aa3a12e2d6cf1622f774f3f1eab4880982a43fa9"
+ integrity sha512-g9mcpFWgw5lgNU40YNf0USNWqoGTJ+EqjDQKjm7556gaRNDeGnLylFKqx9O3opwLHEt6ZODnRDy9U0S5YEMREg==
+ dependencies:
+ ember-cli-babel "^7.22.1"
+ ember-cli-normalize-entity-name "^1.0.0"
+ ember-cli-string-utils "^1.1.0"
+ ember-cli-typescript "^3.1.3"
+ ember-destroyable-polyfill "^2.0.2"
+ ember-modifier-manager-polyfill "^1.2.0"
+
ember-notify@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/ember-notify/-/ember-notify-6.0.0.tgz#f92978e3452934f7c9b70d76ad2ec62bf072495a"