Add a page to list / delete the user tokens

This commit is contained in:
Grégoire Delattre 2018-03-13 23:22:10 +01:00
parent bd20a87548
commit 2ddca462da
8 changed files with 241 additions and 3 deletions

View File

@ -50,3 +50,20 @@ export function getUserInfos() {
configureAxios().get("/users/details") 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(),
]
)
}

View File

@ -164,6 +164,11 @@ function UserDropdown(props) {
<MenuItem>Edit</MenuItem> <MenuItem>Edit</MenuItem>
</LinkContainer> </LinkContainer>
} }
{props.isActivated &&
<LinkContainer to="/users/tokens">
<MenuItem>Tokens</MenuItem>
</LinkContainer>
}
<LinkContainer to="/users/logout"> <LinkContainer to="/users/logout">
<MenuItem>Logout</MenuItem> <MenuItem>Logout</MenuItem>
</LinkContainer> </LinkContainer>

View 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);

View File

@ -1,4 +1,4 @@
import { Map } from "immutable" import { Map, List, fromJS } from "immutable"
import jwtDecode from "jwt-decode" import jwtDecode from "jwt-decode"
import Cookies from "universal-cookie" import Cookies from "universal-cookie"
@ -11,6 +11,7 @@ const defaultState = Map({
isLogged: false, isLogged: false,
polochonToken: "", polochonToken: "",
polochonUrl: "", polochonUrl: "",
tokens: List(),
}); });
const handlers = { const handlers = {
@ -27,6 +28,9 @@ const handlers = {
polochonToken: action.payload.response.data.token, polochonToken: action.payload.response.data.token,
polochonUrl: action.payload.response.data.url, polochonUrl: action.payload.response.data.url,
})), })),
"GET_USER_TOKENS_FULFILLED": (state, action) => state.set(
"tokens", fromJS(action.payload.response.data)
),
} }
function logoutUser() { function logoutUser() {

View File

@ -3,6 +3,7 @@ import ShowList from "./components/shows/list"
import ShowDetails from "./components/shows/details" import ShowDetails from "./components/shows/details"
import UserLoginForm from "./components/users/login" import UserLoginForm from "./components/users/login"
import UserEdit from "./components/users/edit" import UserEdit from "./components/users/edit"
import UserTokens from "./components/users/tokens"
import UserActivation from "./components/users/activation" import UserActivation from "./components/users/activation"
import UserSignUp from "./components/users/signup" import UserSignUp from "./components/users/signup"
import TorrentList from "./components/torrents/list" import TorrentList from "./components/torrents/list"
@ -10,7 +11,7 @@ import TorrentSearch from "./components/torrents/search"
import AdminPanel from "./components/admins/panel" import AdminPanel from "./components/admins/panel"
import { fetchTorrents, searchTorrents } from "./actions/torrents" 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 { fetchMovies, getMovieExploreOptions } from "./actions/movies"
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows" import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
import { getUsers, getStats } from "./actions/admins" 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", path: "/users/activation",
component: UserActivation, component: UserActivation,

View File

@ -53,6 +53,7 @@ body {
max-height: 300px; max-height: 300px;
} }
.spaced-icons,
.episode-buttons { .episode-buttons {
div, span { div, span {
margin: 2px; margin: 2px;
@ -103,3 +104,11 @@ table.torrent-search-result {
table.table-align-middle > tbody > tr > td { table.table-align-middle > tbody > tr > td {
vertical-align: middle; vertical-align: middle;
} }
div.user-token-icon > div {
padding-top: 1%;
}
span.user-token-action {
margin: 10px;
}

View File

@ -17,6 +17,7 @@
"immutable": "^3.8.1", "immutable": "^3.8.1",
"jquery": "^2.2.4", "jquery": "^2.2.4",
"jwt-decode": "^2.1.0", "jwt-decode": "^2.1.0",
"moment": "^2.20.1",
"react": "^16.2.0", "react": "^16.2.0",
"react-bootstrap": "^0.32.1", "react-bootstrap": "^0.32.1",
"react-bootstrap-sweetalert": "^4.2.3", "react-bootstrap-sweetalert": "^4.2.3",
@ -31,6 +32,7 @@
"redux": "^3.7.2", "redux": "^3.7.2",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"ua-parser-js": "^0.7.17",
"universal-cookie": "^2.1.2", "universal-cookie": "^2.1.2",
"webpack": "^3.11.0" "webpack": "^3.11.0"
}, },

View File

@ -2975,6 +2975,10 @@ minimist@^1.2.0:
dependencies: dependencies:
minimist "0.0.8" 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: ms@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -4399,7 +4403,7 @@ typedarray@^0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 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" version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"