Introduce delete confirmation for database

pull/1029/head
Andrew Watkins 2017-03-15 13:57:28 -07:00
parent 9d63d2ac13
commit 94e4ea3d4b
7 changed files with 215 additions and 36 deletions

View File

@ -7,6 +7,7 @@ import {
createUser as createUserAJAX,
createRole as createRoleAJAX,
createDatabase as createDatabaseAJAX,
deleteDatabase as deleteDatabaseAJAX,
deleteUser as deleteUserAJAX,
deleteRole as deleteRoleAJAX,
updateRole as updateRoleAJAX,
@ -180,6 +181,28 @@ export const filterRoles = (text) => ({
},
})
export const startDeleteDatabase = (database) => ({
type: 'START_DELETE_DATABASE',
payload: {
database,
},
})
export const updateDatabaseDeleteCode = (database, deleteCode) => ({
type: 'UPDATE_DATABASE_DELETE_CODE',
payload: {
database,
deleteCode,
},
})
export const removeDatabaseDeleteCode = (database) => ({
type: 'REMOVE_DATABASE_DELETE_CODE',
payload: {
database,
},
})
// async actions
export const loadUsersAsync = (url) => async (dispatch) => {
const {data} = await getUsersAJAX(url)
@ -233,6 +256,7 @@ export const createRoleAsync = (url, role) => async (dispatch) => {
export const createDatabaseAsync = (url, database) => async (dispatch) => {
try {
// TODO: implement once server is up
// const {data} = await createDatabaseAJAX(url, database)
dispatch(publishNotification('success', 'Database created successfully'))
// dispatch(syncDatabase(database, {...data, id: uuid.v4()}))
@ -268,6 +292,18 @@ export const deleteUserAsync = (user, addFlashMessage) => (dispatch) => {
deleteUserAJAX(user.links.self, addFlashMessage, user.name)
}
export const deleteDatabaseAsync = (url, database) => (dispatch) => {
dispatch(removeDatabase(database))
dispatch(publishNotification('success', 'Database deleted'))
// TODO: implement once server is up
// try {
// await deleteDatabaseAJAX(url, database.name)
// } catch (error) {
// dispatch(publishNotification('error', `Failed to delete database: ${error.data.message}`))
// }
}
export const updateRoleUsersAsync = (role, users) => async (dispatch) => {
try {
const {data} = await updateRoleAJAX(role.links.self, users, role.permissions)

View File

@ -112,6 +112,18 @@ export const deleteUser = async (url, addFlashMessage, username) => {
}
}
export const deleteDatabase = async (url, name) => {
try {
return await AJAX({
method: 'DELETE',
url: `${url}/${name}`,
})
} catch (error) {
console.error(error)
throw error
}
}
export const updateRole = async (url, users, permissions) => {
try {
return await AJAX({

View File

@ -9,6 +9,8 @@ const DatabaseManager = ({
onKeyDownDatabase,
onCancelDatabase,
onConfirmDatabase,
onStartDeleteDatabase,
onDatabaseDeleteConfirm,
}) => {
return (
<div className="panel panel-info">
@ -27,6 +29,8 @@ const DatabaseManager = ({
onKeyDownDatabase={onKeyDownDatabase}
onCancelDatabase={onCancelDatabase}
onConfirmDatabase={onConfirmDatabase}
onStartDeleteDatabase={onStartDeleteDatabase}
onDatabaseDeleteConfirm={onDatabaseDeleteConfirm}
/>
)
}
@ -49,6 +53,8 @@ DatabaseManager.propTypes = {
onKeyDownDatabase: func,
onCancelDatabase: func,
onConfirmDatabase: func,
onStartDeleteDatabase: func,
onDatabaseDeleteConfirm: func,
}
export default DatabaseManager

View File

@ -2,6 +2,12 @@ import React, {PropTypes} from 'react'
import {DatabaseRow} from 'src/admin/components/DatabaseRow'
import ConfirmButtons from 'src/admin/components/ConfirmButtons'
const {
arrayOf,
func,
shape,
} = PropTypes
const DatabaseTable = ({
database,
retentionPolicies,
@ -9,6 +15,8 @@ const DatabaseTable = ({
onKeyDownDatabase,
onCancelDatabase,
onConfirmDatabase,
onStartDeleteDatabase,
onDatabaseDeleteConfirm,
}) => {
return (
<div className="db-manager">
@ -18,6 +26,8 @@ const DatabaseTable = ({
onKeyDown={onKeyDownDatabase}
onCancel={onCancelDatabase}
onConfirm={onConfirmDatabase}
onStartDelete={onStartDeleteDatabase}
onDatabaseDeleteConfirm={onDatabaseDeleteConfirm}
/>
<div className="db-manager-table">
<table className="table v-center admin-table">
@ -50,7 +60,26 @@ const DatabaseTable = ({
)
}
const DatabaseTableHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) => {
DatabaseTable.propTypes = {
onEditDatabase: func,
database: shape(),
retentionPolicies: arrayOf(shape()),
onKeyDownDatabase: func,
onCancelDatabase: func,
onConfirmDatabase: func,
onStartDeleteDatabase: func,
onDatabaseDeleteConfirm: func,
}
const DatabaseTableHeader = ({
database,
onEdit,
onKeyDown,
onConfirm,
onCancel,
onStartDelete,
onDatabaseDeleteConfirm,
}) => {
if (database.isEditing) {
return (
<EditHeader
@ -63,22 +92,78 @@ const DatabaseTableHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel})
)
}
return <Header database={database} />
return (
<Header
database={database}
onStartDelete={onStartDelete}
onDatabaseDeleteConfirm={onDatabaseDeleteConfirm}
/>
)
}
const Header = ({database}) => (
<div className="db-manager-header">
<h4>{database.name}</h4>
DatabaseTableHeader.propTypes = {
onEdit: func,
database: shape(),
onKeyDown: func,
onCancel: func,
onConfirm: func,
onStartDelete: func,
onDatabaseDeleteConfirm: func,
}
const Header = ({
database,
onStartDelete,
onDatabaseDeleteConfirm,
}) => {
const confirmStyle = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}
const buttons = (
<div className="text-right">
<button className="btn btn-xs btn-danger">
<button className="btn btn-xs btn-danger" onClick={() => onStartDelete(database)}>
Delete
</button>
<button className="btn btn-xs btn-primary">
{`Add retention policy`}
</button>
</div>
</div>
)
)
const deleteConfirm = (
<div style={confirmStyle}>
<div className="admin-table--delete-cell">
<input
className="form-control"
name="name"
type="text"
value={database.deleteCode || ''}
placeholder="type DELETE to confirm"
onChange={(e) => onDatabaseDeleteConfirm(database, e)}
onKeyDown={(e) => onDatabaseDeleteConfirm(database, e)}
autoFocus={true}
/>
</div>
<ConfirmButtons item={database} onConfirm={() => {}} onCancel={() => {}} />
</div>
)
return (
<div className="db-manager-header">
<h4>{database.name}</h4>
{database.hasOwnProperty('deleteCode') ? deleteConfirm : buttons}
</div>
)
}
Header.propTypes = {
database: shape(),
onStartDelete: func,
onDatabaseDeleteConfirm: func,
}
const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) => (
<div className="db-manager-header">
@ -100,25 +185,6 @@ const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) => (
</div>
)
const {
arrayOf,
func,
shape,
} = PropTypes
DatabaseTable.propTypes = {
onEditDatabase: func,
database: shape(),
retentionPolicies: arrayOf(shape()),
onKeyDownDatabase: func,
onCancelDatabase: func,
onConfirmDatabase: func,
}
Header.propTypes = {
database: shape(),
}
EditHeader.propTypes = {
database: shape(),
onEdit: func,
@ -127,12 +193,4 @@ EditHeader.propTypes = {
onConfirm: func,
}
DatabaseTableHeader.propTypes = {
onEdit: func,
database: shape(),
onKeyDown: func,
onCancel: func,
onConfirm: func,
}
export default DatabaseTable

View File

@ -9,6 +9,7 @@ class DatabaseManagerPage extends Component {
constructor(props) {
super(props)
this.handleKeyDownDatabase = ::this.handleKeyDownDatabase
this.handleDatabaseDeleteConfirm = ::this.handleDatabaseDeleteConfirm
}
componentDidMount() {
@ -30,18 +31,35 @@ class DatabaseManagerPage extends Component {
}
}
handleDatabaseDeleteConfirm(database, e) {
const {key, target: {value}} = e
const {actions, source} = this.props
if (key === 'Escape') {
return actions.removeDatabaseDeleteCode(database)
}
if (key === 'Enter' && database.deleteCode === 'DELETE') {
return actions.deleteDatabaseAsync(source, database)
}
actions.updateDatabaseDeleteCode(database, value)
}
render() {
const {databases, retentionPolicies, actions} = this.props
return (
<DatabaseManager
addDatabase={actions.addDatabase}
databases={databases}
retentionPolicies={retentionPolicies}
onKeyDownDatabase={this.handleKeyDownDatabase}
onDatabaseDeleteConfirm={this.handleDatabaseDeleteConfirm}
addDatabase={actions.addDatabase}
onEditDatabase={actions.editDatabase}
onCancelDatabase={actions.removeDatabase}
onConfirmDatabase={actions.createDatabaseAsync}
onStartDeleteDatabase={actions.startDeleteDatabase}
/>
)
}
@ -77,6 +95,9 @@ DatabaseManagerPage.propTypes = {
createDatabaseAsync: func,
addDatabase: func,
removeDatabase: func,
startDeleteDatabase: func,
updateDatabaseDeleteCode: func,
removeDatabaseDeleteCode: func,
}),
}

View File

@ -178,6 +178,35 @@ export default function admin(state = initialState, action) {
return {...state, ...newState}
}
case 'START_DELETE_DATABASE': {
const {database} = action.payload
const newState = {
databases: state.databases.map(db => db.id === database.id ? {...db, deleteCode: ''} : db),
}
return {...state, ...newState}
}
case 'UPDATE_DATABASE_DELETE_CODE': {
const {database, deleteCode} = action.payload
const newState = {
databases: state.databases.map(db => db.id === database.id ? {...db, deleteCode} : db),
}
return {...state, ...newState}
}
case 'REMOVE_DATABASE_DELETE_CODE': {
const {database} = action.payload
delete database.deleteCode
const newState = {
databases: state.databases.map(db => db.id === database.id ? {...database} : db),
}
return {...state, ...newState}
}
case 'LOAD_QUERIES': {
return {...state, ...action.payload}
}

View File

@ -121,6 +121,23 @@
}
}
.admin-table--delete-cell {
margin: 0 !important;
display: flex !important;
justify-content: space-between;
> input {
height: 30px;
padding: 0 9px;
flex-grow: 1;
margin: 0 2px;
min-width: 110px;
&:first-child {margin-left: 0;}
&:last-child {margin-right: 0;}
}
}
.db-manager-header {
height: 32px;
display: flex;