diff --git a/ui/src/actions/AppAction.js b/ui/src/actions/AppAction.js
new file mode 100644
index 0000000..c70a5a2
--- /dev/null
+++ b/ui/src/actions/AppAction.js
@@ -0,0 +1,27 @@
+import dispatcher from '../stores/dispatcher';
+import config from 'react-global-configuration';
+import axios from 'axios';
+
+/** Fetches all applications. */
+export function fetchApps() {
+ axios.get(config.get('url') + 'application').then((resp) => {
+ dispatcher.dispatch({type: 'UPDATE_APPS', payload: resp.data});
+ });
+}
+
+/**
+ * Delete an application by id.
+ * @param {int} id the application id
+ */
+export function deleteApp(id) {
+ axios.delete(config.get('url') + 'application/' + id).then(fetchApps);
+}
+
+/**
+ * Create an application
+ * @param {string} name the application name
+ * @param {string} description the description of the application.
+ */
+export function createApp(name, description) {
+ axios.post(config.get('url') + 'application', {name, description}).then(fetchApps);
+}
diff --git a/ui/src/pages/Applications.js b/ui/src/pages/Applications.js
new file mode 100644
index 0000000..5f05c66
--- /dev/null
+++ b/ui/src/pages/Applications.js
@@ -0,0 +1,154 @@
+import React, {Component} from 'react';
+import Grid from 'material-ui/Grid';
+import Table, {TableBody, TableCell, TableHead, TableRow} from 'material-ui/Table';
+import Paper from 'material-ui/Paper';
+import Button from 'material-ui/Button';
+import IconButton from 'material-ui/IconButton';
+import Dialog, {DialogActions, DialogContent, DialogContentText, DialogTitle} from 'material-ui/Dialog';
+import TextField from 'material-ui/TextField';
+import Tooltip from 'material-ui/Tooltip';
+import AppStore from '../stores/AppStore';
+import ToggleVisibility from '../component/ToggleVisibility';
+import ConfirmDialog from '../component/ConfirmDialog';
+import * as AppAction from '../actions/AppAction';
+import DefaultPage from '../component/DefaultPage';
+import PropTypes from 'prop-types';
+import Delete from 'material-ui-icons/Delete';
+
+class Applications extends Component {
+ constructor() {
+ super();
+ this.state = {apps: [], createDialog: false, deleteId: -1};
+ }
+
+ componentWillMount() {
+ AppStore.on('change', this.updateApps);
+ this.updateApps();
+ }
+
+ componentWillUnmount() {
+ AppStore.removeListener('change', this.updateApps);
+ }
+
+ updateApps = () => this.setState({...this.state, apps: AppStore.get()});
+
+ showCreateDialog = () => this.setState({...this.state, createDialog: true});
+ hideCreateDialog = () => this.setState({...this.state, createDialog: false});
+
+ showCloseDialog = (deleteId) => this.setState({...this.state, deleteId});
+ hideCloseDialog = () => this.setState({...this.state, deleteId: -1});
+
+ render() {
+ const {apps, createDialog, deleteId} = this.state;
+ return (
+
+
+
+
+
+
+ Name
+ Token
+ Description
+
+
+
+
+ {apps.map((app) => {
+ return (
+ this.showCloseDialog(app.id)}/>
+ );
+ })}
+
+
+
+
+ {createDialog && }
+ {deleteId !== -1 && AppAction.deleteApp(deleteId)}
+ />}
+
+ );
+ }
+}
+
+class Row extends Component {
+ static propTypes = {
+ name: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired,
+ fDelete: PropTypes.func.isRequired,
+ };
+
+ render() {
+ const {name, value, description, fDelete} = this.props;
+ return (
+
+ {name}
+
+
+
+ {description}
+
+
+
+
+ );
+ }
+}
+
+class AddDialog extends Component {
+ static propTypes = {
+ fClose: PropTypes.func.isRequired,
+ fOnSubmit: PropTypes.func.isRequired,
+ };
+
+ constructor() {
+ super();
+ this.state = {name: '', description: ''};
+ }
+
+ handleChange(propertyName, event) {
+ const state = this.state;
+ state[propertyName] = event.target.value;
+ this.setState(state);
+ }
+
+ render() {
+ const {fClose, fOnSubmit} = this.props;
+ const {name, description} = this.state;
+ const submitEnabled = this.state.name.length !== 0;
+ const submitAndClose = () => {
+ fOnSubmit(name, description);
+ fClose();
+ };
+ return (
+
+ );
+ }
+}
+
+export default Applications;
diff --git a/ui/src/stores/AppStore.js b/ui/src/stores/AppStore.js
new file mode 100644
index 0000000..6f34263
--- /dev/null
+++ b/ui/src/stores/AppStore.js
@@ -0,0 +1,33 @@
+import {EventEmitter} from 'events';
+import dispatcher from './dispatcher';
+
+class AppStore extends EventEmitter {
+ constructor() {
+ super();
+ this.apps = [];
+ }
+
+ get() {
+ return this.apps;
+ }
+
+ getById(id) {
+ return this.apps.find((app) => app.id === id);
+ }
+
+ getName(id) {
+ const app = this.getById(id);
+ return id === -1 ? 'All Messages' : app !== undefined ? app.name : 'unknown';
+ }
+
+ handle(data) {
+ if (data.type === 'UPDATE_APPS') {
+ this.apps = data.payload;
+ this.emit('change');
+ }
+ }
+}
+
+const store = new AppStore();
+dispatcher.register(store.handle.bind(store));
+export default store;