Add a page to list / delete the user tokens
This commit is contained in:
parent
bd20a87548
commit
2ddca462da
@ -50,3 +50,20 @@ export function getUserInfos() {
|
||||
configureAxios().get("/users/details")
|
||||
)
|
||||
}
|
||||
|
||||
export function getUserTokens() {
|
||||
return request(
|
||||
"GET_USER_TOKENS",
|
||||
configureAxios().get("/users/tokens")
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteUserToken(token) {
|
||||
return request(
|
||||
"DELETE_USER_TOKEN",
|
||||
configureAxios().delete(`/users/tokens/${token}`),
|
||||
[
|
||||
() => getUserTokens(),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
@ -164,6 +164,11 @@ function UserDropdown(props) {
|
||||
<MenuItem>Edit</MenuItem>
|
||||
</LinkContainer>
|
||||
}
|
||||
{props.isActivated &&
|
||||
<LinkContainer to="/users/tokens">
|
||||
<MenuItem>Tokens</MenuItem>
|
||||
</LinkContainer>
|
||||
}
|
||||
<LinkContainer to="/users/logout">
|
||||
<MenuItem>Logout</MenuItem>
|
||||
</LinkContainer>
|
||||
|
187
frontend/js/components/users/tokens.js
Normal file
187
frontend/js/components/users/tokens.js
Normal file
@ -0,0 +1,187 @@
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { UAParser } from "ua-parser-js"
|
||||
import moment from "moment"
|
||||
|
||||
import { bindActionCreators } from "redux"
|
||||
import { deleteUserToken } from "../../actions/users"
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ deleteUserToken }, dispatch)
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
tokens: state.userStore.get("tokens"),
|
||||
};
|
||||
}
|
||||
|
||||
function UserTokens(props) {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="content-fluid">
|
||||
<TokenList {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TokenList(props) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="hidden-xs">Tokens</h2>
|
||||
<h3 className="visible-xs">Tokens</h3>
|
||||
|
||||
<div>
|
||||
{props.tokens.map(function(el, index) {
|
||||
return (
|
||||
<Token
|
||||
key={index}
|
||||
data={el}
|
||||
deleteToken={props.deleteUserToken}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Token(props) {
|
||||
const ua = UAParser(props.data.get("user_agent"));
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
<div className="container">
|
||||
<Logo {...ua} />
|
||||
<div className="col-xs-10">
|
||||
<dl className="dl-horizontal">
|
||||
<dt>Description</dt>
|
||||
<dd>{props.data.get("description")}</dd>
|
||||
|
||||
<dt>Last IP</dt>
|
||||
<dd>{props.data.get("ip")}</dd>
|
||||
|
||||
<dt>Last used</dt>
|
||||
<dd>{moment(props.data.get("last_used")).fromNow()}</dd>
|
||||
|
||||
<dt>Created</dt>
|
||||
<dd>{moment(props.data.get("created_at")).fromNow()}</dd>
|
||||
|
||||
<dt>Device</dt>
|
||||
<dd><Device {...ua.device}/></dd>
|
||||
|
||||
<dt>OS</dt>
|
||||
<dd><OS {...ua.os}/></dd>
|
||||
|
||||
<dt>Browser</dt>
|
||||
<dd><Browser {...ua.browser}/></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<Actions {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
class Actions extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
const token = this.props.data.get("token");
|
||||
this.props.deleteToken(token);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="col-xs-1">
|
||||
<span
|
||||
className="fa fa-trash fa-lg pull-right clickable user-token-action"
|
||||
onClick={this.handleClick}>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Logo(props) {
|
||||
var className;
|
||||
if (props.ua === "canape-cli") {
|
||||
className = "terminal";
|
||||
} else if (props.device.type == "mobile" ){
|
||||
className = "mobile";
|
||||
} else {
|
||||
switch (props.browser.name) {
|
||||
case "Chrome":
|
||||
case "chrome":
|
||||
className = "chrome";
|
||||
break;
|
||||
case "Safari":
|
||||
case "safari":
|
||||
className = "safari";
|
||||
break;
|
||||
case "Firefox":
|
||||
case "firefox":
|
||||
className = "firefox";
|
||||
break;
|
||||
default:
|
||||
className = "question";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="user-token-icon">
|
||||
<div className="hidden-xs hidden-sm col-md-1">
|
||||
<span className={"fa fa-" + className + " fa-5x"}></span>
|
||||
</div>
|
||||
<div className="hidden-md hidden-lg col-xs-1">
|
||||
<span className={"fa fa-" + className + " fa-lg"}></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OS(props) {
|
||||
var osName = "-";
|
||||
|
||||
if (props.name !== undefined) {
|
||||
osName = props.name;
|
||||
|
||||
if (props.version !== undefined) {
|
||||
osName += " " + props.version;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span> {osName}</span>
|
||||
);
|
||||
}
|
||||
|
||||
function Device(props) {
|
||||
var deviceName = "-";
|
||||
|
||||
if (props.model !== undefined) {
|
||||
deviceName = props.model;
|
||||
}
|
||||
|
||||
return (
|
||||
<span> {deviceName}</span>
|
||||
);
|
||||
}
|
||||
|
||||
function Browser(props) {
|
||||
var browserName = "-";
|
||||
if (props.name !== undefined) {
|
||||
browserName = props.name;
|
||||
|
||||
if (props.version !== undefined) {
|
||||
browserName += " - " + props.version;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span> {browserName}</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserTokens);
|
@ -1,4 +1,4 @@
|
||||
import { Map } from "immutable"
|
||||
import { Map, List, fromJS } from "immutable"
|
||||
|
||||
import jwtDecode from "jwt-decode"
|
||||
import Cookies from "universal-cookie"
|
||||
@ -11,6 +11,7 @@ const defaultState = Map({
|
||||
isLogged: false,
|
||||
polochonToken: "",
|
||||
polochonUrl: "",
|
||||
tokens: List(),
|
||||
});
|
||||
|
||||
const handlers = {
|
||||
@ -27,6 +28,9 @@ const handlers = {
|
||||
polochonToken: action.payload.response.data.token,
|
||||
polochonUrl: action.payload.response.data.url,
|
||||
})),
|
||||
"GET_USER_TOKENS_FULFILLED": (state, action) => state.set(
|
||||
"tokens", fromJS(action.payload.response.data)
|
||||
),
|
||||
}
|
||||
|
||||
function logoutUser() {
|
||||
|
@ -3,6 +3,7 @@ import ShowList from "./components/shows/list"
|
||||
import ShowDetails from "./components/shows/details"
|
||||
import UserLoginForm from "./components/users/login"
|
||||
import UserEdit from "./components/users/edit"
|
||||
import UserTokens from "./components/users/tokens"
|
||||
import UserActivation from "./components/users/activation"
|
||||
import UserSignUp from "./components/users/signup"
|
||||
import TorrentList from "./components/torrents/list"
|
||||
@ -10,7 +11,7 @@ import TorrentSearch from "./components/torrents/search"
|
||||
import AdminPanel from "./components/admins/panel"
|
||||
|
||||
import { fetchTorrents, searchTorrents } from "./actions/torrents"
|
||||
import { userLogout, getUserInfos } from "./actions/users"
|
||||
import { userLogout, getUserInfos, getUserTokens } from "./actions/users"
|
||||
import { fetchMovies, getMovieExploreOptions } from "./actions/movies"
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
|
||||
import { getUsers, getStats } from "./actions/admins"
|
||||
@ -118,6 +119,15 @@ export default function getRoutes(App) {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/users/tokens",
|
||||
component: UserTokens,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(getUserTokens());
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/users/activation",
|
||||
component: UserActivation,
|
||||
|
@ -53,6 +53,7 @@ body {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.spaced-icons,
|
||||
.episode-buttons {
|
||||
div, span {
|
||||
margin: 2px;
|
||||
@ -103,3 +104,11 @@ table.torrent-search-result {
|
||||
table.table-align-middle > tbody > tr > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.user-token-icon > div {
|
||||
padding-top: 1%;
|
||||
}
|
||||
|
||||
span.user-token-action {
|
||||
margin: 10px;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
"immutable": "^3.8.1",
|
||||
"jquery": "^2.2.4",
|
||||
"jwt-decode": "^2.1.0",
|
||||
"moment": "^2.20.1",
|
||||
"react": "^16.2.0",
|
||||
"react-bootstrap": "^0.32.1",
|
||||
"react-bootstrap-sweetalert": "^4.2.3",
|
||||
@ -31,6 +32,7 @@
|
||||
"redux": "^3.7.2",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"ua-parser-js": "^0.7.17",
|
||||
"universal-cookie": "^2.1.2",
|
||||
"webpack": "^3.11.0"
|
||||
},
|
||||
|
@ -2975,6 +2975,10 @@ minimist@^1.2.0:
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
moment@^2.20.1:
|
||||
version "2.20.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
@ -4399,7 +4403,7 @@ typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
ua-parser-js@^0.7.9:
|
||||
ua-parser-js@^0.7.17, ua-parser-js@^0.7.9:
|
||||
version "0.7.17"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user