Merge branch 'torrentSearch' into 'master'
Torrent search See merge request !88
This commit is contained in:
commit
f05d0024fc
@ -17,6 +17,7 @@ movie:
|
||||
- trakttv
|
||||
torrenters:
|
||||
- yts
|
||||
- thepiratebay
|
||||
searchers:
|
||||
- yts
|
||||
explorers:
|
||||
@ -24,6 +25,7 @@ movie:
|
||||
- trakttv
|
||||
show:
|
||||
torrenters:
|
||||
- thepiratebay
|
||||
- eztv
|
||||
detailers:
|
||||
- tvdb
|
||||
@ -34,6 +36,11 @@ show:
|
||||
- trakttv
|
||||
- eztv
|
||||
modules_params:
|
||||
- name: thepiratebay
|
||||
show_users:
|
||||
- EtHD
|
||||
movie_users:
|
||||
- YIFY
|
||||
- name: trakttv
|
||||
client_id: my_trakttv_client_id
|
||||
- name: tmdb
|
||||
|
@ -20,6 +20,11 @@ func (b *Backend) GetTorrents(media interface{}, log *logrus.Entry) error {
|
||||
}
|
||||
}
|
||||
|
||||
// SearchTorrents implements the polochon Torrenter interface
|
||||
func (b *Backend) SearchTorrents(s string) ([]*polochon.Torrent, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetMovieTorrents fetch Torrents for movies
|
||||
func (b *Backend) GetMovieTorrents(pmovie *polochon.Movie, log *logrus.Entry) error {
|
||||
movieTorrents, err := GetMovieTorrents(b.Database, pmovie.ImdbID)
|
||||
|
@ -4,9 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/odwrtw/polochon/lib"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||
@ -94,3 +97,55 @@ func RemoveHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
return env.RenderOK(w, "Torrent removed")
|
||||
}
|
||||
|
||||
// SearchHandler search for torrents
|
||||
func SearchHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
log := env.Log.WithFields(logrus.Fields{
|
||||
"function": "torrents.SearchHandler",
|
||||
})
|
||||
|
||||
vars := mux.Vars(r)
|
||||
searchType := vars["type"]
|
||||
searchStr := vars["search"]
|
||||
|
||||
// Get the appropriate torrenters
|
||||
var torrenters []polochon.Torrenter
|
||||
switch searchType {
|
||||
case "movies":
|
||||
torrenters = env.Config.MovieTorrenters
|
||||
case "shows":
|
||||
torrenters = env.Config.ShowTorrenters
|
||||
default:
|
||||
return env.RenderError(w, errors.New("invalid search type"))
|
||||
}
|
||||
|
||||
log.Debugf("searching for %s torrents for the query %q", searchType, searchStr)
|
||||
|
||||
// Search for torrents
|
||||
results := []*polochon.Torrent{}
|
||||
for _, torrenter := range torrenters {
|
||||
torrents, err := torrenter.SearchTorrents(searchStr)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if torrents == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, torrents...)
|
||||
}
|
||||
|
||||
// Sort by seeds
|
||||
sort.Sort(BySeed(results))
|
||||
|
||||
return env.RenderJSON(w, results)
|
||||
}
|
||||
|
||||
// BySeed is an helper to sort torrents by seeders
|
||||
type BySeed []*polochon.Torrent
|
||||
|
||||
func (t BySeed) Len() int { return len(t) }
|
||||
func (t BySeed) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t BySeed) Less(i, j int) bool { return t[i].Seeders > t[j].Seeders }
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
_ "github.com/odwrtw/polochon/modules/pam"
|
||||
_ "github.com/odwrtw/polochon/modules/pushover"
|
||||
_ "github.com/odwrtw/polochon/modules/tmdb"
|
||||
_ "github.com/odwrtw/polochon/modules/tpb"
|
||||
_ "github.com/odwrtw/polochon/modules/trakttv"
|
||||
_ "github.com/odwrtw/polochon/modules/transmission"
|
||||
_ "github.com/odwrtw/polochon/modules/tvdb"
|
||||
|
@ -19,7 +19,7 @@ export function removeTorrent(id) {
|
||||
"REMOVE_TORRENT",
|
||||
configureAxios().delete(`/torrents/${id}`),
|
||||
[
|
||||
fetchTorrents(),
|
||||
() => fetchTorrents(),
|
||||
]
|
||||
)
|
||||
}
|
||||
@ -30,3 +30,10 @@ export function fetchTorrents() {
|
||||
configureAxios().get("/torrents")
|
||||
)
|
||||
}
|
||||
|
||||
export function searchTorrents(url) {
|
||||
return request(
|
||||
"TORRENTS_SEARCH",
|
||||
configureAxios().get(url)
|
||||
)
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ function TorrentsStat(props) {
|
||||
return (<span>No torrents</span>);
|
||||
}
|
||||
|
||||
const percentage = Math.floor((props.data.torrentCount * 100) / props.data.count);
|
||||
const percentage = Math.floor((props.data.torrentCountById * 100) / props.data.count);
|
||||
return (
|
||||
<span>
|
||||
{percentage}% with torrents
|
||||
|
@ -45,12 +45,11 @@ function MovieButtons(props) {
|
||||
lastFetchUrl={props.lastFetchUrl}
|
||||
/>
|
||||
|
||||
{props.movie.get("torrents") !== null &&
|
||||
<TorrentsButton
|
||||
torrents={props.movie.get("torrents")}
|
||||
addTorrent={props.addTorrent}
|
||||
/>
|
||||
}
|
||||
<TorrentsButton
|
||||
movieTitle={props.movie.get("title")}
|
||||
torrents={props.movie.get("torrents")}
|
||||
addTorrent={props.addTorrent}
|
||||
/>
|
||||
|
||||
<DownloadButton
|
||||
url={props.movie.get("polochon_url")}
|
||||
|
@ -13,8 +13,16 @@ export default class TorrentsButton extends React.PureComponent {
|
||||
}
|
||||
render() {
|
||||
const entries = buildMenuItems(this.props.torrents);
|
||||
const searchUrl = `#/torrents/search/movies/${encodeURI(this.props.movieTitle)}`;
|
||||
return (
|
||||
<DropdownButton className="btn btn-default btn-sm" title="Torrents" id="download-torrents-button" dropup>
|
||||
<MenuItem className="text-warning" header>Advanced</MenuItem>
|
||||
<MenuItem href={searchUrl} >
|
||||
<i className="fa fa-search" aria-hidden="true"></i> Search
|
||||
</MenuItem>
|
||||
{entries.length > 0 &&
|
||||
<MenuItem divider></MenuItem>
|
||||
}
|
||||
{entries.map(function(e, index) {
|
||||
switch (e.type) {
|
||||
case "header":
|
||||
@ -41,6 +49,10 @@ export default class TorrentsButton extends React.PureComponent {
|
||||
}
|
||||
|
||||
function buildMenuItems(torrents) {
|
||||
if (!torrents) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const t = torrents.groupBy((el) => el.get("source"));
|
||||
|
||||
// Build the array of entries
|
||||
|
@ -64,7 +64,7 @@ export default class AppNavBar extends React.PureComponent {
|
||||
<WishlistDropdown />
|
||||
}
|
||||
{loggedAndActivated &&
|
||||
<Torrents torrentsCount={this.props.torrentCount} />
|
||||
<TorrentsDropdown torrentsCount={this.props.torrentCount} />
|
||||
}
|
||||
<UserDropdown
|
||||
username={this.props.username}
|
||||
@ -199,20 +199,31 @@ function WishlistDropdown() {
|
||||
);
|
||||
}
|
||||
|
||||
function Torrents(props) {
|
||||
function TorrentsDropdown(props) {
|
||||
const title = (<TorrentsDropdownTitle torrentsCount={props.torrentsCount} />)
|
||||
return(
|
||||
<Nav>
|
||||
<LinkContainer to="/torrents">
|
||||
<NavItem>
|
||||
Torrents
|
||||
{props.torrentsCount > 0 &&
|
||||
<span>
|
||||
|
||||
<span className="label label-info">{props.torrentsCount}</span>
|
||||
</span>
|
||||
}
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
<NavDropdown title={title} id="navbar-wishlit-dropdown">
|
||||
<LinkContainer to="/torrents/list">
|
||||
<MenuItem>Downloads</MenuItem>
|
||||
</LinkContainer>
|
||||
<LinkContainer to="/torrents/search">
|
||||
<MenuItem>Search</MenuItem>
|
||||
</LinkContainer>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
||||
function TorrentsDropdownTitle(props) {
|
||||
return (
|
||||
<span>
|
||||
Torrents
|
||||
{props.torrentsCount > 0 &&
|
||||
<span>
|
||||
<span className="label label-info">{props.torrentsCount}</span>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import ImdbButton from "../buttons/imdb"
|
||||
import RefreshIndicator from "../buttons/refresh"
|
||||
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||
import { Button, Dropdown, MenuItem } from "react-bootstrap"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
@ -39,6 +40,7 @@ class ShowDetails extends React.Component {
|
||||
/>
|
||||
<SeasonsList
|
||||
data={this.props.show}
|
||||
router={this.props.router}
|
||||
addTorrent={this.props.addTorrent}
|
||||
addToWishlist={this.props.addShowToWishlist}
|
||||
getEpisodeDetails={this.props.getEpisodeDetails}
|
||||
@ -112,6 +114,8 @@ function SeasonsList(props){
|
||||
<Season
|
||||
data={data}
|
||||
season={season}
|
||||
showName={props.data.get("title")}
|
||||
router={props.router}
|
||||
addTorrent={props.addTorrent}
|
||||
addToWishlist={props.addToWishlist}
|
||||
getEpisodeDetails={props.getEpisodeDetails}
|
||||
@ -158,6 +162,8 @@ class Season extends React.Component {
|
||||
<Episode
|
||||
key={key}
|
||||
data={episode}
|
||||
showName={this.props.showName}
|
||||
router={this.props.router}
|
||||
addTorrent={this.props.addTorrent}
|
||||
addToWishlist={this.props.addToWishlist}
|
||||
getEpisodeDetails={this.props.getEpisodeDetails}
|
||||
@ -215,6 +221,8 @@ function Episode(props) {
|
||||
xs
|
||||
/>
|
||||
<GetDetailsButton
|
||||
showName={props.showName}
|
||||
router={props.router}
|
||||
data={props.data}
|
||||
getEpisodeDetails={props.getEpisodeDetails}
|
||||
/>
|
||||
@ -336,23 +344,40 @@ class TrackButton extends React.PureComponent {
|
||||
class GetDetailsButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleFetchClick = this.handleFetchClick.bind(this);
|
||||
this.handleAdvanceTorrentSearchClick = this.handleAdvanceTorrentSearchClick.bind(this);
|
||||
this.state = {
|
||||
imdbId: this.props.data.get("show_imdb_id"),
|
||||
season: this.props.data.get("season"),
|
||||
episode: this.props.data.get("episode"),
|
||||
};
|
||||
}
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
if (this.props.data.get("fetching")) {
|
||||
return
|
||||
}
|
||||
const imdbId = this.props.data.get("show_imdb_id");
|
||||
const season = this.props.data.get("season");
|
||||
const episode = this.props.data.get("episode");
|
||||
this.props.getEpisodeDetails(imdbId, season, episode);
|
||||
handleFetchClick() {
|
||||
if (this.props.data.get("fetching")) { return }
|
||||
this.props.getEpisodeDetails(this.state.imdbId, this.state.season, this.state.episode);
|
||||
}
|
||||
handleAdvanceTorrentSearchClick() {
|
||||
const pad = (d) => (d < 10) ? "0" + d.toString() : d.toString();
|
||||
const search = `${this.props.showName} S${pad(this.state.season)}E${pad(this.state.episode)}`;
|
||||
const url = `/torrents/search/shows/${encodeURI(search)}`;
|
||||
this.props.router.push(url);
|
||||
}
|
||||
render() {
|
||||
const id = `${this.state.imdbId}-${this.state.season}-${this.state.episode}-refresh-dropdown`;
|
||||
return (
|
||||
<a type="button" className="btn btn-xs btn-info" onClick={(e) => this.handleClick(e)}>
|
||||
<RefreshIndicator refresh={this.props.data.get("fetching")} />
|
||||
</a>
|
||||
<Dropdown id={id} dropup>
|
||||
<Button className="btn-xs" bsStyle="info" onClick={this.handleFetchClick}>
|
||||
<RefreshIndicator refresh={this.props.data.get("fetching")} />
|
||||
</Button>
|
||||
<Dropdown.Toggle className="btn-xs" bsStyle="info"/>
|
||||
<Dropdown.Menu>
|
||||
<MenuItem onClick={this.handleAdvanceTorrentSearchClick}>
|
||||
<span>
|
||||
<i className="fa fa-magnet"></i> Advanced torrent search
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent, removeTorrent } from "../../actions/torrents"
|
||||
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { torrents: state.torrentStore.get("torrents") };
|
||||
}
|
||||
@ -116,7 +114,6 @@ class Torrent extends React.PureComponent {
|
||||
this.props.removeTorrent(this.props.data.getIn(["additional_infos", "id"]));
|
||||
}
|
||||
render() {
|
||||
const id = this.props.data.getIn(["additional_infos", "id"]);
|
||||
const done = this.props.data.get("is_finished");
|
||||
var progressStyle = "progress-bar progress-bar-info active";
|
||||
if (done) {
|
||||
@ -128,8 +125,6 @@ class Torrent extends React.PureComponent {
|
||||
percentDone = Number(percentDone).toFixed(1) + "%";
|
||||
}
|
||||
|
||||
const tooltip = (<Tooltip id={id}>Remove this torrent</Tooltip>);
|
||||
|
||||
// Pretty sizes
|
||||
const downloadedSize = prettySize(this.props.data.get("downloaded_size"));
|
||||
const totalSize = prettySize(this.props.data.get("total_size"));
|
||||
@ -138,11 +133,7 @@ class Torrent extends React.PureComponent {
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">
|
||||
{this.props.data.get("name")}
|
||||
<span className="clickable pull-right" onClick={this.handleClick}>
|
||||
<OverlayTrigger placement="top" overlay={tooltip}>
|
||||
<span className="fa fa-trash"></span>
|
||||
</OverlayTrigger>
|
||||
</span>
|
||||
<span className="fa fa-trash clickable pull-right" onClick={this.handleClick}></span>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
{started &&
|
||||
|
212
src/public/js/components/torrents/search.js
Normal file
212
src/public/js/components/torrents/search.js
Normal file
@ -0,0 +1,212 @@
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent, searchTorrents } from "../../actions/torrents"
|
||||
import Loader from "../loader/loader"
|
||||
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
searching: state.torrentStore.get("searching"),
|
||||
results: state.torrentStore.get("searchResults"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ addTorrent, searchTorrents }, dispatch)
|
||||
|
||||
class TorrentSearch extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSearchInput = this.handleSearchInput.bind(this);
|
||||
this.state = { search: (this.props.router.params.search || "") };
|
||||
}
|
||||
handleSearchInput() {
|
||||
this.setState({ search: this.refs.search.value });
|
||||
}
|
||||
handleClick(type) {
|
||||
if (this.state.search === "") { return }
|
||||
const url = `/torrents/search/${type}/${encodeURI(this.state.search)}`;
|
||||
this.props.router.push(url);
|
||||
}
|
||||
render() {
|
||||
const searchFromURL = this.props.router.params.search || "";
|
||||
const typeFromURL = this.props.router.params.type || "";
|
||||
return (
|
||||
<div>
|
||||
<div className="col-xs-12">
|
||||
<form className="form-horizontal" onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Search torrents"
|
||||
value={this.state.search}
|
||||
onChange={this.handleSearchInput}
|
||||
ref="search"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="row">
|
||||
<SearchButton
|
||||
text="Search movies"
|
||||
type="movies"
|
||||
typeFromURL={typeFromURL}
|
||||
handleClick={() => this.handleClick("movies")}
|
||||
/>
|
||||
<SearchButton
|
||||
text="Search shows"
|
||||
type="shows"
|
||||
typeFromURL={typeFromURL}
|
||||
handleClick={() => this.handleClick("shows")}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<TorrentList
|
||||
searching={this.props.searching}
|
||||
results={this.props.results}
|
||||
addTorrent={this.props.addTorrent}
|
||||
searchFromURL={searchFromURL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function SearchButton(props) {
|
||||
const color = (props.type === props.typeFromURL) ? "primary" : "default";
|
||||
return (
|
||||
<div className="col-xs-6">
|
||||
<button
|
||||
className={`btn btn-${color} full-width`}
|
||||
type="button"
|
||||
onClick={props.handleClick}
|
||||
>
|
||||
<i className="fa fa-search" aria-hidden="true"></i> {props.text}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
function TorrentList(props) {
|
||||
if (props.searching) {
|
||||
return (<Loader />);
|
||||
}
|
||||
|
||||
if (props.searchFromURL === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (props.results.size === 0) {
|
||||
return (
|
||||
<div className="col-xs-12">
|
||||
<div className="well well-lg">
|
||||
<h2>No results</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-xs-12">
|
||||
{props.results.map(function(el, index) {
|
||||
return (
|
||||
<Torrent
|
||||
key={index}
|
||||
data={el}
|
||||
addTorrent={props.addTorrent}
|
||||
/>);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Torrent(props) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12">
|
||||
<table className="table responsive table-align-middle torrent-search-result">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowSpan="2" className="col-xs-1 torrent-small-width">
|
||||
<h4>
|
||||
<TorrentHealth
|
||||
url={props.data.get("url")}
|
||||
seeders={props.data.get("seeders")}
|
||||
leechers={props.data.get("leechers")}
|
||||
/>
|
||||
</h4>
|
||||
</td>
|
||||
<td colSpan="4" className="col-xs-9 title">
|
||||
<span className="torrent-title">{props.data.get("name")}</span>
|
||||
</td>
|
||||
<td rowSpan="2" className="col-xs-1 torrent-small-width">
|
||||
<h4 className="pull-right clickable" onClick={() => props.addTorrent(props.data.get("url"))}>
|
||||
<i className="fa fa-cloud-download" aria-hidden="true"></i>
|
||||
</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="col-xs-1 torrent-label">
|
||||
<span className="label label-warning">{props.data.get("quality")}</span>
|
||||
</td>
|
||||
<td className="col-xs-1 torrent-label">
|
||||
<span className="label label-success">{props.data.get("source")}</span>
|
||||
</td>
|
||||
<td className="col-xs-1 torrent-label">
|
||||
<span className="label label-info">{props.data.get("upload_user")}</span>
|
||||
</td>
|
||||
<td className="col-xs-7 torrent-label"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TorrentHealth(props) {
|
||||
const seeders = props.seeders || 0;
|
||||
const leechers = props.leechers || 1;
|
||||
|
||||
let color;
|
||||
let health;
|
||||
let ratio = seeders/leechers;
|
||||
|
||||
if (seeders > 20) {
|
||||
health = "good";
|
||||
color = "success";
|
||||
} else {
|
||||
if (ratio > 1) {
|
||||
health = "medium";
|
||||
color = "warning";
|
||||
} else {
|
||||
health = "bad";
|
||||
color = "danger";
|
||||
}
|
||||
}
|
||||
|
||||
const className = `text text-${color}`;
|
||||
const tooltip = (
|
||||
<Tooltip id={`tooltip-health-${props.url}`}>
|
||||
<p><span className={className}>Health: {health}</span></p>
|
||||
<p>Seeders: {seeders}</p>
|
||||
<p>Leechers: {props.leechers}</p>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<OverlayTrigger placement="right" overlay={tooltip}>
|
||||
<span className={className}>
|
||||
<i className="fa fa-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);
|
@ -2,15 +2,22 @@ import { Map, List, fromJS } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
"fetching": false,
|
||||
"searching": false,
|
||||
"torrents": List(),
|
||||
"searchResults": List(),
|
||||
});
|
||||
|
||||
const handlers = {
|
||||
"TORRENTS_FETCH_PENDING": state => state.set("fetching", false),
|
||||
"TORRENTS_FETCH_PENDING": state => state.set("fetching", true),
|
||||
"TORRENTS_FETCH_FULFILLED": (state, action) => state.merge(fromJS({
|
||||
fetching: false,
|
||||
torrents: action.payload.response.data,
|
||||
})),
|
||||
"TORRENTS_SEARCH_PENDING": state => state.set("searching", true),
|
||||
"TORRENTS_SEARCH_FULFILLED": (state, action) => state.merge(fromJS({
|
||||
searching: false,
|
||||
searchResults: action.payload.response.data,
|
||||
})),
|
||||
}
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
|
@ -30,12 +30,12 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
||||
})
|
||||
promise
|
||||
.then(response => {
|
||||
if (response.data.status === "error")
|
||||
if (response.status === "error")
|
||||
{
|
||||
dispatch({
|
||||
type: "ADD_ALERT_ERROR",
|
||||
payload: {
|
||||
message: response.data.message,
|
||||
message: response.message,
|
||||
main: mainPayload,
|
||||
}
|
||||
});
|
||||
@ -50,7 +50,7 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
||||
})
|
||||
if (callbackEvents) {
|
||||
for (let event of callbackEvents) {
|
||||
if (typeof event === 'function') {
|
||||
if (typeof event === "function") {
|
||||
event = event();
|
||||
}
|
||||
dispatch(event);
|
||||
|
@ -6,9 +6,10 @@ import UserEdit from "./components/users/edit"
|
||||
import UserActivation from "./components/users/activation"
|
||||
import UserSignUp from "./components/users/signup"
|
||||
import TorrentList from "./components/torrents/list"
|
||||
import TorrentSearch from "./components/torrents/search"
|
||||
import AdminPanel from "./components/admins/panel"
|
||||
|
||||
import { fetchTorrents } from "./actions/torrents"
|
||||
import { fetchTorrents, searchTorrents } from "./actions/torrents"
|
||||
import { userLogout, getUserInfos } from "./actions/users"
|
||||
import { fetchMovies, getMovieExploreOptions } from "./actions/movies"
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
|
||||
@ -240,7 +241,7 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents",
|
||||
path: "/torrents/list",
|
||||
component: TorrentList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
@ -248,6 +249,22 @@ export default function getRoutes(App) {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents/search",
|
||||
component: TorrentSearch,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents/search/:type/:search",
|
||||
component: TorrentSearch,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(searchTorrents(`/torrents/search/${nextState.params.type}/${encodeURI(nextState.params.search)}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
component: AdminPanel,
|
||||
|
@ -81,3 +81,25 @@ div.sweet-alert > h2 {
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
button.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.torrent-search-result {
|
||||
font-size: 15px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 2px solid @gray-light;
|
||||
td.torrent-small-width {
|
||||
width: 1%;
|
||||
}
|
||||
td.torrent-label {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
table.table-align-middle > tbody > tr > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ func setupRoutes(env *web.Env) {
|
||||
env.Handle("/torrents", torrents.DownloadHandler).WithRole(users.UserRole).Methods("POST")
|
||||
env.Handle("/torrents", torrents.ListHandler).WithRole(users.UserRole).Methods("GET")
|
||||
env.Handle("/torrents/{id:[0-9]+}", torrents.RemoveHandler).WithRole(users.UserRole).Methods("DELETE")
|
||||
env.Handle("/torrents/search/{type}/{search}", torrents.SearchHandler).WithRole(users.UserRole).Methods("GET")
|
||||
|
||||
// Route to refresh all movies and shows
|
||||
env.Handle("/refresh", extmedias.RefreshHandler).WithRole(users.AdminRole).Methods("POST")
|
||||
|
Loading…
x
Reference in New Issue
Block a user