Takin' out the trash
parent
729ac65367
commit
3b5c59ed15
|
@ -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
|
62
ui/README.md
62
ui/README.md
|
@ -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.
|
|
@ -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(<TasksPage {...props} />);
|
||||
}
|
||||
|
||||
function setupShallow(customProps = {}) {
|
||||
const props = Object.assign({}, {
|
||||
params: {clusterID},
|
||||
}, customProps);
|
||||
return shallow(<TasksPage {...props} />);
|
||||
}
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
|
@ -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({
|
|||
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
||||
<Route path="alert-rules/new" component={KapacitorRulePage} />
|
||||
</Route>
|
||||
<Route path="tasks" component={TasksPage} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Route>
|
||||
</Router>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
|
||||
const {func} = PropTypes;
|
||||
const RebalanceModal = React.createClass({
|
||||
propTypes: {
|
||||
onConfirmRebalance: func.isRequired,
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="modal fade" id="rebalanceModal" tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title" id="myModalLabel">This is a potentially heavy operation. <br/> Are you sure you want to rebalance?</h4>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-default" data-dismiss="modal">No</button>
|
||||
<button type="button" className="btn btn-success" data-dismiss="modal" onClick={this.props.onConfirmRebalance}>Yes, run it!</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default RebalanceModal;
|
|
@ -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 (
|
||||
<tr className="task" id={`task-${id}`}>
|
||||
<td><div className={`status-dot status-dot__${status.toLowerCase()}`}></div></td>
|
||||
<td>Copy Shard</td>{/* TODO: Copy Shard is hardcoded, change when he have more types of tasks */}
|
||||
<td>{source}</td>
|
||||
<td>{dest}</td>
|
||||
{/* TODO: add killing a task into app when it exists in backend
|
||||
<td className="text-right"><a href="#" data-toggle="modal" data-task-id={id} data-target="#killModal">Kill</a></td>
|
||||
*/}
|
||||
</tr>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default Task;
|
|
@ -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 (
|
||||
<div disabled={true} className="btn btn-sm btn-success rebalance">
|
||||
<div>Rebalancing</div>
|
||||
<div id="rebalance" className="icon sync"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button className="btn btn-sm btn-primary rebalance" data-toggle="modal" data-target="#rebalanceModal">
|
||||
Rebalance
|
||||
</button>
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
const {tasks} = this.state;
|
||||
return (
|
||||
<div>
|
||||
<div className="enterprise-header">
|
||||
<div className="enterprise-header__container">
|
||||
<div className="enterprise-header__left">
|
||||
<h1>
|
||||
Tasks
|
||||
</h1>
|
||||
</div>
|
||||
<div className="enterprise-header__right">
|
||||
{this.renderRebalanceButton()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
|
||||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading u-flex u-jc-space-between u-ai-center">
|
||||
<h2 className="panel-title">Running Tasks</h2>
|
||||
<div className="dot-container">
|
||||
<ul className="dot-legend">
|
||||
{/* TODO: add back in when task histrory is introduced
|
||||
<li>
|
||||
<div className="status-dot status-dot__finished">
|
||||
</div>
|
||||
Finished
|
||||
</li>
|
||||
*/}
|
||||
<li>
|
||||
<div className="status-dot status-dot__running">
|
||||
</div>
|
||||
Running
|
||||
</li>
|
||||
<li>
|
||||
<div className="status-dot status-dot__planned">
|
||||
</div>
|
||||
Planned
|
||||
</li>
|
||||
{/* TODO: add back in when task histrory is introduced
|
||||
<li>
|
||||
<div className="status-dot status-dot__failed">
|
||||
</div>
|
||||
Failed
|
||||
</li>
|
||||
*/}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{tasks.length ?
|
||||
<div className="panel-body">
|
||||
<table className="table task-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Type</th>
|
||||
<th>Source</th>
|
||||
<th>Destination</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tasks.map((task, index) => <Task key={index} task={task} />)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div> :
|
||||
<div className="panel-body">
|
||||
<div className="generic-empty-state tasks-empty-state">
|
||||
<span className="icon cubo-node"></span>
|
||||
<h4>No tasks running</h4>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RebalanceModal onConfirmRebalance={this.handleRebalance} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default Tasks;
|
|
@ -1,2 +0,0 @@
|
|||
import TasksPage from './containers/TasksPage';
|
||||
export default TasksPage;
|
Loading…
Reference in New Issue