mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Feature: Delete apps (#323)
This commit is contained in:
parent
80d5ec536d
commit
22e3002265
8 changed files with 106 additions and 4 deletions
|
|
@ -49,6 +49,12 @@ class AppsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
app = App.find params[:id]
|
||||
app.update(current_version: nil)
|
||||
app.destroy
|
||||
end
|
||||
|
||||
def slugs
|
||||
@app = App.find_by(slug: params[:slug])
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
class App < ApplicationRecord
|
||||
belongs_to :organization
|
||||
has_many :data_queries, dependent: :destroy
|
||||
has_many :data_sources, dependent: :destroy
|
||||
has_many :app_users, dependent: :destroy
|
||||
has_many :app_versions, dependent: :destroy
|
||||
has_many :folder_apps, dependent: :destroy
|
||||
belongs_to :current_version, class_name: "AppVersion", optional: true
|
||||
belongs_to :user, optional: true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Rails.application.routes.draw do
|
||||
resources :apps, only: %i[index create show update] do
|
||||
resources :apps, only: %i[index create show update destroy] do
|
||||
resources :versions, only: %i[index create update]
|
||||
|
||||
get '/users', to: 'apps#users'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { folderService } from '@/_services';
|
|||
import { toast } from 'react-toastify';
|
||||
|
||||
export const AppMenu = function AppMenu({
|
||||
app, folders, foldersChanged
|
||||
app, folders, foldersChanged, deleteApp
|
||||
}) {
|
||||
|
||||
const [addToFolder, setAddToFolder] = useState(false);
|
||||
|
|
@ -40,7 +40,7 @@ export const AppMenu = function AppMenu({
|
|||
|
||||
return <OverlayTrigger
|
||||
trigger="click"
|
||||
placement="right"
|
||||
placement="top"
|
||||
rootClose
|
||||
onToggle={(status) => handleToggle(status)}
|
||||
overlay={
|
||||
|
|
@ -50,6 +50,9 @@ export const AppMenu = function AppMenu({
|
|||
{!addToFolder &&
|
||||
<div className="field mb-2">
|
||||
<span role="button" onClick={() => setAddToFolder(true)}>Add to folder </span>
|
||||
<br></br>
|
||||
<br></br>
|
||||
<span class="my-3 text-danger" role="button" onClick={() => deleteApp()}>Delete app </span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { AppMenu } from './AppMenu';
|
|||
import { BlankPage } from './BlankPage';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import { renderTooltip } from '@/_helpers/appUtils';
|
||||
import { ConfirmDialog } from '@/_components';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
class HomePage extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -18,6 +20,7 @@ class HomePage extends React.Component {
|
|||
isLoading: true,
|
||||
creatingApp: false,
|
||||
currentFolder: {},
|
||||
showAppDeletionConfirmation: false,
|
||||
apps: [],
|
||||
folders: [],
|
||||
meta: {
|
||||
|
|
@ -56,6 +59,7 @@ class HomePage extends React.Component {
|
|||
}
|
||||
|
||||
pageChanged = (page) => {
|
||||
this.setState({ currentPage: page });
|
||||
this.fetchApps(page, this.state.currentFolder.id);
|
||||
}
|
||||
|
||||
|
|
@ -77,13 +81,49 @@ class HomePage extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
deleteApp = (app) => {
|
||||
this.setState({ showAppDeletionConfirmation: true, appToBeDeleted: app })
|
||||
}
|
||||
|
||||
executeAppDeletion = () => {
|
||||
this.setState({ isDeletingApp: true });
|
||||
appService.deleteApp(this.state.appToBeDeleted.id).then((data) => {
|
||||
toast.info('App deleted successfully.', {
|
||||
hideProgressBar: true,
|
||||
position: 'top-center'
|
||||
});
|
||||
this.setState({
|
||||
isDeletingApp: false,
|
||||
appToBeDeleted: null,
|
||||
showAppDeletionConfirmation: false
|
||||
});
|
||||
this.fetchApps(this.state.currentPage || 0, this.state.currentFolder.id)
|
||||
}).catch(({ error }) => {
|
||||
toast.error('Could not delete the app.', { hideProgressBar: true, position: 'top-center' });
|
||||
this.setState({
|
||||
isDeletingApp: false,
|
||||
appToBeDeleted: null,
|
||||
showAppDeletionConfirmation: false
|
||||
});
|
||||
});
|
||||
;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
apps, isLoading, creatingApp, meta, currentFolder
|
||||
apps, isLoading, creatingApp, meta, currentFolder, showAppDeletionConfirmation, isDeletingApp
|
||||
} = this.state;
|
||||
return (
|
||||
<div className="wrapper home-page">
|
||||
|
||||
<ConfirmDialog
|
||||
show={showAppDeletionConfirmation}
|
||||
message={'The app and the associated data will be permanently deleted, do you want to continue?'}
|
||||
confirmButtonLoading={isDeletingApp}
|
||||
onConfirm={() => this.executeAppDeletion()}
|
||||
onCancel={() => {}}
|
||||
/>
|
||||
|
||||
<Header
|
||||
|
||||
/>
|
||||
|
|
@ -189,6 +229,7 @@ class HomePage extends React.Component {
|
|||
app={app}
|
||||
folders={this.state.folders}
|
||||
foldersChanged={this.foldersChanged}
|
||||
deleteApp={() => this.deleteApp(app)}
|
||||
/>
|
||||
</td>
|
||||
</tr>))
|
||||
|
|
|
|||
43
frontend/src/_components/ConfirmDialog.jsx
Normal file
43
frontend/src/_components/ConfirmDialog.jsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
|
||||
export function ConfirmDialog({
|
||||
show, message, onConfirm, onCancel, confirmButtonLoading
|
||||
}) {
|
||||
const [showModal, setShow] = useState(show);
|
||||
|
||||
useEffect(() => {
|
||||
setShow(show);
|
||||
}, [show]);
|
||||
|
||||
const handleClose = () => {
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal show={showModal} onHide={handleClose} size="sm" centered={true}>
|
||||
<div className="modal-status bg-danger"></div>
|
||||
<Modal.Body>{message}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" autoFocus className={`${confirmButtonLoading ? 'btn-loading' : ''}`} onClick={handleConfirm}>
|
||||
Yes
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './PrivateRoute';
|
||||
export * from './Pagination';
|
||||
export * from './Header';
|
||||
export * from './ConfirmDialog';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { authHeader, handleResponse } from '@/_helpers';
|
|||
export const appService = {
|
||||
getAll,
|
||||
createApp,
|
||||
deleteApp,
|
||||
getApp,
|
||||
getAppBySlug,
|
||||
saveApp,
|
||||
|
|
@ -31,6 +32,11 @@ function getApp(id) {
|
|||
return fetch(`${config.apiUrl}/apps/${id}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function deleteApp(id) {
|
||||
const requestOptions = { method: 'DELETE', headers: authHeader() };
|
||||
return fetch(`${config.apiUrl}/apps/${id}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getAppBySlug(slug) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader() };
|
||||
return fetch(`${config.apiUrl}/apps/slugs/${slug}`, requestOptions).then(handleResponse);
|
||||
|
|
|
|||
Loading…
Reference in a new issue