"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",
<h3>Friends list</h3>
-<table class="table table-hover">
- <thead>
- <tr>
- <th class="table-column-id">ID</th>
- <th>Host</th>
- <th>Score</th>
- <th>Created Date</th>
- </tr>
- </thead>
+<ng2-smart-table [settings]="tableSettings" [source]="friendsSource"></ng2-smart-table>
- <tbody>
- <tr *ngFor="let friend of friends">
- <td>{{ friend.id }}</td>
- <td>{{ friend.host }}</td>
- <td>{{ friend.score }}</td>
- <td>{{ friend.createdAt | date: 'medium' }}</td>
- </tr>
- </tbody>
-</table>
-
-<a *ngIf="friends && friends.length !== 0" class="add-user btn btn-danger pull-left" (click)="quitFriends()">
+<a *ngIf="hasFriends()" class="add-user btn btn-danger pull-left" (click)="quitFriends()">
Quit friends
</a>
-<a *ngIf="friends?.length === 0" class="add-user btn btn-success pull-right" [routerLink]="['/admin/friends/add']">
+<a *ngIf="!hasFriends()" class="add-user btn btn-success pull-right" [routerLink]="['/admin/friends/add']">
Make friends
</a>
-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({
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() {
status => {
this.notificationsService.success('Sucess', 'Friends left!');
- this.getFriends();
+ this.friendsSource.refresh();
},
err => this.notificationsService.error('Error', err.text)
}
);
}
-
- private getFriends() {
- this.friendService.getFriends().subscribe(
- res => this.friends = res.friends,
-
- err => this.notificationsService.error('Error', err.text)
- );
- }
}
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 {
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) {
.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 };
- }
}
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 {
.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 };
- }
}
<h3>Users list</h3>
-<table class="table table-hover">
- <thead>
- <tr>
- <th class="table-column-id">ID</th>
- <th>Username</th>
- <th>Created Date</th>
- <th class="text-right">Remove</th>
- </tr>
- </thead>
-
- <tbody>
- <tr *ngFor="let user of users">
- <td>{{ user.id }}</td>
- <td>{{ user.username }}</td>
- <td>{{ user.createdAt | date: 'medium' }}</td>
- <td class="text-right">
- <span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span>
- </td>
- </tr>
- </tbody>
-</table>
+<ng2-smart-table
+ [settings]="tableSettings" [source]="usersSource"
+ (delete)="removeUser($event)"
+></ng2-smart-table>
<a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
<span class="glyphicon glyphicon-plus"></span>
-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({
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;
- },
-
- err => this.notificationsService.error('Error', err.text)
- );
- }
+ removeUser({ data }) {
+ const user: User = data;
+ 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;
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)
<h3>Video abuses list</h3>
-<table class="table table-hover">
- <thead>
- <tr>
- <th class="cell-id">ID</th>
- <th class="cell-reason">Reason</th>
- <th>Reporter pod host</th>
- <th>Reporter username</th>
- <th>Video</th>
- <th>Created at</th>
- </tr>
- </thead>
-
- <tbody>
- <tr *ngFor="let videoAbuse of videoAbuses">
- <td>{{ videoAbuse.id }}</td>
- <td>{{ videoAbuse.reason }}</td>
- <td>{{ videoAbuse.reporterPodHost }}</td>
- <td>{{ videoAbuse.reporterUsername }}</td>
- <td>
- <a [routerLink]="buildVideoLink(videoAbuse)" title="Go to video">{{ videoAbuse.videoId }}</a>
- </td>
- <td>{{ videoAbuse.createdAt | date: 'medium' }}</td>
- </tr>
- </tbody>
-</table>
+<ng2-smart-table
+ [settings]="tableSettings" [source]="videoAbusesSource"
+></ng2-smart-table>
-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
- ) { }
-
- ngOnInit() {
- this.getVideoAbuses();
- }
-
- buildVideoLink(videoAbuse: VideoAbuse) {
- return `/videos/${videoAbuse.videoId}`;
- }
-
- private getVideoAbuses() {
- this.videoAbuseService.getVideoAbuses().subscribe(
- res => this.videoAbuses = res.videoAbuses,
+ ) {
+ this.videoAbusesSource = this.videoAbuseService.getDataSource();
+ }
- err => this.notificationsService.error('Error', err.text)
- );
+ buildVideoLink(videoId: string) {
+ // TODO: transform to routerLink
+ // https://github.com/akveo/ng2-smart-table/issues/57
+ return `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>`;
}
}
export * from './users';
export * from './video-abuse';
export * from './shared.module';
+export * from './utils';
+export * from './rest-data-source';
export * from './rest-extractor.service';
export * from './rest-pagination';
export * from './rest.service';
--- /dev/null
+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 = <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 = <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;
+ }
+}
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';
PaginationModule.forRoot(),
ProgressbarModule.forRoot(),
- FileUploadModule
+ FileUploadModule,
+ Ng2SmartTableModule
],
declarations: [
ModalModule,
PaginationModule,
ProgressbarModule,
+ Ng2SmartTableModule,
BytesPipe,
SearchComponent
--- /dev/null
+import { DatePipe } from '@angular/common';
+
+export class Utils {
+
+ static dateToHuman(date: String) {
+ return new DatePipe('en').transform(date, 'medium')
+ }
+
+ static getRowDeleteButton() {
+ return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>';
+ }
+}
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()
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) {
}
}
-.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] {
background-color: #fff !important;
}
+.glyphicon-black {
+ color: black;
+}
+
footer {
border-top: 1px solid rgba(0, 0, 0, 0.2);
padding-top: 10px;
// 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' ]
}