Update the admin panel to manage users' polochons
This commit is contained in:
parent
360363c0ae
commit
c36c8b3c97
@ -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"
|
||||
|
28
frontend/js/components/admins/modules.js
Normal file
28
frontend/js/components/admins/modules.js
Normal 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);
|
@ -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);
|
||||
)
|
||||
|
30
frontend/js/components/admins/stat.js
Normal file
30
frontend/js/components/admins/stat.js
Normal 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,
|
||||
};
|
@ -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> - {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);
|
||||
|
21
frontend/js/components/admins/torrentsStat.js
Normal file
21
frontend/js/components/admins/torrentsStat.js
Normal 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> - {torrentCount} total</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
TorrentsStat.propTypes = {
|
||||
count: PropTypes.number,
|
||||
torrentCount: PropTypes.number,
|
||||
torrentCountById: PropTypes.number,
|
||||
};
|
60
frontend/js/components/admins/user.js
Normal file
60
frontend/js/components/admins/user.js
Normal 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,
|
||||
};
|
104
frontend/js/components/admins/userEdit.js
Normal file
104
frontend/js/components/admins/userEdit.js
Normal 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);
|
63
frontend/js/components/admins/userList.js
Normal file
63
frontend/js/components/admins/userList.js
Normal 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);
|
@ -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) =>
|
||||
|
Loading…
x
Reference in New Issue
Block a user