diff --git a/ui/Dockerfile b/ui/Dockerfile deleted file mode 100644 index bd535c34a7..0000000000 --- a/ui/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node - -RUN apt-get update -qq && apt-get install -y build-essential git-core - -ENV GOPATH=/go -ENV APP_HOME=$GOPATH/src/github.com/influxdata/enterprise -ENV UI_HOME=$APP_HOME/ui - -ARG GITHUB_TOKEN - -RUN git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/" - -ADD . $APP_HOME -WORKDIR $UI_HOME -RUN npm install diff --git a/ui/README.md b/ui/README.md deleted file mode 100644 index 3c44aee275..0000000000 --- a/ui/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Enterprise UI - -Here lies a collection of React components used in the Enterprise web app. - -Currently they're organized per "page", with each separate directly having it's own components/containers folders, index.js, etc. For example: - -/ui - /overview - /components - /containers - index.js - /chronograf - /components - /containers - index.js - -## Getting started - -This project uses Node v5. - -It depends on at least one private Node module. -In order to successfully `npm install`, you'll need an [npm authentication token](http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules). - -There are two ways to get an npm token. - -1. You can copy someone else's token (have them give you a copy of their `~/.npmrc`). -This is fine for most cases, and it's probably necessary for CI machines. -2. You can create an npm account and coordinate with Mark R to join the `influxdata` npm org. -You probably don't need this if you aren't ever going to create/publish a private Node module. - -### Development - -First, in the `ui/` folder run `npm install`. - -run `npm run build:dev` to start the webpack process which bundles the JS into `assets/javascripts/generated/`. - -### Tests - -We use mocha, sinon, chai, and React's TestUtils library. - -Run tests against jsdom from the command line: - -``` -npm run test # single run -npm run test:watch # re-run tests on file changes -``` - -Run tests in the browser: - -``` -# This starts a webpack process that you'll need to leave running. -# It rebuilds your tests on each file change. -npm run test:browser -# open http://localhost:7357/spec/test.html -``` - -### Production - -As before, `npm install` first. -Then `npm run build` to generate a production build into the `assets/javascripts/generated/` folder. - -If you want to run tests against the production build, `npm run test` will build the test files and run the tests in a headless Phantom browser. diff --git a/ui/spec/tasks/containers/TasksPageSpec.js b/ui/spec/tasks/containers/TasksPageSpec.js deleted file mode 100644 index b3ed3527ec..0000000000 --- a/ui/spec/tasks/containers/TasksPageSpec.js +++ /dev/null @@ -1,108 +0,0 @@ -import TasksPage from 'src/tasks/containers/TasksPage'; -import RebalanceModal from 'src/tasks/components/RebalanceModal'; -import React from 'react'; -import {mount, shallow} from 'enzyme'; - -const clusterID = '1000'; -const JOBS = [ - { - "source": "localhost:8088", - "dest": "localhost:8188", - "id": 20, - "status": "Planned" - }, - { - "source": "localhost:8088", - "dest": "localhost:8188", - "id": 10, - "status": "Planned" - }, -]; - -function setup(customProps = {}) { - const props = Object.assign({}, { - params: {clusterID}, - }, customProps); - return mount(); -} - -function setupShallow(customProps = {}) { - const props = Object.assign({}, { - params: {clusterID}, - }, customProps); - return shallow(); -} - -describe('Tasks.Containers.TasksPage', function() { - before(function() { this.server = sinon.fakeServer.create(); }); - after(function() { this.server.restore() }); - - beforeEach(function() { - this.server.respondWith("GET", /\/api\/int\/v1\/clusters\/1000\/authorized/, [ - 200, { "Content-Type": "application/json" }, '']); - this.server.respondWith("GET", '/api/v1/jobs', [ - 200, { "Content-Type": "application/json" }, '']); - }); - - describe('in intial state with no tasks', function() { - it('renders a an empty state for the task list', function() { - const wrapper = setupShallow(); - - const table = wrapper.find('.tasks-empty-state'); - expect(table.text()).to.match(/No tasks/); - }); - - it('renders a legend', function() { - const wrapper = setupShallow(); - - const legend = wrapper.find('.dot-legend'); - expect(legend.text()).to.match(/Running/); - expect(legend.text()).to.match(/Planned/); - }); - - it('renders a rebalance modal', function() { - const wrapper = setupShallow(); - - expect(wrapper.find(RebalanceModal).length).to.equal(1); - }); - - it('fetches a list of active tasks', function(done) { - const wrapper = setup(); - - this.server.respond(); - - setTimeout(() => { - const request = this.server.requests.find(r => r.url.match(/\/api\/v1\/jobs/)); - expect(request).to.be.ok; - expect(request.method).to.equal('GET'); - done(); - }); - }); - }); - - describe('when there are active tasks', function() { - it('renders a table row for each task'); - }); - - describe('with valid permissions', function() { - it('renders the rebalance button', function() { - const wrapper = setupShallow(); - - // TODO: get this working with FakeServer. For some reason I can't - // get it to respond and trigger the `then` block in componentDidMount. - // Lots of async issues with testing containers like this :(. - wrapper.setState({canRebalance: true}); - wrapper.update(); - - const button = wrapper.find('button.rebalance'); - - expect(button.length).to.equal(1); - }); - - it('sends a request to rebalance after the rebalance button is clicked'); - }); - - describe('with invalid permissions', function() { - it('doesn\'t render the rebalance button'); - }); -}); diff --git a/ui/src/index.js b/ui/src/index.js index 08a439e640..84173bd462 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -8,7 +8,6 @@ import AlertsApp from 'src/alerts'; import CheckSources from 'src/CheckSources'; import {HostsPage, HostPage} from 'src/hosts'; import {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage} from 'src/kapacitor'; -import TasksPage from 'src/tasks'; import DataExplorer from 'src/chronograf'; import {CreateSource, SourceForm, ManageSources} from 'src/sources'; import NotFound from 'src/shared/components/NotFound'; @@ -107,7 +106,6 @@ const Root = React.createClass({ - diff --git a/ui/src/shared/apis/index.js b/ui/src/shared/apis/index.js index b7c90c487e..76620ea455 100644 --- a/ui/src/shared/apis/index.js +++ b/ui/src/shared/apis/index.js @@ -35,435 +35,6 @@ export function deleteSource(source) { }); } -export function updateCluster(clusterID, displayName) { - return AJAX({ - url: `/api/int/v1/clusters/${clusterID}`, - method: 'PUT', - data: { - display_name: displayName, - }, - }); -} - -export function getDatabaseManager(clusterID, dbName) { - return AJAX({ - url: `/api/int/v1/${clusterID}/databases/${dbName}`, - }); -} - -export function createDatabase({database, rpName, duration, replicaN}) { - const params = new window.URLSearchParams(); - params.append('name', database); - params.append('retention-policy', rpName); - params.append('duration', duration); - params.append('replication-factor', replicaN); - - return AJAX({ - url: `/api/int/v1/databases`, - method: 'POST', - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - data: params, - }); -} - -export function getClusters() { - return AJAX({ - url: ``, - }); -} - -export function meShow() { - return AJAX({ - url: `/api/int/v1/me`, - }); -} - -export function meUpdate({firstName, lastName, email, password, confirmation, oldPassword}) { - return AJAX({ - url: `/api/int/v1/me`, - method: 'PUT', - data: { - first_name: firstName, - last_name: lastName, - email, - password, - confirmation, - old_password: oldPassword, - }, - }); -} - -export function getWebUsers() { - return AJAX({ - url: `/api/int/v1/users`, - }); -} - -export function createWebUser({firstName, lastName, email, password}) { - return AJAX({ - url: `/api/int/v1/users`, - method: 'POST', - data: { - first_name: firstName, - last_name: lastName, - email, - password, - }, - }); -} - -export function deleteWebUsers(userID) { - return AJAX({ - url: `/api/int/v1/users/${userID}`, - method: 'DELETE', - }); -} - -export function showUser(userID) { - return AJAX({ - url: `/api/int/v1/users/${userID}`, - }); -} - -export function updateUser(userID, {firstName, lastName, email, password, confirmation, admin}) { - return AJAX({ - url: `/api/int/v1/users/${userID}`, - method: 'PUT', - data: { - first_name: firstName, - last_name: lastName, - email, - password, - confirmation, - admin, - }, - }); -} - -export function getClusterAccounts(clusterID) { - return AJAX({ - url: metaProxy(clusterID, '/user'), - }); -} - -// can only be used for initial app setup. will create first cluster user -// with global admin permissions. -export function createClusterUserAtSetup(clusterID, username, password) { - return AJAX({ - url: `/api/v1/setup/cluster_user`, - method: 'POST', - data: { - cluster_id: clusterID, - username, - password, - }, - }); -} - -// can only be used for initial app setup -export function createWebAdmin({firstName, lastName, email, password, confirmation, clusterLinks}) { - return AJAX({ - url: `/api/v1/setup/admin`, - method: 'POST', - data: { - first_name: firstName, - last_name: lastName, - email, - password, - confirmation, - cluster_links: clusterLinks, - }, - }); -} - -// can only be used for initial app setup -export function updateClusterAtSetup(clusterID, displayName) { - return AJAX({ - url: `/api/v1/setup/clusters/${clusterID}`, - method: 'POST', - data: { - display_name: displayName, - }, - }); -} - -export function createClusterUser(clusterID, name, password) { - return AJAX({ - url: metaProxy(clusterID, '/user'), - method: 'POST', - data: { - action: 'create', - user: { - name, - password, - }, - }, - }); -} - -export function addUsersToRole(clusterID, name, users) { - return AJAX({ - url: metaProxy(clusterID, '/role'), - method: 'POST', - data: { - action: 'add-users', - role: { - name, - users, - }, - }, - }); -} - -export function getClusterAccount(clusterID, accountID) { - return AJAX({ - url: metaProxy(clusterID, `/user?name=${encodeURIComponent(accountID)}`), - }); -} - -export function updateClusterAccountPassword(clusterID, name, password) { - return AJAX({ - url: metaProxy(clusterID, '/user'), - method: 'POST', - data: { - action: 'change-password', - user: { - name, - password, - }, - }, - }); -} - - -export function getRoles(clusterID) { - return AJAX({ - url: metaProxy(clusterID, '/role'), - }); -} - -export function createRole(clusterID, roleName) { - return AJAX({ - url: metaProxy(clusterID, '/role'), - method: 'POST', - data: { - action: 'create', - role: { - name: roleName, - }, - }, - }); -} - -// TODO: update usage on index page -export function deleteClusterAccount(clusterID, accountName) { - return Promise.all([ - // Remove the cluster account from plutonium. - AJAX({ - url: metaProxy(clusterID, '/user'), - method: `POST`, - data: { - action: 'delete', - user: { - name: accountName, - }, - }, - }), - // Remove any cluster user links that are tied to this cluster account. - AJAX({ - url: `/api/int/v1/user_links/batch/${accountName}`, - method: 'DELETE', - }), - ]); -} - -export function createClusterAccount(clusterID, name, password) { - return AJAX({ - url: metaProxy(clusterID, '/user'), - method: `POST`, - data: { - action: 'create', - user: { - name, - password, - }, - }, - }); -} - -export function addAccountsToRole(clusterID, roleName, usernames) { - return AJAX({ - url: metaProxy(clusterID, '/role'), - method: 'POST', - data: { - action: 'add-users', - role: { - name: roleName, - users: usernames, - }, - }, - }); -} - -export function removeAccountsFromRole(clusterID, roleName, usernames) { - return AJAX({ - url: metaProxy(clusterID, '/role'), - method: 'POST', - data: { - action: 'remove-users', - role: { - name: roleName, - users: usernames, - }, - }, - }); -} - -export function addPermissionToRole(clusterID, roleName, permission) { - const permissions = buildPermissionForPlutonium(permission); - return AJAX({ - url: metaProxy(clusterID, '/role'), - method: 'POST', - data: { - action: 'add-permissions', - role: { - name: roleName, - permissions, - }, - }, - }); -} - -export function removePermissionFromRole(clusterID, roleName, permission) { - const permissions = buildPermissionForPlutonium(permission); - return AJAX({ - url: metaProxy(clusterID, '/role'), - method: 'POST', - data: { - action: 'remove-permissions', - role: { - name: roleName, - permissions, - }, - }, - }); -} - -export function removePermissionFromAccount(clusterID, username, permission) { - const permissions = buildPermissionForPlutonium(permission); - return AJAX({ - url: metaProxy(clusterID, '/user'), - method: 'POST', - data: { - action: 'remove-permissions', - user: { - name: username, - permissions, - }, - }, - }); -} - -// The structure that plutonium expects for adding permissions is a little unorthodox, -// where the permission(s) being added have to be under a resource key, e.g. -// { -// "db1": ["ViewAdmin"], -// "": ["CreateRole"] -// } -// This transforms a more web client-friendly permissions object into something plutonium understands. -function buildPermissionForPlutonium({name, resources}) { - return resources.reduce((obj, resource) => { - obj[resource] = [name]; - return obj; - }, {}); -} - -export function addPermissionToAccount(clusterID, name, permission, resources) { - const permissions = resources.reduce((obj, resource) => { - obj[resource] = [permission]; - return obj; - }, {}); - - return AJAX({ - url: metaProxy(clusterID, '/user'), - method: 'POST', - data: { - action: 'add-permissions', - user: { - name, - permissions, - }, - }, - }); -} - -export function deleteRole(clusterID, roleName) { - return AJAX({ - url: metaProxy(clusterID, '/role'), - method: 'POST', - data: { - action: 'delete', - role: { - name: roleName, - }, - }, - }); -} - -export function deleteUserClusterLink(clusterID, userClusterLinkID) { - return AJAX({ - url: `/api/int/v1/user_links/${userClusterLinkID}`, - method: `DELETE`, - }); -} - -export function getUserClusterLinks() { - return AJAX({ - url: `/api/int/v1/user_links`, - }); -} - -export function createUserClusterLink({userID, clusterID, clusterUser}) { - return AJAX({ - url: `/api/int/v1/user_links`, - method: 'POST', - data: { - user_id: userID, - cluster_user: clusterUser, - cluster_id: clusterID, - }, - }); -} - -export function getWebUsersByClusterAccount(clusterID, clusterAccount) { - return AJAX({ - url: `/api/int/v1/user_links/batch/${encodeURIComponent(clusterAccount)}`, - }); -} - -export function batchCreateUserClusterLink(userID, clusterLinks) { - return AJAX({ - url: `/api/int/v1/users/${userID}/cluster_links/batch`, - method: 'POST', - data: clusterLinks, - }); -} - -export function addWebUsersToClusterAccount(clusterID, clusterAccount, userIDs) { - return AJAX({ - url: `/api/int/v1/user_links/batch/${encodeURIComponent(clusterAccount)}`, - method: 'POST', - data: userIDs, - }); -} - -function metaProxy(clusterID, slug) { - return `/api/int/v1/meta${slug}`; -} - -// Kapacitor functions -// TODO: update kapacitor functions to assume only one kapacitor. waiting for @goller - export function getKapacitor(source) { return AJAX({ url: source.links.kapacitors, @@ -503,7 +74,6 @@ export function getKapacitorConfig(kapacitor) { return kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/config', ''); } -// updateKapacitorConfigSection will update one section in the Kapacitor config. export function updateKapacitorConfigSection(kapacitor, section, properties) { return AJAX({ method: 'POST', diff --git a/ui/src/tasks/components/RebalanceModal.js b/ui/src/tasks/components/RebalanceModal.js deleted file mode 100644 index 50da87b830..0000000000 --- a/ui/src/tasks/components/RebalanceModal.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, {PropTypes} from 'react'; - -const {func} = PropTypes; -const RebalanceModal = React.createClass({ - propTypes: { - onConfirmRebalance: func.isRequired, - }, - - render() { - return ( - - ); - }, -}); - -export default RebalanceModal; diff --git a/ui/src/tasks/components/Task.js b/ui/src/tasks/components/Task.js deleted file mode 100644 index c451879f96..0000000000 --- a/ui/src/tasks/components/Task.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, {PropTypes} from 'react'; - -const {shape, string, number} = PropTypes; -const Task = React.createClass({ - propTypes: { - task: shape({ - source: string, - dest: string, - id: number, - status: string, - }).isRequired, - }, - - render() { - const {id, source, dest, status} = this.props.task; - return ( - -
- Copy Shard{/* TODO: Copy Shard is hardcoded, change when he have more types of tasks */} - {source} - {dest} - {/* TODO: add killing a task into app when it exists in backend - Kill - */} - - ); - }, -}); - -export default Task; diff --git a/ui/src/tasks/containers/TasksPage.js b/ui/src/tasks/containers/TasksPage.js deleted file mode 100644 index f925d78bb5..0000000000 --- a/ui/src/tasks/containers/TasksPage.js +++ /dev/null @@ -1,185 +0,0 @@ -import React, {PropTypes} from 'react'; -import RebalanceModal from '../components/RebalanceModal'; -import Task from '../components/Task'; -import AJAX from 'utils/ajax'; -import {meShow} from 'shared/apis'; - -const REFRESH_INTERVAL = 2000; -const REBALANCE_PERMISSION = 'Rebalance'; - -const Tasks = React.createClass({ - propTypes: { - params: PropTypes.shape({ - clusterID: PropTypes.string.isRequired, - }).isRequired, - }, - - getInitialState() { - return { - isRebalancing: false, - canRebalance: false, - tasks: [], - }; - }, - - componentDidMount() { - this.fetchTasks(); - meShow().then(({data}) => { - const clusterAccount = data.cluster_links.find((cl) => cl.cluster_id === this.props.params.clusterID); - - if (!clusterAccount) { - return this.setState({canRebalance: false}); - } - - AJAX({ - url: `/api/int/v1/clusters/${this.props.params.clusterID}/meta/authorized?password=""&resource=""`, - params: { - name: clusterAccount.cluster_user, - permission: REBALANCE_PERMISSION, - }, - }).then(() => { - this.setState({canRebalance: true}); - }).catch(() => { - this.setState({canRebalance: false}); - }); - }); - }, - - fetchTasks() { - AJAX({ - url: '/api/v1/jobs', - }).then((resp) => { - const tasks = resp.data; - this.setState({tasks}); - - if (tasks.length) { - this.setState({isRebalancing: true}); - if (!this.intervalID) { - this.intervalID = setInterval(() => this.fetchTasks(), REFRESH_INTERVAL); - } - } else { - this.setState({isRebalancing: false}); - clearInterval(this.intervalID); - } - }); - }, - - handleRebalance() { - AJAX({ - url: `/clusters/${this.props.params.clusterID}/rebalance`, - method: 'POST', - }).then(() => { - this.fetchTasks(); - }).catch(() => { - // TODO: render flash message - }); - }, - - renderRebalanceButton() { - if (!this.state.canRebalance) { - return null; - } - - if (this.state.isRebalancing) { - return ( -
-
Rebalancing
-
-
- ); - } - - return ( - - ); - }, - - render() { - const {tasks} = this.state; - return ( -
-
-
-
-

- Tasks -

-
-
- {this.renderRebalanceButton()} -
-
-
-
-
-
- -
-
-

Running Tasks

-
-
    - {/* TODO: add back in when task histrory is introduced -
  • -
    -
    - Finished -
  • - */} -
  • -
    -
    - Running -
  • -
  • -
    -
    - Planned -
  • - {/* TODO: add back in when task histrory is introduced -
  • -
    -
    - Failed -
  • - */} -
-
-
- {tasks.length ? -
- - - - - - - - - - - - {tasks.map((task, index) => )} - -
StatusTypeSourceDestination
-
: -
-
- -

No tasks running

-
-
- } -
-
-
-
- -
- ); - }, -}); - -export default Tasks; diff --git a/ui/src/tasks/index.js b/ui/src/tasks/index.js deleted file mode 100644 index 82ba5db2ba..0000000000 --- a/ui/src/tasks/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import TasksPage from './containers/TasksPage'; -export default TasksPage;