diff --git a/client/package.json b/client/package.json
index bd6cb03e7..c762a1fd5 100644
--- a/client/package.json
+++ b/client/package.json
@@ -58,6 +58,7 @@
"ng2-bootstrap": "1.1.16-10",
"ng2-file-upload": "^1.1.4-2",
"ng2-meta": "https://github.com/chocobozzz/ng2-meta#build",
+ "ng2-smart-table": "^0.5.1-0",
"ngc-webpack": "1.1.0",
"node-sass": "^4.1.1",
"normalize.css": "^5.0.0",
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.html b/client/src/app/+admin/friends/friend-list/friend-list.component.html
index 06258f8c8..254d0c65e 100644
--- a/client/src/app/+admin/friends/friend-list/friend-list.component.html
+++ b/client/src/app/+admin/friends/friend-list/friend-list.component.html
@@ -1,29 +1,11 @@
Friends list
-
-
-
- ID |
- Host |
- Score |
- Created Date |
-
-
+
-
-
- {{ friend.id }} |
- {{ friend.host }} |
- {{ friend.score }} |
- {{ friend.createdAt | date: 'medium' }} |
-
-
-
-
-
+
Quit friends
-
+
Make friends
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.ts b/client/src/app/+admin/friends/friend-list/friend-list.component.ts
index 175ad9cba..f29427640 100644
--- a/client/src/app/+admin/friends/friend-list/friend-list.component.ts
+++ b/client/src/app/+admin/friends/friend-list/friend-list.component.ts
@@ -1,8 +1,10 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
import { NotificationsService } from 'angular2-notifications';
+import { ServerDataSource } from 'ng2-smart-table';
import { ConfirmService } from '../../../core';
+import { Utils } from '../../../shared';
import { Friend, FriendService } from '../shared';
@Component({
@@ -10,17 +12,51 @@ import { Friend, FriendService } from '../shared';
templateUrl: './friend-list.component.html',
styleUrls: [ './friend-list.component.scss' ]
})
-export class FriendListComponent implements OnInit {
- friends: Friend[];
+export class FriendListComponent {
+ friendsSource = null;
+ tableSettings = {
+ attr: {
+ class: 'table-hover'
+ },
+ hideSubHeader: true,
+ actions: {
+ position: 'right',
+ add: false,
+ edit: false,
+ delete: false
+ },
+ columns: {
+ id: {
+ title: 'ID',
+ sort: false,
+ sortDirection: 'asc'
+ },
+ host: {
+ title: 'Host',
+ sort: false
+ },
+ score: {
+ title: 'Score',
+ sort: false
+ },
+ createdAt: {
+ title: 'Created Date',
+ sort: false,
+ valuePrepareFunction: Utils.dateToHuman
+ }
+ }
+ }
constructor(
private notificationsService: NotificationsService,
private confirmService: ConfirmService,
private friendService: FriendService
- ) { }
+ ) {
+ this.friendsSource = this.friendService.getDataSource();
+ }
- ngOnInit() {
- this.getFriends();
+ hasFriends() {
+ return this.friendsSource.count() != 0;
}
quitFriends() {
@@ -33,7 +69,7 @@ export class FriendListComponent implements OnInit {
status => {
this.notificationsService.success('Sucess', 'Friends left!');
- this.getFriends();
+ this.friendsSource.refresh();
},
err => this.notificationsService.error('Error', err.text)
@@ -41,12 +77,4 @@ export class FriendListComponent implements OnInit {
}
);
}
-
- private getFriends() {
- this.friendService.getFriends().subscribe(
- res => this.friends = res.friends,
-
- err => this.notificationsService.error('Error', err.text)
- );
- }
}
diff --git a/client/src/app/+admin/friends/shared/friend.service.ts b/client/src/app/+admin/friends/shared/friend.service.ts
index e97459385..6cb84f5cd 100644
--- a/client/src/app/+admin/friends/shared/friend.service.ts
+++ b/client/src/app/+admin/friends/shared/friend.service.ts
@@ -3,8 +3,10 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
+import { ServerDataSource } from 'ng2-smart-table';
+
import { Friend } from './friend.model';
-import { AuthHttp, RestExtractor, ResultList } from '../../../shared';
+import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared';
@Injectable()
export class FriendService {
@@ -15,11 +17,8 @@ export class FriendService {
private restExtractor: RestExtractor
) {}
- getFriends() {
- return this.authHttp.get(FriendService.BASE_FRIEND_URL)
- .map(this.restExtractor.extractDataList)
- .map(this.extractFriends)
- .catch((res) => this.restExtractor.handleError(res));
+ getDataSource() {
+ return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL);
}
makeFriends(notEmptyHosts) {
@@ -37,11 +36,4 @@ export class FriendService {
.map(res => res.status)
.catch((res) => this.restExtractor.handleError(res));
}
-
- private extractFriends(result: ResultList) {
- const friends: Friend[] = result.data;
- const totalFriends = result.total;
-
- return { friends, totalFriends };
- }
}
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts
index d9005b213..f6d360e09 100644
--- a/client/src/app/+admin/users/shared/user.service.ts
+++ b/client/src/app/+admin/users/shared/user.service.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
-import { AuthHttp, RestExtractor, ResultList, User } from '../../../shared';
+import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared';
@Injectable()
export class UserService {
@@ -25,25 +25,11 @@ export class UserService {
.catch(this.restExtractor.handleError);
}
- getUsers() {
- return this.authHttp.get(UserService.BASE_USERS_URL)
- .map(this.restExtractor.extractDataList)
- .map(this.extractUsers)
- .catch((res) => this.restExtractor.handleError(res));
+ getDataSource() {
+ return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL);
}
removeUser(user: User) {
return this.authHttp.delete(UserService.BASE_USERS_URL + user.id);
}
-
- private extractUsers(result: ResultList) {
- const usersJson = result.data;
- const totalUsers = result.total;
- const users = [];
- for (const userJson of usersJson) {
- users.push(new User(userJson));
- }
-
- return { users, totalUsers };
- }
}
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index 36193d119..3d3d7e054 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -1,26 +1,9 @@
Users list
-
-
-
- ID |
- Username |
- Created Date |
- Remove |
-
-
-
-
-
- {{ user.id }} |
- {{ user.username }} |
- {{ user.createdAt | date: 'medium' }} |
-
-
- |
-
-
-
+
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index baefb7064..db025d3a8 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -1,9 +1,9 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
import { NotificationsService } from 'angular2-notifications';
import { ConfirmService } from '../../../core';
-import { User } from '../../../shared';
+import { User, Utils } from '../../../shared';
import { UserService } from '../shared';
@Component({
@@ -11,33 +11,62 @@ import { UserService } from '../shared';
templateUrl: './user-list.component.html',
styleUrls: [ './user-list.component.scss' ]
})
-export class UserListComponent implements OnInit {
- totalUsers: number;
- users: User[];
+export class UserListComponent {
+ usersSource = null;
+ tableSettings = {
+ mode: 'external',
+ attr: {
+ class: 'table-hover'
+ },
+ hideSubHeader: true,
+ actions: {
+ position: 'right',
+ add: false,
+ edit: false,
+ delete: true
+ },
+ delete: {
+ deleteButtonContent: Utils.getRowDeleteButton()
+ },
+ pager: {
+ display: true,
+ perPage: 10
+ },
+ columns: {
+ id: {
+ title: 'ID',
+ sortDirection: 'asc'
+ },
+ username: {
+ title: 'Username'
+ },
+ role: {
+ title: 'Role',
+ sort: false
+ },
+ createdAt: {
+ title: 'Created Date',
+ valuePrepareFunction: Utils.dateToHuman
+ }
+ }
+ }
constructor(
private notificationsService: NotificationsService,
private confirmService: ConfirmService,
private userService: UserService
- ) {}
-
- ngOnInit() {
- this.getUsers();
+ ) {
+ this.usersSource = this.userService.getDataSource();
}
- getUsers() {
- this.userService.getUsers().subscribe(
- ({ users, totalUsers }) => {
- this.users = users;
- this.totalUsers = totalUsers;
- },
+ removeUser({ data }) {
+ const user: User = data;
- err => this.notificationsService.error('Error', err.text)
- );
- }
+ if (user.username === 'root') {
+ this.notificationsService.error('Error', 'You cannot delete root.');
+ return;
+ }
-
- removeUser(user: User) {
this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe(
res => {
if (res === false) return;
@@ -45,7 +74,7 @@ export class UserListComponent implements OnInit {
this.userService.removeUser(user).subscribe(
() => {
this.notificationsService.success('Success', `User ${user.username} deleted.`);
- this.getUsers();
+ this.usersSource.refresh();
},
err => this.notificationsService.error('Error', err.text)
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
index 46043577c..b2fd17bf0 100644
--- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
+++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
@@ -1,27 +1,5 @@
Video abuses list
-
-
-
- ID |
- Reason |
- Reporter pod host |
- Reporter username |
- Video |
- Created at |
-
-
-
-
-
- {{ videoAbuse.id }} |
- {{ videoAbuse.reason }} |
- {{ videoAbuse.reporterPodHost }} |
- {{ videoAbuse.reporterUsername }} |
-
- {{ videoAbuse.videoId }}
- |
- {{ videoAbuse.createdAt | date: 'medium' }} |
-
-
-
+
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
index cfd9151b0..2f22a4ab0 100644
--- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
+++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
@@ -1,35 +1,72 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
import { NotificationsService } from 'angular2-notifications';
-import { VideoAbuseService, VideoAbuse} from '../../../shared';
+import { Utils, VideoAbuseService, VideoAbuse} from '../../../shared';
@Component({
selector: 'my-video-abuse-list',
templateUrl: './video-abuse-list.component.html',
styleUrls: [ './video-abuse-list.component.scss' ]
})
-export class VideoAbuseListComponent implements OnInit {
- videoAbuses: VideoAbuse[];
+export class VideoAbuseListComponent {
+ videoAbusesSource = null;
+ tableSettings = {
+ mode: 'external',
+ attr: {
+ class: 'table-hover'
+ },
+ hideSubHeader: true,
+ actions: {
+ position: 'right',
+ add: false,
+ edit: false,
+ delete: false
+ },
+ pager: {
+ display: true,
+ perPage: 10
+ },
+ columns: {
+ id: {
+ title: 'ID',
+ sortDirection: 'asc'
+ },
+ reason: {
+ title: 'Reason',
+ sort: false
+ },
+ reporterPodHost: {
+ title: 'Reporter pod host',
+ sort: false
+ },
+ reporterUsername: {
+ title: 'Reporter username',
+ sort: false
+ },
+ videoId: {
+ title: 'Video',
+ type: 'html',
+ sort: false,
+ valuePrepareFunction: this.buildVideoLink
+ },
+ createdAt: {
+ title: 'Created Date',
+ valuePrepareFunction: Utils.dateToHuman
+ }
+ }
+ }
constructor(
private notificationsService: NotificationsService,
private videoAbuseService: VideoAbuseService
- ) { }
+ ) {
+ this.videoAbusesSource = this.videoAbuseService.getDataSource();
+ }
- ngOnInit() {
- this.getVideoAbuses();
- }
-
- buildVideoLink(videoAbuse: VideoAbuse) {
- return `/videos/${videoAbuse.videoId}`;
- }
-
- private getVideoAbuses() {
- this.videoAbuseService.getVideoAbuses().subscribe(
- res => this.videoAbuses = res.videoAbuses,
-
- err => this.notificationsService.error('Error', err.text)
- );
+ buildVideoLink(videoId: string) {
+ // TODO: transform to routerLink
+ // https://github.com/akveo/ng2-smart-table/issues/57
+ return `${videoId}`;
}
}
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts
index 7876e6f14..61e8ed523 100644
--- a/client/src/app/shared/index.ts
+++ b/client/src/app/shared/index.ts
@@ -5,3 +5,4 @@ export * from './search';
export * from './users';
export * from './video-abuse';
export * from './shared.module';
+export * from './utils';
diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts
index 3c9509dc7..3cb123c3b 100644
--- a/client/src/app/shared/rest/index.ts
+++ b/client/src/app/shared/rest/index.ts
@@ -1,3 +1,4 @@
+export * from './rest-data-source';
export * from './rest-extractor.service';
export * from './rest-pagination';
export * from './rest.service';
diff --git a/client/src/app/shared/rest/rest-data-source.ts b/client/src/app/shared/rest/rest-data-source.ts
new file mode 100644
index 000000000..847dd7c56
--- /dev/null
+++ b/client/src/app/shared/rest/rest-data-source.ts
@@ -0,0 +1,51 @@
+import { Http, RequestOptionsArgs, URLSearchParams, } from '@angular/http';
+
+import { ServerDataSource } from 'ng2-smart-table';
+
+export class RestDataSource extends ServerDataSource {
+ constructor(http: Http, endpoint: string) {
+ const options = {
+ endPoint: endpoint,
+ sortFieldKey: 'sort',
+ dataKey: 'data'
+ }
+
+ super(http, options);
+ }
+
+ protected extractTotalFromResponse(res) {
+ const rawData = res.json();
+ return rawData ? parseInt(rawData.total): 0;
+ }
+
+ protected addSortRequestOptions(requestOptions: RequestOptionsArgs) {
+ let searchParams: URLSearchParams = requestOptions.search;
+
+ if (this.sortConf) {
+ this.sortConf.forEach((fieldConf) => {
+ const sortPrefix = fieldConf.direction === 'desc' ? '-' : '';
+
+ searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field);
+ });
+ }
+
+ return requestOptions;
+ }
+
+ protected addPagerRequestOptions(requestOptions: RequestOptionsArgs) {
+ let searchParams: URLSearchParams = requestOptions.search;
+
+ if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) {
+ const perPage = this.pagingConf['perPage'];
+ const page = this.pagingConf['page'];
+
+ const start = (page - 1) * perPage;
+ const count = perPage;
+
+ searchParams.set('start', start.toString());
+ searchParams.set('count', count.toString());
+ }
+
+ return requestOptions;
+ }
+}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 7b2386d6c..99893c8b1 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -10,6 +10,7 @@ import { ProgressbarModule } from 'ng2-bootstrap/progressbar';
import { PaginationModule } from 'ng2-bootstrap/pagination';
import { ModalModule } from 'ng2-bootstrap/modal';
import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload';
+import { Ng2SmartTableModule } from 'ng2-smart-table';
import { AUTH_HTTP_PROVIDERS } from './auth';
import { RestExtractor, RestService } from './rest';
@@ -29,7 +30,8 @@ import { VideoAbuseService } from './video-abuse';
PaginationModule.forRoot(),
ProgressbarModule.forRoot(),
- FileUploadModule
+ FileUploadModule,
+ Ng2SmartTableModule
],
declarations: [
@@ -49,6 +51,7 @@ import { VideoAbuseService } from './video-abuse';
ModalModule,
PaginationModule,
ProgressbarModule,
+ Ng2SmartTableModule,
BytesPipe,
SearchComponent
diff --git a/client/src/app/shared/utils.ts b/client/src/app/shared/utils.ts
new file mode 100644
index 000000000..1dd6f96f0
--- /dev/null
+++ b/client/src/app/shared/utils.ts
@@ -0,0 +1,12 @@
+import { DatePipe } from '@angular/common';
+
+export class Utils {
+
+ static dateToHuman(date: String) {
+ return new DatePipe('en').transform(date, 'medium')
+ }
+
+ static getRowDeleteButton() {
+ return '';
+ }
+}
diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts
index 2750a41c7..f23c36f05 100644
--- a/client/src/app/shared/video-abuse/video-abuse.service.ts
+++ b/client/src/app/shared/video-abuse/video-abuse.service.ts
@@ -6,7 +6,7 @@ import 'rxjs/add/operator/map';
import { AuthService } from '../core';
import { AuthHttp } from '../auth';
-import { RestExtractor, ResultList } from '../rest';
+import { RestDataSource, RestExtractor, ResultList } from '../rest';
import { VideoAbuse } from './video-abuse.model';
@Injectable()
@@ -18,10 +18,8 @@ export class VideoAbuseService {
private restExtractor: RestExtractor
) {}
- getVideoAbuses() {
- return this.authHttp.get(VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse')
- .map(this.restExtractor.extractDataList)
- .map(this.extractVideoAbuses)
+ getDataSource() {
+ return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse');
}
reportVideo(id: string, reason: string) {
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 30588067f..994b1e2b9 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -42,8 +42,23 @@ menu {
}
}
-.table-column-id {
- width: 200px;
+.ng2-smart-table-container {
+ .ng2-smart-table {
+
+ thead tr {
+ border-top: 1px solid rgb(233, 235, 236)
+ }
+
+ td, th {
+ padding: 8px !important;
+ color: #333333 !important;
+ font-size: 14px !important;
+ }
+ }
+
+ .ng2-smart-pagination-nav .page-link {
+ font-size: 11px !important;
+ }
}
[hidden] {
@@ -55,6 +70,10 @@ input.readonly {
background-color: #fff !important;
}
+.glyphicon-black {
+ color: black;
+}
+
footer {
border-top: 1px solid rgba(0, 0, 0, 0.2);
padding-top: 10px;
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
index 90adbf406..ad7cf4f4d 100644
--- a/server/initializers/constants.js
+++ b/server/initializers/constants.js
@@ -18,8 +18,8 @@ const SEARCHABLE_COLUMNS = {
// Sortable columns per schema
const SORTABLE_COLUMNS = {
- USERS: [ 'username', '-username', 'createdAt', '-createdAt' ],
- VIDEO_ABUSES: [ 'createdAt', '-createdAt' ],
+ USERS: [ 'id', '-id', 'username', '-username', 'createdAt', '-createdAt' ],
+ VIDEO_ABUSES: [ 'id', '-id', 'createdAt', '-createdAt' ],
VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdAt', '-createdAt' ]
}