mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-09 16:42:48 +00:00
add upload history to stats UI
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import './scss/index.scss';
|
|
||||||
import { Provider } from 'mobx-react';
|
import { Provider } from 'mobx-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||||
import { Clips, Downloader, NotFound, Oauth, Soundboard, Stats, VideoArchive } from './pages';
|
import { Admin, Clips, Downloader, NotFound, Oauth, Soundboard, Stats, VideoArchive } from './pages';
|
||||||
|
import './scss/index.scss';
|
||||||
import { rootStoreInstance } from './stores';
|
import { rootStoreInstance } from './stores';
|
||||||
import { Wrapper } from './wrapper';
|
import { Wrapper } from './wrapper';
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ const App: any = (): any => {
|
|||||||
<Route path="/oauth" component={Oauth} />
|
<Route path="/oauth" component={Oauth} />
|
||||||
<Route path="/stats" component={Stats} />
|
<Route path="/stats" component={Stats} />
|
||||||
<Route path="/video-archive" component={VideoArchive} />
|
<Route path="/video-archive" component={VideoArchive} />
|
||||||
|
<Route path="/admin" component={Admin} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export * from './embedded-youtube/embedded-youtube';
|
|||||||
export * from './header/header';
|
export * from './header/header';
|
||||||
export * from './navbar/navbar';
|
export * from './navbar/navbar';
|
||||||
export * from './sound-list/sound-list';
|
export * from './sound-list/sound-list';
|
||||||
|
export * from './upload-history/upload-history';
|
||||||
export * from './uploader/uploader';
|
export * from './uploader/uploader';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { IClaims } from '../../model';
|
import { IClaims, Permissions } from '../../model';
|
||||||
import { OauthService, StorageService } from '../../services';
|
import { OauthService, StorageService } from '../../services';
|
||||||
import './navbar.scss';
|
import './navbar.scss';
|
||||||
|
|
||||||
@@ -78,6 +78,10 @@ export class Navbar extends React.Component<Props, State> {
|
|||||||
{this.renderNavLink('Youtube Downloader', '/downloader')}
|
{this.renderNavLink('Youtube Downloader', '/downloader')}
|
||||||
{this.renderNavLink('Clips', '/clips')}
|
{this.renderNavLink('Clips', '/clips')}
|
||||||
{this.renderNavLink('Stats', '/stats')}
|
{this.renderNavLink('Stats', '/stats')}
|
||||||
|
{claims &&
|
||||||
|
claims.permissions &&
|
||||||
|
claims.permissions === Permissions.Admin &&
|
||||||
|
this.renderNavLink('Admin', '/admin')}
|
||||||
{this.renderLoginButton()}
|
{this.renderLoginButton()}
|
||||||
|
|
||||||
{claims && claims.email && <div className="navbar__email">{claims.email}</div>}
|
{claims && claims.email && <div className="navbar__email">{claims.email}</div>}
|
||||||
|
|||||||
46
client/app/components/upload-history/upload-history.tsx
Normal file
46
client/app/components/upload-history/upload-history.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import * as _ from 'lodash';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import React from 'react';
|
||||||
|
import { ISound } from '../../model';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
sounds: ISound[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadHistory = ({ sounds }: IProps) => {
|
||||||
|
const sortedSounds = _.orderBy(sounds, 'created_at', 'desc');
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card__header">Upload History</div>
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Sound</th>
|
||||||
|
<th className="hide-tiny">Ext</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th className="hide-tiny">Email</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{sortedSounds.map((s: ISound, i) => {
|
||||||
|
const formattedDate = DateTime.fromISO(s.created_at).toLocaleString();
|
||||||
|
return (
|
||||||
|
<tr key={i}>
|
||||||
|
<td title={formattedDate}>{formattedDate}</td>
|
||||||
|
<td title={s.name}>{s.name}</td>
|
||||||
|
<td className="hide-tiny" title={s.extension}>
|
||||||
|
{s.extension}
|
||||||
|
</td>
|
||||||
|
<td title={s.user.username}>{s.user.username}</td>
|
||||||
|
<td className="hide-tiny" title={s.user.email}>
|
||||||
|
{s.user.email}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './claims';
|
export * from './claims';
|
||||||
export * from './permissions';
|
export * from './permissions';
|
||||||
|
export * from './sound';
|
||||||
|
export * from './user';
|
||||||
export * from './video-archive';
|
export * from './video-archive';
|
||||||
|
|||||||
12
client/app/model/sound.ts
Normal file
12
client/app/model/sound.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { IUser } from './user';
|
||||||
|
|
||||||
|
export interface ISound {
|
||||||
|
created_at: string;
|
||||||
|
deleted_at?: string;
|
||||||
|
extension: string;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
updated_at: string;
|
||||||
|
user: IUser;
|
||||||
|
user_id: string;
|
||||||
|
}
|
||||||
17
client/app/model/user.ts
Normal file
17
client/app/model/user.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Permissions } from './permissions';
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
avatar: string;
|
||||||
|
bot: boolean;
|
||||||
|
created_at: string;
|
||||||
|
deleted_at?: string;
|
||||||
|
discriminator: string;
|
||||||
|
email: string;
|
||||||
|
id: string;
|
||||||
|
mfa_enabled: boolean;
|
||||||
|
permissions: Permissions;
|
||||||
|
token: string;
|
||||||
|
updated_at: string;
|
||||||
|
username: string;
|
||||||
|
verified: boolean;
|
||||||
|
}
|
||||||
11
client/app/pages/admin/admin.tsx
Normal file
11
client/app/pages/admin/admin.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface IProps {}
|
||||||
|
|
||||||
|
interface IState {}
|
||||||
|
|
||||||
|
export class Admin extends React.Component<IProps, IState> {
|
||||||
|
render() {
|
||||||
|
return <div className="content">TODO:</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
client/app/pages/admin/index.ts
Normal file
1
client/app/pages/admin/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './admin';
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from './admin';
|
||||||
export * from './clips/clips';
|
export * from './clips/clips';
|
||||||
export * from './downloader/downloader';
|
export * from './downloader/downloader';
|
||||||
export * from './not-found/not-found';
|
export * from './not-found/not-found';
|
||||||
|
|||||||
@@ -1,30 +1,45 @@
|
|||||||
|
import { chain, map } from 'lodash';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { HorizontalBar } from 'react-chartjs-2';
|
import { HorizontalBar } from 'react-chartjs-2';
|
||||||
import { chain, map } from 'lodash';
|
import { UploadHistory } from '../../components';
|
||||||
import { axios } from '../../services';
|
import { ISound } from '../../model';
|
||||||
|
import { axios, SoundService } from '../../services';
|
||||||
|
import { AppStore } from '../../stores';
|
||||||
import './stats.scss';
|
import './stats.scss';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
appStore: AppStore;
|
||||||
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
data: {
|
data: {
|
||||||
username: string;
|
username: string;
|
||||||
count: number;
|
count: number;
|
||||||
}[];
|
}[];
|
||||||
|
sounds: ISound[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a page to show discord chat statistics
|
* a page to show discord chat statistics
|
||||||
* currently keeps track of number messages that contain external links
|
* currently keeps track of number messages that contain external links
|
||||||
*/
|
*/
|
||||||
export class Stats extends Component<any, IState> {
|
@inject('appStore')
|
||||||
|
@observer
|
||||||
|
export class Stats extends Component<IProps, IState> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
data: [],
|
data: [],
|
||||||
|
sounds: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getdata();
|
this.getdata();
|
||||||
|
SoundService.getSounds().then(sounds => {
|
||||||
|
this.setState({ sounds });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getdata() {
|
async getdata() {
|
||||||
@@ -59,12 +74,15 @@ export class Stats extends Component<any, IState> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { claims } = this.props.appStore;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="card" style={{ maxWidth: '1000px' }}>
|
<div className="card">
|
||||||
<div className="card__header">Posts containing links</div>
|
<div className="card__header">Posts containing links</div>
|
||||||
<HorizontalBar data={data} />
|
<HorizontalBar data={data} />
|
||||||
</div>
|
</div>
|
||||||
|
{claims && <UploadHistory sounds={this.state.sounds} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,3 +12,4 @@
|
|||||||
@import './input.scss';
|
@import './input.scss';
|
||||||
@import './grid.scss';
|
@import './grid.scss';
|
||||||
@import './nprogress.scss';
|
@import './nprogress.scss';
|
||||||
|
@import './table.scss';
|
||||||
|
|||||||
@@ -71,3 +71,9 @@ body {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide-tiny {
|
||||||
|
@include tinyScreen {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
client/app/scss/table.scss
Normal file
14
client/app/scss/table.scss
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
thead {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
import { SoundType } from '../components/sound-list/sound-list';
|
import { SoundType } from '../components/sound-list/sound-list';
|
||||||
|
import { ISound } from '../model';
|
||||||
import { axios } from './axios.service';
|
import { axios } from './axios.service';
|
||||||
|
|
||||||
const playSound = (sound: SoundType): Promise<any> => {
|
const playSound = (sound: SoundType): Promise<any> => {
|
||||||
return axios.post('/api/sound/play', { name: sound.name });
|
return axios.post('/api/sound/play', { name: sound.name });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSounds = async (): Promise<ISound[]> => {
|
||||||
|
const res = await axios.get('/api/sound');
|
||||||
|
return res.data.data;
|
||||||
|
};
|
||||||
|
|
||||||
export const SoundService = {
|
export const SoundService = {
|
||||||
|
getSounds,
|
||||||
playSound,
|
playSound,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user