Update the admin panel to manage users' polochons

This commit is contained in:
Grégoire Delattre 2019-07-15 10:56:58 +02:00
parent 360363c0ae
commit c36c8b3c97
10 changed files with 362 additions and 103 deletions

View File

@ -25,7 +25,7 @@ import { ProtectedRoute, AdminRoute } from "./auth"
import store, { history } from "./store"
// Components
import AdminPanel from "./components/admins/panel"
import { AdminPanel } from "./components/admins/panel"
import Alert from "./components/alerts/alert"
import MovieList from "./components/movies/list"
import MoviesRoute from "./components/movies/route"

View File

@ -0,0 +1,28 @@
import React, { useEffect } from "react"
import PropTypes from "prop-types"
import { connect } from "react-redux"
import { getAdminModules} from "../../actions/admins"
import Modules from "../modules/modules"
const AdminModulesConnected = ({ modules, loading, getAdminModules }) => {
useEffect(() => {
getAdminModules();
}, [getAdminModules])
return (
<Modules modules={modules} isLoading={loading} />
)
}
AdminModulesConnected.propTypes = {
modules: PropTypes.object,
loading: PropTypes.bool,
getAdminModules: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
loading: state.adminStore.get("fetchingModules"),
modules: state.adminStore.get("modules"),
});
export const AdminModules = connect(mapStateToProps, {getAdminModules})(AdminModulesConnected);

View File

@ -1,50 +1,13 @@
import React, { useState } from "react"
import PropTypes from "prop-types"
import { connect } from "react-redux"
import {
getUsers, getStats,
getAdminModules, updateUser
} from "../../actions/admins"
import React from "react"
import Modules from "../modules/modules"
import { UserList } from "./users"
import { UserList } from "./userList"
import { Stats } from "./stats"
import { AdminModules } from "./modules"
const AdminPanel = props => {
const [fetched, setIsFetched] = useState(false);
if (!fetched) {
props.getUsers();
props.getStats();
props.getAdminModules();
setIsFetched(true);
}
return (
export const AdminPanel = () => (
<React.Fragment>
<Stats stats={props.stats}/>
<UserList users={props.users} updateUser={props.updateUser}/>
<Modules modules={props.modules} isLoading={false} />
<Stats />
<UserList />
<AdminModules />
</React.Fragment>
)
}
AdminPanel.propTypes = {
stats: PropTypes.object,
users: PropTypes.object,
modules: PropTypes.object,
updateUser: PropTypes.func.isRequired,
getUsers: PropTypes.func.isRequired,
getStats: PropTypes.func.isRequired,
getAdminModules: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
users: state.adminStore.get("users"),
stats: state.adminStore.get("stats"),
modules: state.adminStore.get("modules"),
});
const mapDispatchToProps = {
getUsers, getStats,
getAdminModules, updateUser
}
export default connect(mapStateToProps, mapDispatchToProps)(AdminPanel);
)

View File

@ -0,0 +1,30 @@
import React from "react"
import PropTypes from "prop-types"
import { TorrentsStat } from "./torrentsStat"
export const Stat = ({ name, count, torrentCount, torrentCountById }) => (
<div className="col-12 col-md-4 my-2">
<div className="card">
<div className="card-header">
<h3>
{name}
<span className="badge badge-pill badge-info pull-right">{count}</span>
</h3>
</div>
<div className="card-body">
<TorrentsStat
count={count}
torrentCount={torrentCount}
torrentCountById={torrentCountById}
/>
</div>
</div>
</div>
)
Stat.propTypes = {
name: PropTypes.string,
count: PropTypes.number,
torrentCount: PropTypes.number,
torrentCountById: PropTypes.number,
};

View File

@ -1,61 +1,46 @@
import React from "react"
import React, { useEffect } from "react"
import PropTypes from "prop-types"
import { connect } from "react-redux"
export const Stats = props => (
import { Stat } from "./stat"
import { getStats } from "../../actions/admins"
const StatsConnected = ({ stats, getStats }) => {
useEffect(() => {
getStats();
}, [getStats])
return (
<div className="row d-flex flex-wrap">
<Stat
name="Movies"
count={props.stats.get("movies_count")}
torrentCount={props.stats.get("movies_torrents_count")}
torrentCountById={props.stats.get("movies_torrents_count_by_id")}
count={stats.get("movies_count")}
torrentCount={stats.get("movies_torrents_count")}
torrentCountById={stats.get("movies_torrents_count_by_id")}
/>
<Stat
name="Shows"
count={props.stats.get("shows_count")}
torrentCount={props.stats.get("episodes_torrents_count")}
torrentCountById={props.stats.get("shows_torrents_count_by_id")}
count={stats.get("shows_count")}
torrentCount={stats.get("episodes_torrents_count")}
torrentCountById={stats.get("shows_torrents_count_by_id")}
/>
<Stat
name="Episodes"
count={props.stats.get("episodes_count")}
torrentCount={props.stats.get("episodes_torrents_count")}
torrentCountById={props.stats.get("episodes_torrents_count_by_id")}
count={stats.get("episodes_count")}
torrentCount={stats.get("episodes_torrents_count")}
torrentCountById={stats.get("episodes_torrents_count_by_id")}
/>
</div>
)
Stats.propTypes = { stats: PropTypes.object }
const Stat = props => (
<div className="col-12 col-md-4 my-2">
<div className="card">
<div className="card-header">
<h3>
{props.name}
<span className="badge badge-pill badge-info pull-right">{props.count}</span>
</h3>
</div>
<div className="card-body">
<TorrentsStat data={props} />
</div>
</div>
</div>
)
Stat.propTypes = {
name: PropTypes.string,
count: PropTypes.number,
};
const TorrentsStat = function(props) {
if (props.data.torrentCount === undefined) {
return (<span>No torrents</span>);
}
const percentage = Math.floor((props.data.torrentCountById * 100) / props.data.count);
return (
<span>
{percentage}% with torrents
<small>&nbsp; - {props.data.torrentCount} total</small>
</span>
);
)
}
TorrentsStat.propTypes = { data: PropTypes.object };
StatsConnected.propTypes = {
stats: PropTypes.object,
getStats: PropTypes.func,
}
const mapStateToProps = state => ({
stats: state.adminStore.get("stats"),
});
export const Stats = connect(mapStateToProps, {getStats})(StatsConnected);

View File

@ -0,0 +1,21 @@
import React from "react"
import PropTypes from "prop-types"
export const TorrentsStat = ({ count, torrentCount, torrentCountById }) => {
if (torrentCount === undefined) {
return (<span>No torrents</span>);
}
const percentage = Math.floor((torrentCountById * 100) / count);
return (
<span>
{percentage}% with torrents
<small>&nbsp; - {torrentCount} total</small>
</span>
);
}
TorrentsStat.propTypes = {
count: PropTypes.number,
torrentCount: PropTypes.number,
torrentCountById: PropTypes.number,
};

View File

@ -0,0 +1,60 @@
import React from "react"
import PropTypes from "prop-types"
import { UserEdit } from "./userEdit"
export const User = ({
id,
admin,
activated,
name,
polochonActivated,
polochonUrl,
polochonName,
polochonId,
token,
}) => {
return (
<tr>
<td>{id}</td>
<td>{name}</td>
<td>
<span className={activated ? "fa fa-check" : "fa fa-times text-danger"}></span>
</td>
<td>
<span className={admin ? "fa fa-check" : "fa fa-times"}></span>
</td>
<td>
{polochonName !== "" ? polochonName : "-"}
{polochonUrl !== "" &&
<small className="ml-1">({polochonUrl})</small>
}
</td>
<td>{token}</td>
<td>
<span className={polochonActivated ? "fa fa-check" : "fa fa-times text-danger"}></span>
</td>
<td>
<UserEdit
id={id}
admin={admin}
activated={activated}
polochonActivated={polochonActivated}
polochonToken={token}
polochonId={polochonId}
/>
</td>
</tr>
);
}
User.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
polochonId: PropTypes.string,
polochonUrl: PropTypes.string,
polochonName: PropTypes.string,
token: PropTypes.string,
admin: PropTypes.bool,
activated: PropTypes.bool,
polochonActivated: PropTypes.bool,
};

View File

@ -0,0 +1,104 @@
import React, { useState } from "react"
import PropTypes from "prop-types"
import { connect } from "react-redux"
import { List } from "immutable"
import { updateUser } from "../../actions/admins"
import Toggle from "react-bootstrap-toggle";
import { PolochonSelect } from "../polochons/select"
import { FormModal } from "../forms/modal"
import { FormInput } from "../forms/input"
const UserEditConnect = ({
id,
admin: initAdmin,
activated: initActivated,
polochonToken,
polochonId: initPolochonId,
polochonActivated: initPolochonActivated,
updateUser,
publicPolochons,
}) => {
const [modal, setModal] = useState(false);
const [admin, setAdmin] = useState(initAdmin);
const [activated, setActivated] = useState(initActivated);
const [token, setToken] = useState(polochonToken);
const [polochonId, setPolochonId] = useState(initPolochonId);
const [polochonActivated, setPolochonActivated] = useState(initPolochonActivated);
const [password, setPassword] = useState("");
const handleSubmit = function(e) {
if (e) { e.preventDefault(); }
updateUser({
userId: id,
polochonToken: token,
admin,
activated,
polochonId,
polochonActivated,
password,
});
setModal(false);
};
return (
<span>
<i
className="fa fa-pencil clickable"
onClick={() => setModal(true)} />
<FormModal
show={modal}
setShow={setModal}
title="Edit user"
icon="edit"
handleSubmit={handleSubmit}
>
<div className="form-group">
<label>Account status</label>
<Toggle className="pull-right" on="Activated" off="Deactivated" active={activated}
offstyle="danger" handlestyle="secondary" onClick={() => setActivated(!activated)}
/>
</div>
<div className="form-group">
<label>Admin status</label>
<Toggle className="pull-right" on="Admin" off="User" active={admin}
offstyle="info" handlestyle="secondary" onClick={() => setAdmin(!admin)} />
</div>
<FormInput label="Password" value={password} updateValue={setPassword} />
<PolochonSelect value={polochonId} changeValue={setPolochonId} polochonList={publicPolochons} />
<FormInput label="Polochon Token" value={token} updateValue={setToken} />
<div className="form-group">
<label>Polochon activated</label>
<Toggle className="pull-right" on="Activated" off="Deactivated" active={polochonActivated}
offstyle="danger" handlestyle="secondary" onClick={() => setPolochonActivated(!polochonActivated)}
/>
</div>
</FormModal>
</span>
);
}
UserEditConnect.propTypes = {
id: PropTypes.string,
activated: PropTypes.bool,
admin: PropTypes.bool,
data: PropTypes.object,
updateUser: PropTypes.func,
polochonToken: PropTypes.string,
polochonId: PropTypes.string,
polochonActivated: PropTypes.bool,
publicPolochons: PropTypes.instanceOf(List),
};
const mapStateToProps = (state) => ({
publicPolochons: state.polochon.get("public"),
});
export const UserEdit = connect(mapStateToProps, {updateUser})(UserEditConnect);

View File

@ -0,0 +1,63 @@
import React, { useEffect } from "react"
import PropTypes from "prop-types"
import { List } from "immutable"
import { connect } from "react-redux"
import { User } from "./user"
import { getUsers } from "../../actions/admins"
import { getPolochons } from "../../actions/polochon"
const mapStateToProps = state => ({
users: state.adminStore.get("users"),
});
const mapDispatchToProps = { getUsers, getPolochons };
const UserListConnect = ({ users, getUsers, getPolochons }) => {
useEffect(() => {
getUsers();
getPolochons();
}, [getUsers, getPolochons])
return (
<div className="table-responsive my-2">
<table className="table table-striped">
<thead className="table-secondary">
<tr>
<th>#</th>
<th>Name</th>
<th>Activated</th>
<th>Admin</th>
<th>Polochon URL</th>
<th>Polochon token</th>
<th>Polochon activated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{users.map((el, index) =>
<User
key={index}
id={el.get("id")}
name={el.get("name")}
admin={el.get("admin")}
activated={el.get("activated")}
polochonActivated={el.get("polochon_activated")}
token={el.get("token", "")}
polochonId={el.getIn(["polochon", "id"], "")}
polochonUrl={el.getIn(["polochon", "url"], "")}
polochonName={el.getIn(["polochon", "name"], "")}
/>
)}
</tbody>
</table>
</div>
);
};
UserListConnect.propTypes = {
getUsers: PropTypes.func,
getPolochons: PropTypes.func,
users: PropTypes.PropTypes.instanceOf(List),
};
export const UserList = connect(mapStateToProps, mapDispatchToProps)(UserListConnect);

View File

@ -1,6 +1,7 @@
import { Map, List, fromJS } from "immutable"
export const defaultState = Map({
"fetchingModules": false,
"users": List(),
"stats": Map({}),
"modules": Map({}),
@ -9,7 +10,11 @@ export const defaultState = Map({
const handlers = {
"ADMIN_LIST_USERS_FULFILLED": (state, action) => state.set("users", fromJS(action.payload.response.data)),
"ADMIN_GET_STATS_FULFILLED": (state, action) => state.set("stats", fromJS(action.payload.response.data)),
"ADMIN_GET_MODULES_FULFILLED": (state, action) => state.set("modules", fromJS(action.payload.response.data)),
"ADMIN_GET_MODULES_PENDING": state => state.set("fetchingModules", true),
"ADMIN_GET_MODULES_FULFILLED": (state, action) => state.merge(fromJS({
"modules": action.payload.response.data,
"fetchingModules": false,
})),
}
export default (state = defaultState, action) =>