diff --git a/components/centraldashboard/.eslintrc.json b/components/centraldashboard/.eslintrc.json
index c2592d9d841..eb871cf94ec 100644
--- a/components/centraldashboard/.eslintrc.json
+++ b/components/centraldashboard/.eslintrc.json
@@ -5,7 +5,8 @@
},
"extends": ["eslint:recommended", "google"],
"globals": {
- "VERSION": true,
+ "VERSION": "readonly",
+ "DEVMODE": "readonly",
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
diff --git a/components/centraldashboard/app/server.ts b/components/centraldashboard/app/server.ts
index b2790d6c608..afd70a413fc 100644
--- a/components/centraldashboard/app/server.ts
+++ b/components/centraldashboard/app/server.ts
@@ -7,12 +7,45 @@ const {PORT, PORT_1} = process.env;
const port: number = Number(PORT) || Number(PORT_1) || 8082;
const frontEnd: string = resolve(__dirname, "public");
+interface Activity {
+ time: Date;
+ event: string;
+ isError: boolean;
+ source: string;
+}
+const actvities = _generateActivities();
+function _generateActivities(): Activity[] {
+ const activities: Activity[] = [];
+ const systems: string[] = [
+ "serving-system",
+ "gpu-system",
+ "training-system"
+ ];
+ const now = Date.now();
+ for (let i = 0; i < 100; i++) {
+ activities.push({
+ time: new Date(now - (Math.random() * 86400000)),
+ event: `Event #${i}`,
+ isError: Math.random() * 10 <= 1, // 1/10 probability
+ source: systems[Math.floor(Math.random() * 3)]
+ });
+ }
+ return activities.sort((a, b) => b.time.getTime() - a.time.getTime());
+}
+
+app.use(express.json());
app.use(express.static(frontEnd));
app.get("/api", (req: express.Request, res: express.Response) => {
console.info(`Request ${req.url} received`);
res.send("Hello World");
});
+app.get("/api/activities", (req: express.Request, res: express.Response) => {
+ res.send(actvities);
+});
+app.get("/*", (req: express.Request, res: express.Response) => {
+ res.sendFile(resolve(frontEnd, "index.html"));
+});
app.listen(port,
- () => console.info(`Server listening on port http://localhost:${port}`));
\ No newline at end of file
+ () => console.info(`Server listening on port http://localhost:${port}`));
diff --git a/components/centraldashboard/package-lock.json b/components/centraldashboard/package-lock.json
index 4fb51fc187e..06204f076bf 100644
--- a/components/centraldashboard/package-lock.json
+++ b/components/centraldashboard/package-lock.json
@@ -845,6 +845,14 @@
"@polymer/polymer": "^3.0.0"
}
},
+ "@polymer/iron-ajax": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-ajax/-/iron-ajax-3.0.1.tgz",
+ "integrity": "sha512-7+TPEAfWsRdhj1Y8UeF1759ktpVu+c3sG16rJiUC3wF9+woQ9xI1zUm2d59i7Yc3aDEJrR/Q8Y262KlOvyGVNg==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
"@polymer/iron-autogrow-textarea": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@polymer/iron-autogrow-textarea/-/iron-autogrow-textarea-3.0.1.tgz",
@@ -1022,6 +1030,14 @@
"@polymer/polymer": "^3.0.0"
}
},
+ "@polymer/iron-range-behavior": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-range-behavior/-/iron-range-behavior-3.0.1.tgz",
+ "integrity": "sha512-+jtL9v45M/T1RJleWyQaNH84S9/mIIR+AjNbYIttbKGp1eG+98j8MDWe7LXNtg79V2LQnE/+VS82cBeELyGVeg==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
"@polymer/iron-resizable-behavior": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@polymer/iron-resizable-behavior/-/iron-resizable-behavior-3.0.1.tgz",
@@ -1155,6 +1171,17 @@
"@polymer/polymer": "^3.0.0"
}
},
+ "@polymer/paper-progress": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/paper-progress/-/paper-progress-3.0.1.tgz",
+ "integrity": "sha512-5nguG+tmnyoaWKVNG8Smtno2uLSPBgEsT3f20JY8yJTjUBYWaqa8E3l5RLkTRXgA4x9OnvLb8/CdlQWXQIogBg==",
+ "requires": {
+ "@polymer/iron-flex-layout": "^3.0.0-pre.26",
+ "@polymer/iron-range-behavior": "^3.0.0-pre.26",
+ "@polymer/paper-styles": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
"@polymer/paper-ripple": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@polymer/paper-ripple/-/paper-ripple-3.0.1.tgz",
@@ -4057,7 +4084,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -4472,7 +4500,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4528,6 +4557,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4571,12 +4601,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
diff --git a/components/centraldashboard/package.json b/components/centraldashboard/package.json
index 4ba98ed150b..3978979b5a0 100644
--- a/components/centraldashboard/package.json
+++ b/components/centraldashboard/package.json
@@ -31,6 +31,7 @@
"@babel/polyfill": "^7.2.5",
"@polymer/app-layout": "^3.0.0",
"@polymer/app-route": "^3.0.0",
+ "@polymer/iron-ajax": "^3.0.1",
"@polymer/iron-collapse": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.0",
"@polymer/iron-icons": "^3.0.1",
@@ -43,11 +44,11 @@
"@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-icon-button": "^3.0.0",
"@polymer/paper-item": "^3.0.1",
+ "@polymer/paper-progress": "^3.0.1",
"@polymer/paper-tabs": "^3.0.1",
"@polymer/polymer": "^3.1.0",
"@types/dotenv": "^6.1.0",
"@webcomponents/webcomponentsjs": "^2.0.0",
- "body-parser": "^1.18.3",
"express": "^4.16.4",
"web-animations-js": "^2.3.1"
},
diff --git a/components/centraldashboard/public/components/activity-view.js b/components/centraldashboard/public/components/activity-view.js
new file mode 100644
index 00000000000..0d6b2a8ad90
--- /dev/null
+++ b/components/centraldashboard/public/components/activity-view.js
@@ -0,0 +1,113 @@
+import {PolymerElement, html} from '@polymer/polymer';
+
+import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
+import '@polymer/iron-ajax/iron-ajax.js';
+import '@polymer/iron-icon/iron-icon.js';
+import '@polymer/iron-icons/iron-icons.js';
+import '@polymer/paper-progress/paper-progress.js';
+
+export class ActivityView extends PolymerElement {
+ static get template() {
+ return html`
+
+
+
+
+
+
+
+
+
[[item.formattedTime]]
+
+
+ [[item.event]]
+
+
[[item.source]]
+
+
+
+ `;
+ }
+
+ /**
+ * Object describing property-related metadata used by Polymer features
+ */
+ static get properties() {
+ return {
+ loading: Boolean,
+ activities: Array,
+ };
+ }
+
+ /**
+ * Handles the Activities response to set date format and icon.
+ * @param {Event} responseEvent
+ */
+ _onResponse(responseEvent) {
+ const {status, response} = responseEvent.detail;
+ this.activities = [];
+ // TODO: Surface the error in some manner
+ if (status !== 200) return;
+ this.activities = response.map((a) => {
+ const activity = {
+ formattedTime: new Date(a.time).toLocaleString(),
+ icon: a.isError ? 'error' : 'build',
+ };
+ return Object.assign(activity, a);
+ });
+ }
+}
+
+window.customElements.define('activity-view', ActivityView);
diff --git a/components/centraldashboard/public/components/dashboard-view.css b/components/centraldashboard/public/components/dashboard-view.css
new file mode 100644
index 00000000000..7a2ba4ebba1
--- /dev/null
+++ b/components/centraldashboard/public/components/dashboard-view.css
@@ -0,0 +1,89 @@
+:host {
+ @apply --layout-vertical;
+ background: #f1f3f4;
+ --accent-color: #007dfc;
+ --primary-background-color: #003c75;
+ --sidebar-default-color: #ffffff4f;
+ --border-color: #f4f4f6;
+}
+
+article {
+ background: #f1f3f4;
+ padding: 1em;
+ grid-gap: 1em;
+ display: grid;
+ min-height: 0;
+ min-width: 0;
+ --primary-background-color: white;
+}
+
+article:after {
+ content: '';
+ grid-column: 1 span 3
+}
+
+article>paper-card {
+ border-radius: 5px;
+ grid-column: 1 / span 2;
+ max-width: 100%;
+ overflow: hidden;
+ min-width: 0;
+ --paper-card-header: {
+ font-family: "Google Sans"
+ }
+}
+
+article>paper-card.thin {
+ grid-column: 3;
+ min-width: 19em;
+}
+
+#Getting-Started paper-icon-item:not(:last-of-type) {
+ border-bottom: 1px solid var(--border-color);
+}
+
+#Getting-Started paper-icon-item iron-icon {
+ color: var(--accent-color)
+}
+
+#Getting-Started [secondary] {
+ word-break: break-word;
+ width: 100%;
+ white-space: normal;
+ font-size: .8em;
+}
+
+#Quick-Links {
+ grid-column: 3
+}
+
+#Quick-Links .link {
+ width: 80%;
+ margin: .5em auto;
+ border: 1px solid #eeeeef;
+ padding: .5em 1em;
+ border-radius: 5px;
+ @apply --layout-horizontal;
+}
+
+#Quick-Links .link.more-coming {
+ opacity: .4;
+ font-style: italic;
+ pointer-events: none
+}
+
+#Quick-Links .link .button {
+ color: var(--accent-color);
+ background: rgba(0, 125, 252, 0.25);
+ border-radius: 50%
+}
+
+a {
+ text-decoration: none;
+ color: initial;
+}
+
+.header:hover {
+ color: var(--paper-blue-700);
+ text-decoration: underline;
+}
diff --git a/components/centraldashboard/public/components/dashboard-view.js b/components/centraldashboard/public/components/dashboard-view.js
new file mode 100644
index 00000000000..653c49b1d8e
--- /dev/null
+++ b/components/centraldashboard/public/components/dashboard-view.js
@@ -0,0 +1,71 @@
+import {html, PolymerElement} from '@polymer/polymer';
+
+import css from './dashboard-view.css';
+
+import template from './dashboard-view.pug';
+
+export class DashboardView extends PolymerElement {
+ static get template() {
+ return html([` ${template()}`]);
+ }
+
+ /**
+ * Object describing property-related metadata used by Polymer features
+ */
+ static get properties() {
+ const kubeflowDocs = 'https://www.kubeflow.org/docs/started';
+
+ return {
+ gettingStartedItems: {
+ type: Array,
+ value: [
+ {
+ text: 'Getting started with Kubeflow',
+ desc: 'Quickly get running with your ML workflow on ' +
+ 'an existing Kubernetes installation',
+ link: `${kubeflowDocs}/getting-started/`,
+ },
+ {
+ text: 'Microk8s for Kubeflow',
+ desc: 'Quickly get Kubeflow running locally on ' +
+ 'native hypervisors',
+ link: `${kubeflowDocs}/getting-started-multipass/`,
+ },
+ {
+ text: 'Minikube for Kubeflow',
+ desc: 'Quickly get Kubeflow running locally',
+ link: `${kubeflowDocs}/getting-started-minikube/`,
+ },
+ {
+ text: 'Kubernetes Engine for Kubeflow',
+ desc: 'Get Kubeflow running on Google Cloud ' +
+ 'Platform. This guide is a quickstart to deploying Kubeflow ' +
+ 'on Google Kubernetes Engine',
+ link: `${kubeflowDocs}/getting-started-gke/`,
+ },
+ {
+ text: 'Requirements for Kubeflow',
+ desc: 'Get more detailed information about using ' +
+ 'Kubeflow and its components',
+ link: `${kubeflowDocs}/requirements/`,
+ },
+ ],
+ },
+ quickLinks: {
+ type: Array,
+ value: [
+ {
+ text: 'Open docs',
+ link: `${kubeflowDocs}/getting-started/`,
+ },
+ {
+ text: 'Open Github',
+ link: 'https://github.com/kubeflow/kubeflow',
+ },
+ ],
+ },
+ };
+ }
+}
+
+window.customElements.define('dashboard-view', DashboardView);
diff --git a/components/centraldashboard/public/components/dashboard-view.pug b/components/centraldashboard/public/components/dashboard-view.pug
new file mode 100644
index 00000000000..7eb3e1f6e67
--- /dev/null
+++ b/components/centraldashboard/public/components/dashboard-view.pug
@@ -0,0 +1,19 @@
+article
+ paper-card#Getting-Started(heading='Getting Started')
+ template(is='dom-repeat', items='[[gettingStartedItems]]')
+ a.heading(href$='[[item.link]]', tabindex='-1',
+ target='_blank')
+ paper-icon-item
+ iron-icon(icon='launch', slot='item-icon')
+ paper-item-body(two-line)
+ .header [[item.text]]
+ aside(secondary) [[item.desc]]
+ paper-card.thin#Quick-Links(heading='Quick Links')
+ template(is='dom-repeat', items='[[quickLinks]]')
+ article.link
+ paper-item-body [[item.text]]
+ a(href$='[[item.link]]', tabindex='-1', target='_blank')
+ paper-icon-button.button(icon='arrow-forward', alt='[[item.text]]')
+ article.link.more-coming
+ paper-item-body More coming soon
+ paper-icon-button.button(icon='arrow-forward', disabled)
diff --git a/components/centraldashboard/public/components/main-page.css b/components/centraldashboard/public/components/main-page.css
index 6bc71f558ee..68f816bf166 100644
--- a/components/centraldashboard/public/components/main-page.css
+++ b/components/centraldashboard/public/components/main-page.css
@@ -1,4 +1,7 @@
-*, :host, :host * {box-sizing: border-box}
+*, :host, :host * {
+ box-sizing: border-box
+}
+
:host {
@apply --layout-vertical;
--accent-color: #007dfc;
@@ -6,12 +9,17 @@
--sidebar-default-color: #ffffff4f;
--border-color: #f4f4f6;
}
-.flex {flex: 1}
+
+.flex {
+ flex: 1
+}
+
.bottom {
margin-top: auto;
@apply --layout-horizontal;
}
-#MainDrawer {
+
+app-drawer {
color: white;
background: var(--primary-background-color);
--app-drawer-content-container: {
@@ -19,19 +27,55 @@
@apply --layout-vertical;
}
}
-#MainDrawer .menu-item {
+
+app-drawer .menu-item {
cursor: pointer;
transition: background .25s;
font-family: Google Sans;
color: var(--sidebar-default-color);
}
-#MainDrawer .menu-item+.divider {width: 90%;margin: 1em auto;border-bottom: 2px solid var(--sidebar-default-color)}
-#MainDrawer .menu-item:hover {background: #ffffff1b}
-#MainDrawer .menu-item.iron-selected {background: #ffffff3b;color: white;font-weight: 100}
-#MainDrawer .footer {padding: 1.25rem;font-size: .9em;color: var(--sidebar-default-color);line-height: 1.5em}
-#MainDrawer .footer > .build {font-size: .9em;font-style: italic;color: rgba(255, 255, 255, 0.64)}
-#NamespaceSelector {position: absolute;left: 1em;top: 50%;transform: translateY(-50%)}
-app-toolbar {display: flex;justify-content: center}
+
+app-drawer .menu-item+.divider {
+ width: 90%;
+ margin: 1em auto;
+ border-bottom: 2px solid var(--sidebar-default-color)
+}
+
+app-drawer .menu-item:hover {
+ background: #ffffff1b
+}
+
+app-drawer .iron-selected>.menu-item {
+ background: #ffffff3b;
+ color: white;
+ font-weight: 100
+}
+
+app-drawer .footer {
+ padding: 1.25rem;
+ font-size: .9em;
+ color: var(--sidebar-default-color);
+ line-height: 1.5em
+}
+
+app-drawer .footer>.build {
+ font-size: .9em;
+ font-style: italic;
+ color: rgba(255, 255, 255, 0.64)
+}
+
+#NamespaceSelector {
+ position: absolute;
+ left: 1em;
+ top: 50%;
+ transform: translateY(-50%)
+}
+
+app-toolbar {
+ display: flex;
+ justify-content: center
+}
+
paper-tabs {
--paper-tabs-selection-bar-color: var(--accent-color);
--paper-tab-ink: var(--accent-color);
@@ -40,30 +84,71 @@ paper-tabs {
border-width: 3px;
}
}
-paper-tab {padding: 0 2em}
-paper-tab.iron-selected {color: var(--accent-color)}
-app-header-layout {@apply --layout-vertical}
-#Menu {position: absolute;right: 10px;top: 50%;transform: translateY(-50%)}
+
+paper-tab {
+ padding: 0 2em
+}
+
+paper-tab.iron-selected {
+ color: var(--accent-color)
+}
+
+paper-tab[link] a {
+ /* These mixins (from iron-flex-layout) center the link text. */
+ @apply --layout-horizontal;
+ @apply --layout-center-center;
+ color: var(--paper-tabs-selection-bar);
+ text-decoration: none;
+}
+
+app-header-layout {
+ @apply --layout-vertical
+}
+
+neon-animated-pages {
+ height: 100%;
+}
+
+neon-animatable {
+ @apply --layout-vertical;
+ height: 100%;
+}
+
+neon-animatable#iframe-page {
+ top: -64px;
+}
+
+#Menu {
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%)
+}
+
#PageFrame {
border: 0;
display: inline-block;
width: 100%;
height: 100%;
}
+
#Logo {
display: flex;
align-items: center;
margin: 0 0 1em;
padding: 2em;
}
-#Logo > img {
+
+#Logo>img {
width: 3em;
margin-right: 1em;
}
-#Logo > figcaption {
+
+#Logo>figcaption {
font-size: 1.3em;
font-family: Google Sans;
}
+
#Dashboard {
background: #f1f3f4;
padding: 1em;
@@ -73,7 +158,8 @@ app-header-layout {@apply --layout-vertical}
min-width: 0;
--primary-background-color: white;
}
-#Dashboard > paper-card {
+
+#Dashboard>paper-card {
border-radius: 5px;
grid-column: 1 / span 2;
max-width: 100%;
@@ -83,27 +169,37 @@ app-header-layout {@apply --layout-vertical}
font-family: "Google Sans"
}
}
-#Dashboard > paper-card.thin {grid-column: 3;min-width: 19em}
-#Dashboard > .Getting-Started paper-icon-item {
+
+#Dashboard>paper-card.thin {
+ grid-column: 3;
+ min-width: 19em
+}
+
+#Dashboard>.Getting-Started paper-icon-item {
cursor: pointer;
}
-#Dashboard > .Getting-Started paper-icon-item:not(:last-of-type) {
+
+#Dashboard>.Getting-Started paper-icon-item:not(:last-of-type) {
border-bottom: 1px solid var(--border-color);
}
-#Dashboard > .Getting-Started paper-icon-item iron-icon {
+
+#Dashboard>.Getting-Started paper-icon-item iron-icon {
color: var(--accent-color)
}
-#Dashboard > .Getting-Started paper-icon-item:hover .heading {
+
+#Dashboard>.Getting-Started paper-icon-item:hover .heading {
color: var(--paper-blue-700);
text-decoration: underline;
}
-#Dashboard > .Getting-Started [secondary] {
-word-break: break-word;
+
+#Dashboard>.Getting-Started [secondary] {
+ word-break: break-word;
width: 100%;
white-space: normal;
font-size: .8em;
}
-#Dashboard > .Quick-Links .link {
+
+#Dashboard>.Quick-Links .link {
width: 80%;
margin: .5em auto;
border: 1px solid #eeeeef;
@@ -111,21 +207,63 @@ word-break: break-word;
border-radius: 5px;
@apply --layout-horizontal;
}
-#Dashboard > .Quick-Links .link.more-coming {opacity: .4;font-style: italic;pointer-events: none}
-#Dashboard > .Quick-Links .link .button {color: var(--accent-color);background: rgba(0, 125, 252, 0.25);border-radius: 50%}
-#Dashboard:after {content: '';grid-column: 1 span 3}
-#Activity {@apply --layout-vertical;@apply --layout-center-center}
-#Activity:before, #Activity:after {font-size: 3em;opacity: .3;font-family: Google Sans}
-#Activity:before {content: ":(";font-size: 6em;transform: rotateZ(90deg)}
-#Activity:after {content: "No content here yet..."}
-
-[hides] {transition: opacity .25s}
-[hidden] {opacity: 0;pointer-events: none}
-[hidden]:not([hides]) {display: none}
+
+#Dashboard>.Quick-Links .link.more-coming {
+ opacity: .4;
+ font-style: italic;
+ pointer-events: none
+}
+
+#Dashboard>.Quick-Links .link .button {
+ color: var(--accent-color);
+ background: rgba(0, 125, 252, 0.25);
+ border-radius: 50%
+}
+
+#Dashboard:after {
+ content: '';
+ grid-column: 1 span 3
+}
+
+#Activity {
+ @apply --layout-vertical;
+ @apply --layout-center-center
+}
+
+#Activity:before, #Activity:after {
+ font-size: 3em;
+ opacity: .3;
+ font-family: Google Sans
+}
+
+#Activity:before {
+ content: ":(";
+ font-size: 6em;
+ transform: rotateZ(90deg)
+}
+
+#Activity:after {
+ content: "No content here yet..."
+}
+
+[hides] {
+ transition: opacity .25s
+}
+
+[hidden] {
+ opacity: 0;
+ pointer-events: none
+}
+
+[hidden]:not([hides]) {
+ display: none
+}
+
a[href] {
text-decoration: none;
color: initial;
}
+
a[href]:hover {
color: var(--paper-blue-700);
text-decoration: underline;
diff --git a/components/centraldashboard/public/components/main-page.js b/components/centraldashboard/public/components/main-page.js
index c4bffa2a63e..a7b2ccdd1e4 100644
--- a/components/centraldashboard/public/components/main-page.js
+++ b/components/centraldashboard/public/components/main-page.js
@@ -1,6 +1,3 @@
-/* eslint-disable max-len */
-import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
-
import '@polymer/app-layout/app-drawer/app-drawer.js';
import '@polymer/app-layout/app-drawer-layout/app-drawer-layout.js';
import '@polymer/app-layout/app-header/app-header.js';
@@ -12,7 +9,8 @@ import '@polymer/app-route/app-route.js';
import '@polymer/iron-icons/iron-icons.js';
import '@polymer/iron-collapse/iron-collapse.js';
import '@polymer/iron-selector/iron-selector.js';
-import '@polymer/iron-flex-layout/iron-flex-layout';
+import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
+import '@polymer/iron-flex-layout/iron-flex-layout.js';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-tabs/paper-tabs.js';
import '@polymer/paper-item/paper-item.js';
@@ -25,112 +23,135 @@ import '@polymer/neon-animation/neon-animated-pages.js';
import '@polymer/neon-animation/animations/fade-in-animation.js';
import '@polymer/neon-animation/animations/fade-out-animation.js';
+import {html, PolymerElement} from '@polymer/polymer/polymer-element.js';
+
import css from './main-page.css';
+
import template from './main-page.pug';
+import './dashboard-view.js';
+import './activity-view.js';
+
/**
* Entry point for application UI.
*/
export class MainPage extends PolymerElement {
static get template() {
- return html([` ${template()}`]);
+ return html([`
+ ${template()}
+ `]);
}
static get properties() {
return {
- links: {
+ page: String,
+ routeData: Object,
+ subRouteData: Object,
+ iframeRoute: Object,
+ menuLinks: {
type: Array,
value: [
- {text: 'Home', defaultPage: true, hasDivider: true},
{
- link: 'https://www.kubeflow.org/docs/about/kubeflow/',
+ iframeUrl: 'https://www.kubeflow.org/docs/about/kubeflow/',
text: 'Kubeflow docs',
+ href: '/docs',
},
- {link: '/jupyter/', text: 'Notebooks'},
- {link: '/tfjobs/ui/', text: 'TFJob Dashboard'},
- {link: '/katib/', text: 'Katib Dashboard'},
- {link: '/pipeline/', text: 'Pipeline Dashboard'},
- ],
- },
- gettingStartedItems: {
- type: Array,
- value: [
{
- text: 'Getting started with Kubeflow',
- desc: 'Quickly get running with your ML workflow on an existing Kubernetes installation',
- link: 'https://www.kubeflow.org/docs/started/getting-started/',
- icon: 'launch',
- }, {
- text: 'Microk8s for Kubeflow',
- desc: 'Quickly get Kubeflow running locally on native hypervisors',
- link: 'https://www.kubeflow.org/docs/started/getting-started-multipass/',
- icon: 'launch',
- }, {
- text: 'Minikube for Kubeflow',
- desc: 'Quickly get Kubeflow running locally',
- link: 'https://www.kubeflow.org/docs/started/getting-started-minikube/',
- icon: 'launch',
- }, {
- text: 'Kubernetes Engine for Kubeflow',
- desc: 'Get Kubeflow running on Google Cloud Platform. This guide is a quickstart to deploying Kubeflow on Google Kubernetes Engine',
- link: 'https://www.kubeflow.org/docs/started/getting-started-gke/',
- icon: 'launch',
- }, {
- text: 'Requirements for Kubeflow',
- desc: 'Get more detailed information about using Kubeflow and its components',
- link: 'https://www.kubeflow.org/docs/started/requirements/',
- icon: 'launch',
+ iframeUrl: '/jupyter/',
+ text: 'Notebooks',
+ href: '/notebooks',
+ },
+ {
+ iframeUrl: '/tfjobs/ui/',
+ text: 'TFJob Dashboard',
+ href: '/tjob-dashboard',
},
- ],
- },
- quickLinks: {
- type: Array,
- value: [
{
- text: 'Open docs',
- link: 'https://www.kubeflow.org/docs/started/getting-started/',
+ iframeUrl: '/katib/',
+ text: 'Katib Dashboard',
+ href: '/katib-dashboard',
},
{
- text: 'Open Github',
- link: 'https://github.com/kubeflow/kubeflow',
+ iframeUrl: '/pipeline/',
+ text: 'Pipeline Dashboard',
+ href: '/pipeline-dashboard',
},
],
},
+ hideToolbar: {type: Boolean, value: false},
sidebarItemIndex: {type: Number, value: 0},
- primaryViewIndex: {type: Number, value: 0},
- homeOrIframeViewIndex: {type: Number, value: 0},
- url: {type: String, value: ''},
+ iframeUrl: {type: String, value: ''},
buildVersion: {type: String, value: '0.4.1'},
dashVersion: {type: String, value: VERSION},
- _devMode: {type: Boolean, value: false},
+ _devMode: {type: Boolean, value: DEVMODE},
};
}
- openExternalLink(href) {
- const a = document.createElement('a');
- a.href = href;
- a.target = '_blank';
- a.click();
+ /**
+ * Array of strings describing multi-property observer methods and their
+ * dependant properties
+ */
+ static get observers() {
+ return [
+ '_routePageChanged(routeData.page)',
+ ];
}
- openQuickLink(e) {
- const {link} = e.model.item;
- this.openExternalLink(link);
- }
-
- openLink(e) {
- const {link, defaultPage} = e.model.item;
- this.homeOrIframeViewIndex = defaultPage ? 0 : 1;
- if (defaultPage) return;
- this.url = link;
+ /**
+ * Intercepts any external links and ensures that they are captured in
+ * the route and sent to the iframe source.
+ * @param {MouseEvent} e
+ */
+ openInIframe(e) {
+ const url = new URL(e.currentTarget.href);
+ window.history.pushState({}, null, `_${url.pathname}`);
+ window.dispatchEvent(new CustomEvent('location-changed'));
+ e.preventDefault();
}
toggleSidebar() {
this.$.MainDrawer.toggle();
}
- isZero(i) {
- return i === 0;
+ /**
+ * Handles route changes by evaluating the page path component
+ * @param {string} newPage
+ */
+ _routePageChanged(newPage) {
+ this.hideToolbar = false;
+ switch (newPage) {
+ case 'activity':
+ this.sidebarItemIndex = 0;
+ this.page = 'activity';
+ break;
+ case '_': // iframe case
+ this._setIframeFromRoute(this.subRouteData.path);
+ break;
+ default:
+ this.sidebarItemIndex = 0;
+ this.page = 'dashboard';
+ }
+ }
+
+ /**
+ * Sets the iframeUrl and sidebarItem based on the subpage component
+ * provided.
+ * @param {string} href
+ */
+ _setIframeFromRoute(href) {
+ const menuLinkIndex =
+ this.menuLinks.findIndex((m) => m.href === this.subRouteData.path);
+ if (menuLinkIndex >= 0) {
+ this.page = 'iframe';
+ this.iframeUrl = this.menuLinks[menuLinkIndex].iframeUrl;
+ this.sidebarItemIndex = menuLinkIndex + 1;
+ this.hideToolbar = true;
+ } else {
+ this.sidebarItemIndex = 0;
+ this.page = 'dashboard';
+ }
}
}
diff --git a/components/centraldashboard/public/components/main-page.pug b/components/centraldashboard/public/components/main-page.pug
index e180c877b29..8430f40eee7 100644
--- a/components/centraldashboard/public/components/main-page.pug
+++ b/components/centraldashboard/public/components/main-page.pug
@@ -1,44 +1,44 @@
app-drawer-layout.flex(narrow='{{narrowMode}}')
+ app-location(route='{{route}}')
+ app-route(route='{{route}}', pattern='/:page', data='{{routeData}}',
+ tail='{{subRouteData}}')
+
app-drawer#MainDrawer(slot='drawer')
figure#Logo
img(alt='Kubeflow Logo', src='assets/kf-logo_64px.svg')
figcaption Kubeflow
- iron-selector#SidebarSelector(selected='{{sidebarItemIndex}}')
- template(is='dom-repeat', items='[[links]]')
- paper-item.menu-item(on-click='openLink') [[item.text]]
- template(is='dom-if', if='[[item.hasDivider]]')
- aside.divider
+ iron-selector(selected='{{sidebarItemIndex}}')
+ a(href='/', tabindex='-1')
+ paper-item.menu-item Home
+ aside.divider
+ template(is='dom-repeat', items='[[menuLinks]]')
+ a(href$='[[item.href]]', on-click='openInIframe', tabindex='-1')
+ paper-item.menu-item [[item.text]]
aside.flex
footer.footer
section.privacy Privacy
- section.build build version
+ section.build build version
span(title="Build: v[[buildVersion]] | Dashboard: v[[dashVersion]]") v[[buildVersion]]
app-header-layout(fullbleed)
- app-header(slot='header', fixed)
+ app-header(slot='header')
app-toolbar
- paper-tabs.bottom(selected='{{primaryViewIndex}}', hides, hidden$='[[!isZero(homeOrIframeViewIndex)]]')
- paper-tab Dashboard
- paper-tab(hidden$='[[!_devMode]]') Activity
- aside#NamespaceSelector(hidden$='[[!_devMode]]')
- paper-dropdown-menu(label='Namespace')
- paper-icon-button#Menu(icon='menu', on-click='toggleSidebar', hides, hidden$='[[!narrowMode]]')
- neon-animated-pages.flex.layout.vertical(selected='[[homeOrIframeViewIndex]]', entry-animation='fade-in-animation', exit-animation='fade-out-animation')
- neon-animated-pages#PrimaryView.flex.layout.vertical(selected='[[primaryViewIndex]]', entry-animation='fade-in-animation', exit-animation='fade-out-animation')
- article#Dashboard
- paper-card.Getting-Started(heading='Getting Started')
- template(is='dom-repeat', items='[[gettingStartedItems]]')
- paper-icon-item(on-click='openQuickLink')
- iron-icon(icon='[[item.icon]]', slot='item-icon')
- paper-item-body(two-line)
- .heading [[item.text]]
- aside(secondary) [[item.desc]]
- paper-card.thin.Quick-Links(heading='Quick Links')
- template(is='dom-repeat', items='[[quickLinks]]')
- article.link
- paper-item-body [[item.text]]
- paper-icon-button.button(icon='arrow-forward', alt='[[item.text]]', on-click='openQuickLink')
- article.link.more-coming
- paper-item-body More coming soon
- paper-icon-button.button(icon='arrow-forward', diabled)
- article#Activity
- iframe#PageFrame.flex(src='[[url]]')
+ header(hides, hidden$='[[hideToolbar]]')
+ paper-tabs.bottom(selected='[[page]]', attr-for-selected='page')
+ paper-tab(page='dashboard', link)
+ a.link(tabindex='-1', href='/') Dashboard
+ paper-tab(page='activity', link,hidden$='[[!_devMode]]')
+ a.link(tabindex='-1', href='/activity') Activity
+ aside#NamespaceSelector(hidden$='[[!_devMode]]')
+ paper-dropdown-menu(label='Namespace')
+ paper-icon-button#Menu(icon='menu',
+ on-click='toggleSidebar', hides,
+ hidden$='[[!narrowMode]]')
+ neon-animated-pages(selected='[[page]]', attr-for-selected='page',
+ entry-animation='fade-in-animation',
+ exit-animation='fade-out-animation')
+ neon-animatable(page='dashboard')
+ dashboard-view
+ neon-animatable(page='activity')
+ activity-view
+ neon-animatable#iframe-page(page='iframe')
+ iframe#PageFrame.flex(src='[[iframeUrl]]')
diff --git a/components/centraldashboard/webpack.config.js b/components/centraldashboard/webpack.config.js
index 9dc002c05f8..ba469c7da53 100644
--- a/components/centraldashboard/webpack.config.js
+++ b/components/centraldashboard/webpack.config.js
@@ -1,4 +1,3 @@
-/* eslint-disable linebreak-style,no-undef */
'use strict';
const {resolve} = require('path');
@@ -114,7 +113,6 @@ module.exports = {
'not op_mini all',
],
},
- debug: true,
},
]],
plugins: ['@babel/plugin-transform-runtime'],
@@ -153,6 +151,7 @@ module.exports = {
new CopyWebpackPlugin(POLYFILLS),
new DefinePlugin({
VERSION: JSON.stringify(PKG_VERSION),
+ DEVMODE: JSON.stringify(ENV == 'development'),
}),
new HtmlWebpackPlugin({
filename: resolve(DESTINATION, 'index.html'),
@@ -178,5 +177,8 @@ module.exports = {
devServer: {
port: 8081,
proxy: {'/api': 'http://localhost:8082'},
+ historyApiFallback: {
+ disableDotRule: true,
+ },
},
};