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"
|
import store, { history } from "./store"
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import AdminPanel from "./components/admins/panel"
|
import { AdminPanel } from "./components/admins/panel"
|
||||||
import Alert from "./components/alerts/alert"
|
import Alert from "./components/alerts/alert"
|
||||||
import MovieList from "./components/movies/list"
|
import MovieList from "./components/movies/list"
|
||||||
import MoviesRoute from "./components/movies/route"
|
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 React from "react"
|
||||||
import PropTypes from "prop-types"
|
|
||||||
import { connect } from "react-redux"
|
|
||||||
import {
|
|
||||||
getUsers, getStats,
|
|
||||||
getAdminModules, updateUser
|
|
||||||
} from "../../actions/admins"
|
|
||||||
|
|
||||||
import Modules from "../modules/modules"
|
import { UserList } from "./userList"
|
||||||
import { UserList } from "./users"
|
|
||||||
import { Stats } from "./stats"
|
import { Stats } from "./stats"
|
||||||
|
import { AdminModules } from "./modules"
|
||||||
|
|
||||||
const AdminPanel = props => {
|
export const AdminPanel = () => (
|
||||||
const [fetched, setIsFetched] = useState(false);
|
|
||||||
if (!fetched) {
|
|
||||||
props.getUsers();
|
|
||||||
props.getStats();
|
|
||||||
props.getAdminModules();
|
|
||||||
setIsFetched(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Stats stats={props.stats}/>
|
<Stats />
|
||||||
<UserList users={props.users} updateUser={props.updateUser}/>
|
<UserList />
|
||||||
<Modules modules={props.modules} isLoading={false} />
|
<AdminModules />
|
||||||
</React.Fragment>
|
</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 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">
|
<div className="row d-flex flex-wrap">
|
||||||
<Stat
|
<Stat
|
||||||
name="Movies"
|
name="Movies"
|
||||||
count={props.stats.get("movies_count")}
|
count={stats.get("movies_count")}
|
||||||
torrentCount={props.stats.get("movies_torrents_count")}
|
torrentCount={stats.get("movies_torrents_count")}
|
||||||
torrentCountById={props.stats.get("movies_torrents_count_by_id")}
|
torrentCountById={stats.get("movies_torrents_count_by_id")}
|
||||||
/>
|
/>
|
||||||
<Stat
|
<Stat
|
||||||
name="Shows"
|
name="Shows"
|
||||||
count={props.stats.get("shows_count")}
|
count={stats.get("shows_count")}
|
||||||
torrentCount={props.stats.get("episodes_torrents_count")}
|
torrentCount={stats.get("episodes_torrents_count")}
|
||||||
torrentCountById={props.stats.get("shows_torrents_count_by_id")}
|
torrentCountById={stats.get("shows_torrents_count_by_id")}
|
||||||
/>
|
/>
|
||||||
<Stat
|
<Stat
|
||||||
name="Episodes"
|
name="Episodes"
|
||||||
count={props.stats.get("episodes_count")}
|
count={stats.get("episodes_count")}
|
||||||
torrentCount={props.stats.get("episodes_torrents_count")}
|
torrentCount={stats.get("episodes_torrents_count")}
|
||||||
torrentCountById={props.stats.get("episodes_torrents_count_by_id")}
|
torrentCountById={stats.get("episodes_torrents_count_by_id")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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"
|
import { Map, List, fromJS } from "immutable"
|
||||||
|
|
||||||
export const defaultState = Map({
|
export const defaultState = Map({
|
||||||
|
"fetchingModules": false,
|
||||||
"users": List(),
|
"users": List(),
|
||||||
"stats": Map({}),
|
"stats": Map({}),
|
||||||
"modules": Map({}),
|
"modules": Map({}),
|
||||||
@ -9,7 +10,11 @@ export const defaultState = Map({
|
|||||||
const handlers = {
|
const handlers = {
|
||||||
"ADMIN_LIST_USERS_FULFILLED": (state, action) => state.set("users", fromJS(action.payload.response.data)),
|
"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_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) =>
|
export default (state = defaultState, action) =>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user