aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html4
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss5
-rw-r--r--client/src/app/+admin/users/user-edit/user-create.component.ts2
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.component.html268
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.component.scss35
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.ts19
-rw-r--r--client/src/app/+admin/users/user-edit/user-password.component.ts2
-rw-r--r--client/src/app/+admin/users/user-edit/user-update.component.ts33
-rw-r--r--client/src/app/+my-account/my-account.module.ts2
-rw-r--r--client/src/app/+my-account/shared/actor-avatar-info.component.html13
-rw-r--r--client/src/app/shared/shared.module.ts7
-rw-r--r--client/src/app/shared/users/user.model.ts10
-rw-r--r--client/src/app/shared/users/user.service.ts5
-rw-r--r--client/src/app/shared/video/modals/video-report.component.html3
-rw-r--r--client/src/sass/include/_mixins.scss82
15 files changed, 358 insertions, 132 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
index b3b4f7728..9991e1f63 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
@@ -579,7 +579,7 @@
579 i18n-labelText labelText="Allow additional extensions" 579 i18n-labelText labelText="Allow additional extensions"
580 > 580 >
581 <ng-container ngProjectAs="description"> 581 <ng-container ngProjectAs="description">
582 <span i18n>Allow your users to upload .mkv, .mov, .avi and .flv videos.</span> 582 <span i18n>Allows users to upload .mkv, .mov, .avi and .flv videos.</span>
583 </ng-container> 583 </ng-container>
584 </my-peertube-checkbox> 584 </my-peertube-checkbox>
585 </div> 585 </div>
@@ -590,7 +590,7 @@
590 i18n-labelText labelText="Allow audio files upload" 590 i18n-labelText labelText="Allow audio files upload"
591 > 591 >
592 <ng-container ngProjectAs="description"> 592 <ng-container ngProjectAs="description">
593 <span i18n>Allow your users to upload audio files that will be merged with the preview file on upload.</span> 593 <span i18n>Allows users to upload audio files that will be merged with the preview file on upload.</span>
594 </ng-container> 594 </ng-container>
595 </my-peertube-checkbox> 595 </my-peertube-checkbox>
596 </div> 596 </div>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
index 8b1cdcbba..d8bc30d55 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
@@ -50,6 +50,7 @@ input[type=submit] {
50textarea { 50textarea {
51 @include peertube-textarea(500px, 150px); 51 @include peertube-textarea(500px, 150px);
52 52
53 max-width: 100%;
53 display: block; 54 display: block;
54 55
55 &.small { 56 &.small {
@@ -72,6 +73,10 @@ my-markdown-textarea ::ng-deep {
72 @media screen and (max-width: 1400px) { 73 @media screen and (max-width: 1400px) {
73 flex-direction: column !important; 74 flex-direction: column !important;
74 } 75 }
76
77 textarea {
78 max-width: 100%;
79 }
75 } 80 }
76} 81}
77 82
diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts
index 1769c0de0..a394418cb 100644
--- a/client/src/app/+admin/users/user-edit/user-create.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-create.component.ts
@@ -8,6 +8,7 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
8import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' 8import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
9import { ConfigService } from '@app/+admin/config/shared/config.service' 9import { ConfigService } from '@app/+admin/config/shared/config.service'
10import { UserService } from '@app/shared' 10import { UserService } from '@app/shared'
11import { ScreenService } from '@app/shared/misc/screen.service'
11 12
12@Component({ 13@Component({
13 selector: 'my-user-create', 14 selector: 'my-user-create',
@@ -21,6 +22,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
21 protected serverService: ServerService, 22 protected serverService: ServerService,
22 protected formValidatorService: FormValidatorService, 23 protected formValidatorService: FormValidatorService,
23 protected configService: ConfigService, 24 protected configService: ConfigService,
25 protected screenService: ScreenService,
24 protected auth: AuthService, 26 protected auth: AuthService,
25 private userValidatorsService: UserValidatorsService, 27 private userValidatorsService: UserValidatorsService,
26 private route: ActivatedRoute, 28 private route: ActivatedRoute,
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html
index dbb0e36b9..6c42fde57 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.component.html
+++ b/client/src/app/+admin/users/user-edit/user-edit.component.html
@@ -1,112 +1,204 @@
1<div i18n class="form-sub-title" *ngIf="isCreation() === true">Create user</div> 1<nav aria-label="breadcrumb">
2<div i18n class="form-sub-title" *ngIf="isCreation() === false">Edit user {{ username }}</div> 2 <ol class="breadcrumb">
3 <li class="breadcrumb-item">
4 <a routerLink="/admin/users" i18n>Users</a>
5 </li>
3 6
4<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 7 <ng-container *ngIf="isCreation()">
8 <li class="breadcrumb-item active" i18n>Create</li>
9 </ng-container>
10 <ng-container *ngIf="!isCreation()">
11 <li class="breadcrumb-item active" i18n>Edit</li>
12 <li class="breadcrumb-item active" aria-current="page">
13 <a *ngIf="user" [routerLink]="[ '/accounts', user?.username ]">{{ user?.username }}</a>
14 </li>
15 </ng-container>
16 </ol>
17</nav>
5 18
6<form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> 19<ng-template #dashboard>
7 <div class="form-group" *ngIf="isCreation()"> 20 <div *ngIf="!isCreation() && user" class="dashboard">
8 <label i18n for="username">Username</label> 21 <div>
9 <input 22 <a>
10 type="text" id="username" i18n-placeholder placeholder="john" 23 <div class="dashboard-num">{{ user.videosCount }} ({{ user.videoQuotaUsed | bytes: 0 }})</div>
11 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }" 24 <div class="dashboard-label" i18n>{user.videosCount, plural, =1 {Video} other {Videos}}</div>
12 > 25 </a>
13 <div *ngIf="formErrors.username" class="form-error">
14 {{ formErrors.username }}
15 </div> 26 </div>
16 </div> 27 <div>
17 28 <a>
18 <div class="form-group"> 29 <div class="dashboard-num">{{ user.videoChannels.length || 0 }}</div>
19 <label i18n for="email">Email</label> 30 <div class="dashboard-label" i18n>{user.videoChannels.length, plural, =1 {Channel} other {Channels}}</div>
20 <input 31 </a>
21 type="text" id="email" i18n-placeholder placeholder="mail@example.com"
22 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
23 autocomplete="off"
24 >
25 <div *ngIf="formErrors.email" class="form-error">
26 {{ formErrors.email }}
27 </div> 32 </div>
28 </div> 33 <div>
29 34 <a>
30 <div class="form-group" *ngIf="isCreation()"> 35 <div class="dashboard-num">{{ subscribersCount }}</div>
31 <label i18n for="password">Password</label> 36 <div class="dashboard-label" i18n>{subscribersCount, plural, =1 {Subscriber} other {Subscribers}}</div>
32 <my-help *ngIf="isPasswordOptional()"> 37 </a>
33 <ng-template ptTemplate="customHtml"> 38 </div>
34 <ng-container i18n> 39 <div>
35 If you leave the password empty, an email will be sent to the user. 40 <a>
36 </ng-container> 41 <div class="dashboard-num">{{ user.videoAbusesCount }}</div>
37 </ng-template> 42 <div class="dashboard-label" i18n>Incriminated in reports</div>
38 </my-help> 43 </a>
39 <input 44 </div>
40 type="password" id="password" autocomplete="new-password" 45 <div>
41 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" 46 <a>
42 > 47 <div class="dashboard-num">{{ user.videoAbusesAcceptedCount }} / {{ user.videoAbusesCreatedCount }}</div>
43 <div *ngIf="formErrors.password" class="form-error"> 48 <div class="dashboard-label" i18n>Authored reports accepted</div>
44 {{ formErrors.password }} 49 </a>
50 </div>
51 <div>
52 <a>
53 <div class="dashboard-num">{{ user.videoCommentsCount }}</div>
54 <div class="dashboard-label" i18n>{user.videoCommentsCount, plural, =1 {Comment} other {Comments}}</div>
55 </a>
45 </div> 56 </div>
46 </div> 57 </div>
58</ng-template>
47 59
48 <div class="form-group"> 60<div class="form-row" *ngIf="!isInBigView()"> <!-- hidden on large screens, as it is then displayed on the right side of the form -->
49 <label i18n for="role">Role</label> 61 <div class="col-12 col-xl-3"></div>
50 <div class="peertube-select-container">
51 <select id="role" formControlName="role">
52 <option *ngFor="let role of roles" [value]="role.value">
53 {{ role.label }}
54 </option>
55 </select>
56 </div>
57 62
58 <div *ngIf="formErrors.role" class="form-error"> 63 <div class="form-group-right col-12 col-xl-9">
59 {{ formErrors.role }} 64 <ng-template *ngTemplateOutlet="dashboard"></ng-template>
60 </div>
61 </div> 65 </div>
66</div>
62 67
63 <div class="form-group"> 68<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
64 <label i18n for="videoQuota">Video quota</label>
65 <div class="peertube-select-container">
66 <select id="videoQuota" formControlName="videoQuota">
67 <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
68 {{ videoQuotaOption.label }}
69 </option>
70 </select>
71 </div>
72 69
73 <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()"> 70<div class="form-row mt-4"> <!-- user grid -->
74 Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br /> 71 <div class="form-group col-12 col-lg-4 col-xl-3">
75 At most, this user could use ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}. 72 <div class="anchor" id="user"></div> <!-- user anchor -->
73 <div *ngIf="isCreation()" class="account-title" i18n>NEW USER</div>
74 <div *ngIf="!isCreation() && user" class="account-title">
75 <my-actor-avatar-info [actor]="user.account"></my-actor-avatar-info>
76 </div> 76 </div>
77 </div> 77 </div>
78 78
79 <div class="form-group"> 79 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }">
80 <label i18n for="videoQuotaDaily">Daily video quota</label> 80
81 <div class="peertube-select-container"> 81 <form role="form" (ngSubmit)="formValidated()" [formGroup]="form" [ngClass]="{ 'col-5': isInBigView() }">
82 <select id="videoQuotaDaily" formControlName="videoQuotaDaily"> 82 <div class="form-group" *ngIf="isCreation()">
83 <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> 83 <label i18n for="username">Username</label>
84 {{ videoQuotaDailyOption.label }} 84 <input
85 </option> 85 type="text" id="username" i18n-placeholder placeholder="john"
86 </select> 86 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
87 >
88 <div *ngIf="formErrors.username" class="form-error">
89 {{ formErrors.username }}
90 </div>
91 </div>
92
93 <div class="form-group">
94 <label i18n for="email">Email</label>
95 <input
96 type="text" id="email" i18n-placeholder placeholder="mail@example.com"
97 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
98 autocomplete="off"
99 >
100 <div *ngIf="formErrors.email" class="form-error">
101 {{ formErrors.email }}
102 </div>
103 </div>
104
105 <div class="form-group" *ngIf="isCreation()">
106 <label i18n for="password">Password</label>
107 <my-help *ngIf="isPasswordOptional()">
108 <ng-template ptTemplate="customHtml">
109 <ng-container i18n>
110 If you leave the password empty, an email will be sent to the user.
111 </ng-container>
112 </ng-template>
113 </my-help>
114 <input
115 type="password" id="password" autocomplete="new-password"
116 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
117 >
118 <div *ngIf="formErrors.password" class="form-error">
119 {{ formErrors.password }}
120 </div>
121 </div>
122
123 <div class="form-group">
124 <label i18n for="role">Role</label>
125 <div class="peertube-select-container">
126 <select id="role" formControlName="role">
127 <option *ngFor="let role of roles" [value]="role.value">
128 {{ role.label }}
129 </option>
130 </select>
131 </div>
132
133 <div *ngIf="formErrors.role" class="form-error">
134 {{ formErrors.role }}
135 </div>
136 </div>
137
138 <div class="form-group">
139 <label i18n for="videoQuota">Video quota</label>
140 <div class="peertube-select-container">
141 <select id="videoQuota" formControlName="videoQuota">
142 <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
143 {{ videoQuotaOption.label }}
144 </option>
145 </select>
146 </div>
147
148 <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
149 Transcoding is enabled. The video quota only takes into account <strong>original</strong> video size. <br />
150 At most, this user could upload ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
151 </div>
152 </div>
153
154 <div class="form-group">
155 <label i18n for="videoQuotaDaily">Daily video quota</label>
156 <div class="peertube-select-container">
157 <select id="videoQuotaDaily" formControlName="videoQuotaDaily">
158 <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value">
159 {{ videoQuotaDailyOption.label }}
160 </option>
161 </select>
162 </div>
163 </div>
164
165 <div class="form-group">
166 <my-peertube-checkbox
167 inputName="byPassAutoBlacklist" formControlName="byPassAutoBlacklist"
168 i18n-labelText labelText="Doesn't need review before a video goes public"
169 ></my-peertube-checkbox>
170 </div>
171
172 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
173 </form>
174
175 <div *ngIf="isInBigView()" class="col-7">
176 <ng-template *ngTemplateOutlet="dashboard"></ng-template>
87 </div> 177 </div>
178
88 </div> 179 </div>
180</div>
181
89 182
90 <div class="form-group"> 183<div *ngIf="!isCreation() && user" class="form-row mt-4"> <!-- danger zone grid -->
91 <my-peertube-checkbox 184 <div class="form-group col-12 col-lg-4 col-xl-3">
92 inputName="byPassAutoBlacklist" formControlName="byPassAutoBlacklist" 185 <div class="anchor" id="danger"></div> <!-- danger zone anchor -->
93 i18n-labelText labelText="Bypass video auto blacklist" 186 <div i18n class="account-title">DANGER ZONE</div>
94 ></my-peertube-checkbox>
95 </div> 187 </div>
96 188
97 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> 189 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }">
98</form>
99 190
100<div *ngIf="!isCreation()" class="danger-zone"> 191 <div class="danger-zone">
101 <div class="account-title" i18n>Danger Zone</div> 192 <div class="form-group reset-password-email">
193 <label i18n>Send a link to reset the password by email to the user</label>
194 <button (click)="resetPassword()" i18n>Ask for new password</button>
195 </div>
102 196
103 <div class="form-group reset-password-email"> 197 <div class="form-group">
104 <label i18n>Send a link to reset the password by email to the user</label> 198 <label i18n>Manually set the user password</label>
105 <button (click)="resetPassword()" i18n>Ask for new password</button> 199 <my-user-password [userId]="user.id"></my-user-password>
106 </div> 200 </div>
201 </div>
107 202
108 <div class="form-group">
109 <label i18n>Manually set the user password</label>
110 <my-user-password [userId]="userId"></my-user-password>
111 </div> 203 </div>
112</div> 204</div>
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.scss b/client/src/app/+admin/users/user-edit/user-edit.component.scss
index c1cc4ca45..d4c1b600e 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.component.scss
+++ b/client/src/app/+admin/users/user-edit/user-edit.component.scss
@@ -1,8 +1,13 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.form-sub-title { 4label {
5 margin-bottom: 30px; 5 font-weight: $font-regular;
6 font-size: 100%;
7}
8
9.account-title {
10 @include settings-big-title;
6} 11}
7 12
8input:not([type=submit]) { 13input:not([type=submit]) {
@@ -26,18 +31,9 @@ input[type=submit], button {
26 font-size: 11px; 31 font-size: 11px;
27} 32}
28 33
29.account-title {
30 @include in-content-small-title;
31
32 margin-top: 55px;
33 margin-bottom: 30px;
34}
35
36.danger-zone { 34.danger-zone {
37 .reset-password-email { 35 .reset-password-email {
38 margin-bottom: 30px; 36 margin-bottom: 30px;
39 padding-bottom: 30px;
40 border-bottom: 1px solid rgba(0, 0, 0, 0.1);
41 37
42 button { 38 button {
43 display: block; 39 display: block;
@@ -45,3 +41,20 @@ input[type=submit], button {
45 } 41 }
46 } 42 }
47} 43}
44
45.breadcrumb {
46 @include breadcrumb;
47}
48
49.dashboard {
50 @include dashboard;
51 max-width: 900px;
52}
53
54my-actor-avatar-info ::ng-deep {
55 .actor-img-edit-container,
56 .actor-info-followers,
57 .actor-info-username {
58 display: none;
59 }
60}
diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts
index 47b57d2ec..a23cd9033 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.ts
+++ b/client/src/app/+admin/users/user-edit/user-edit.ts
@@ -4,12 +4,14 @@ import { ServerConfig, USER_ROLE_LABELS, UserRole, VideoResolution } from '../..
4import { ConfigService } from '@app/+admin/config/shared/config.service' 4import { ConfigService } from '@app/+admin/config/shared/config.service'
5import { UserAdminFlag } from '@shared/models/users/user-flag.model' 5import { UserAdminFlag } from '@shared/models/users/user-flag.model'
6import { OnInit } from '@angular/core' 6import { OnInit } from '@angular/core'
7import { User } from '@app/shared/users/user.model'
8import { ScreenService } from '@app/shared/misc/screen.service'
7 9
8export abstract class UserEdit extends FormReactive implements OnInit { 10export abstract class UserEdit extends FormReactive implements OnInit {
9 videoQuotaOptions: { value: string, label: string }[] = [] 11 videoQuotaOptions: { value: string, label: string }[] = []
10 videoQuotaDailyOptions: { value: string, label: string }[] = [] 12 videoQuotaDailyOptions: { value: string, label: string }[] = []
11 username: string 13 username: string
12 userId: number 14 user: User
13 15
14 roles: { value: string, label: string }[] = [] 16 roles: { value: string, label: string }[] = []
15 17
@@ -17,6 +19,7 @@ export abstract class UserEdit extends FormReactive implements OnInit {
17 19
18 protected abstract serverService: ServerService 20 protected abstract serverService: ServerService
19 protected abstract configService: ConfigService 21 protected abstract configService: ConfigService
22 protected abstract screenService: ScreenService
20 protected abstract auth: AuthService 23 protected abstract auth: AuthService
21 abstract isCreation (): boolean 24 abstract isCreation (): boolean
22 abstract getFormButtonTitle (): string 25 abstract getFormButtonTitle (): string
@@ -29,6 +32,20 @@ export abstract class UserEdit extends FormReactive implements OnInit {
29 this.buildRoles() 32 this.buildRoles()
30 } 33 }
31 34
35 get subscribersCount () {
36 const forAccount = this.user
37 ? this.user.account.followersCount
38 : 0
39 const forChannels = this.user
40 ? this.user.videoChannels.map(c => c.followersCount).reduce((a, b) => a + b, 0)
41 : 0
42 return forAccount + forChannels
43 }
44
45 isInBigView () {
46 return this.screenService.getWindowInnerWidth() > 1600
47 }
48
32 buildRoles () { 49 buildRoles () {
33 const authUser = this.auth.getUser() 50 const authUser = this.auth.getUser()
34 51
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.ts b/client/src/app/+admin/users/user-edit/user-password.component.ts
index 5b3040440..ecad000f7 100644
--- a/client/src/app/+admin/users/user-edit/user-password.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-password.component.ts
@@ -23,8 +23,6 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
23 constructor ( 23 constructor (
24 protected formValidatorService: FormValidatorService, 24 protected formValidatorService: FormValidatorService,
25 private userValidatorsService: UserValidatorsService, 25 private userValidatorsService: UserValidatorsService,
26 private route: ActivatedRoute,
27 private router: Router,
28 private notifier: Notifier, 26 private notifier: Notifier,
29 private userService: UserService, 27 private userService: UserService,
30 private i18n: I18n 28 private i18n: I18n
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts
index 1ab2e9dbf..fbe3d6950 100644
--- a/client/src/app/+admin/users/user-edit/user-update.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-update.component.ts
@@ -4,13 +4,15 @@ import { Subscription } from 'rxjs'
4import { AuthService, Notifier } from '@app/core' 4import { AuthService, Notifier } from '@app/core'
5import { ServerService } from '../../../core' 5import { ServerService } from '../../../core'
6import { UserEdit } from './user-edit' 6import { UserEdit } from './user-edit'
7import { User, UserUpdate } from '../../../../../../shared' 7import { User as UserType, UserUpdate, UserRole } from '../../../../../../shared'
8import { I18n } from '@ngx-translate/i18n-polyfill' 8import { I18n } from '@ngx-translate/i18n-polyfill'
9import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 9import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
10import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' 10import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
11import { ConfigService } from '@app/+admin/config/shared/config.service' 11import { ConfigService } from '@app/+admin/config/shared/config.service'
12import { UserService } from '@app/shared' 12import { UserService } from '@app/shared'
13import { UserAdminFlag } from '@shared/models/users/user-flag.model' 13import { UserAdminFlag } from '@shared/models/users/user-flag.model'
14import { User } from '@app/shared/users/user.model'
15import { ScreenService } from '@app/shared/misc/screen.service'
14 16
15@Component({ 17@Component({
16 selector: 'my-user-update', 18 selector: 'my-user-update',
@@ -19,9 +21,6 @@ import { UserAdminFlag } from '@shared/models/users/user-flag.model'
19}) 21})
20export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { 22export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
21 error: string 23 error: string
22 userId: number
23 userEmail: string
24 username: string
25 24
26 private paramsSub: Subscription 25 private paramsSub: Subscription
27 26
@@ -29,6 +28,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
29 protected formValidatorService: FormValidatorService, 28 protected formValidatorService: FormValidatorService,
30 protected serverService: ServerService, 29 protected serverService: ServerService,
31 protected configService: ConfigService, 30 protected configService: ConfigService,
31 protected screenService: ScreenService,
32 protected auth: AuthService, 32 protected auth: AuthService,
33 private userValidatorsService: UserValidatorsService, 33 private userValidatorsService: UserValidatorsService,
34 private route: ActivatedRoute, 34 private route: ActivatedRoute,
@@ -45,7 +45,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
45 ngOnInit () { 45 ngOnInit () {
46 super.ngOnInit() 46 super.ngOnInit()
47 47
48 const defaultValues = { videoQuota: '-1', videoQuotaDaily: '-1' } 48 const defaultValues = {
49 role: UserRole.USER.toString(),
50 videoQuota: '-1',
51 videoQuotaDaily: '-1'
52 }
53
49 this.buildForm({ 54 this.buildForm({
50 email: this.userValidatorsService.USER_EMAIL, 55 email: this.userValidatorsService.USER_EMAIL,
51 role: this.userValidatorsService.USER_ROLE, 56 role: this.userValidatorsService.USER_ROLE,
@@ -56,7 +61,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
56 61
57 this.paramsSub = this.route.params.subscribe(routeParams => { 62 this.paramsSub = this.route.params.subscribe(routeParams => {
58 const userId = routeParams['id'] 63 const userId = routeParams['id']
59 this.userService.getUser(userId).subscribe( 64 this.userService.getUser(userId, true).subscribe(
60 user => this.onUserFetched(user), 65 user => this.onUserFetched(user),
61 66
62 err => this.error = err.message 67 err => this.error = err.message
@@ -78,9 +83,9 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
78 userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10) 83 userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
79 userUpdate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10) 84 userUpdate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
80 85
81 this.userService.updateUser(this.userId, userUpdate).subscribe( 86 this.userService.updateUser(this.user.id, userUpdate).subscribe(
82 () => { 87 () => {
83 this.notifier.success(this.i18n('User {{username}} updated.', { username: this.username })) 88 this.notifier.success(this.i18n('User {{user.username}} updated.', { username: this.user.username }))
84 this.router.navigate([ '/admin/users/list' ]) 89 this.router.navigate([ '/admin/users/list' ])
85 }, 90 },
86 91
@@ -101,10 +106,10 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
101 } 106 }
102 107
103 resetPassword () { 108 resetPassword () {
104 this.userService.askResetPassword(this.userEmail).subscribe( 109 this.userService.askResetPassword(this.user.email).subscribe(
105 () => { 110 () => {
106 this.notifier.success( 111 this.notifier.success(
107 this.i18n('An email asking for password reset has been sent to {{username}}.', { username: this.username }) 112 this.i18n('An email asking for password reset has been sent to {{username}}.', { username: this.user.username })
108 ) 113 )
109 }, 114 },
110 115
@@ -112,14 +117,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
112 ) 117 )
113 } 118 }
114 119
115 private onUserFetched (userJson: User) { 120 private onUserFetched (userJson: UserType) {
116 this.userId = userJson.id 121 this.user = new User(userJson)
117 this.username = userJson.username
118 this.userEmail = userJson.email
119 122
120 this.form.patchValue({ 123 this.form.patchValue({
121 email: userJson.email, 124 email: userJson.email,
122 role: userJson.role, 125 role: userJson.role.toString(),
123 videoQuota: userJson.videoQuota, 126 videoQuota: userJson.videoQuota,
124 videoQuotaDaily: userJson.videoQuotaDaily, 127 videoQuotaDaily: userJson.videoQuotaDaily,
125 byPassAutoBlacklist: userJson.adminFlags & UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST 128 byPassAutoBlacklist: userJson.adminFlags & UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index db8ffac16..f8c04cb4d 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -15,7 +15,6 @@ import { MyAccountProfileComponent } from '@app/+my-account/my-account-settings/
15import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component' 15import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
16import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' 16import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
17import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' 17import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
18import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
19import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' 18import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
20import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' 19import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone'
21import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component' 20import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
@@ -63,7 +62,6 @@ import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-setti
63 MyAccountVideoChannelsComponent, 62 MyAccountVideoChannelsComponent,
64 MyAccountVideoChannelCreateComponent, 63 MyAccountVideoChannelCreateComponent,
65 MyAccountVideoChannelUpdateComponent, 64 MyAccountVideoChannelUpdateComponent,
66 ActorAvatarInfoComponent,
67 MyAccountVideoImportsComponent, 65 MyAccountVideoImportsComponent,
68 MyAccountDangerZoneComponent, 66 MyAccountDangerZoneComponent,
69 MyAccountSubscriptionsComponent, 67 MyAccountSubscriptionsComponent,
diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.html b/client/src/app/+my-account/shared/actor-avatar-info.component.html
index 2050950be..82f5123de 100644
--- a/client/src/app/+my-account/shared/actor-avatar-info.component.html
+++ b/client/src/app/+my-account/shared/actor-avatar-info.component.html
@@ -1,14 +1,17 @@
1<ng-container *ngIf="actor"> 1<ng-container *ngIf="actor">
2 <div class="actor"> 2 <div class="actor">
3 <img [src]="actor.avatarUrl" alt="Avatar" /> 3 <div class="d-flex">
4 <img [src]="actor.avatarUrl" alt="Avatar" />
4 5
5 <div class="actor-img-edit-container"> 6 <div class="actor-img-edit-container">
6 <div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body"> 7 <div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body">
7 <my-global-icon iconName="edit"></my-global-icon> 8 <my-global-icon iconName="edit"></my-global-icon>
8 <input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/> 9 <input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/>
10 </div>
9 </div> 11 </div>
10 </div> 12 </div>
11 13
14
12 <div class="actor-info"> 15 <div class="actor-info">
13 <div class="actor-info-names"> 16 <div class="actor-info-names">
14 <div class="actor-info-display-name">{{ actor.displayName }}</div> 17 <div class="actor-info-display-name">{{ actor.displayName }}</div>
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 75aa30dab..b89f0a8d1 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -106,6 +106,7 @@ import { InputSwitchModule } from 'primeng/inputswitch'
106 106
107import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' 107import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
108import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' 108import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
109import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
109 110
110@NgModule({ 111@NgModule({
111 imports: [ 112 imports: [
@@ -189,7 +190,8 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account
189 PreviewUploadComponent, 190 PreviewUploadComponent,
190 191
191 MyAccountVideoSettingsComponent, 192 MyAccountVideoSettingsComponent,
192 MyAccountInterfaceSettingsComponent 193 MyAccountInterfaceSettingsComponent,
194 ActorAvatarInfoComponent
193 ], 195 ],
194 196
195 exports: [ 197 exports: [
@@ -270,7 +272,8 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account
270 VideoDurationPipe, 272 VideoDurationPipe,
271 273
272 MyAccountVideoSettingsComponent, 274 MyAccountVideoSettingsComponent,
273 MyAccountInterfaceSettingsComponent 275 MyAccountInterfaceSettingsComponent,
276 ActorAvatarInfoComponent
274 ], 277 ],
275 278
276 providers: [ 279 providers: [
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index a37cae749..76c57d2fb 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -51,6 +51,11 @@ export class User implements UserServerModel {
51 videoQuotaDaily: number 51 videoQuotaDaily: number
52 videoQuotaUsed?: number 52 videoQuotaUsed?: number
53 videoQuotaUsedDaily?: number 53 videoQuotaUsedDaily?: number
54 videosCount?: number
55 videoAbusesCount?: number
56 videoAbusesAcceptedCount?: number
57 videoAbusesCreatedCount?: number
58 videoCommentsCount?: number
54 59
55 theme: string 60 theme: string
56 61
@@ -79,6 +84,11 @@ export class User implements UserServerModel {
79 this.videoQuotaDaily = hash.videoQuotaDaily 84 this.videoQuotaDaily = hash.videoQuotaDaily
80 this.videoQuotaUsed = hash.videoQuotaUsed 85 this.videoQuotaUsed = hash.videoQuotaUsed
81 this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily 86 this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily
87 this.videosCount = hash.videosCount
88 this.videoAbusesCount = hash.videoAbusesCount
89 this.videoAbusesAcceptedCount = hash.videoAbusesAcceptedCount
90 this.videoAbusesCreatedCount = hash.videoAbusesCreatedCount
91 this.videoCommentsCount = hash.videoCommentsCount
82 92
83 this.nsfwPolicy = hash.nsfwPolicy 93 this.nsfwPolicy = hash.nsfwPolicy
84 this.webTorrentEnabled = hash.webTorrentEnabled 94 this.webTorrentEnabled = hash.webTorrentEnabled
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index a79343646..5442a8453 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -234,8 +234,9 @@ export class UserService {
234 return this.userCache[userId] 234 return this.userCache[userId]
235 } 235 }
236 236
237 getUser (userId: number) { 237 getUser (userId: number, withStats = false) {
238 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId) 238 const params = new HttpParams().append('withStats', withStats + '')
239 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params })
239 .pipe(catchError(err => this.restExtractor.handleError(err))) 240 .pipe(catchError(err => this.restExtractor.handleError(err)))
240 } 241 }
241 242
diff --git a/client/src/app/shared/video/modals/video-report.component.html b/client/src/app/shared/video/modals/video-report.component.html
index b9434da26..cc1d361b3 100644
--- a/client/src/app/shared/video/modals/video-report.component.html
+++ b/client/src/app/shared/video/modals/video-report.component.html
@@ -7,8 +7,7 @@
7 <div class="modal-body"> 7 <div class="modal-body">
8 8
9 <div i18n class="information"> 9 <div i18n class="information">
10 Your report will be sent to moderators of {{ currentHost }}. 10 Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemoteVideo()"> and will be forwarded to the video origin ({{ originHost }}) too</ng-container>.
11 <ng-container *ngIf="isRemoteVideo()"> It will be forwarded to origin instance {{ originHost }} too.</ng-container>
12 </div> 11 </div>
13 12
14 <form novalidate [formGroup]="form" (ngSubmit)="report()"> 13 <form novalidate [formGroup]="form" (ngSubmit)="report()">
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index e8dfb79bc..f96a43b34 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -621,3 +621,85 @@
621 } 621 }
622 } 622 }
623} 623}
624
625@mixin breadcrumb {
626 display: flex;
627 flex-wrap: wrap;
628 padding: 0.75rem 1rem;
629 margin-bottom: 1rem;
630 list-style: none;
631 background-color: var(--submenuColor);
632 border-radius: 0.25rem;
633
634 .breadcrumb-item {
635 display: flex;
636
637 a {
638 color: var(--mainColor);
639 }
640
641 & + .breadcrumb-item {
642 padding-left: 0.5rem;
643 &::before {
644 display: inline-block;
645 padding-right: 0.5rem;
646 color: #6c757d;
647 content: "/";
648 }
649 }
650
651 &.active {
652 color: #6c757d;
653 }
654 }
655}
656
657@mixin dashboard {
658 display: flex;
659 flex-wrap: wrap;
660 margin: 0 -5px;
661
662 & > div {
663 box-sizing: border-box;
664 flex: 0 0 percentage(1/3);
665 padding: 0 5px;
666 margin-bottom: 10px;
667
668 & > a {
669 text-decoration: none;
670 color: inherit;
671 display: block;
672 font-size: 18px;
673
674 &:active,
675 &:focus,
676 &:hover {
677 opacity: .8;
678 }
679 }
680
681 & > a,
682 & > div {
683 padding: 20px;
684 background: var(--submenuColor);
685 border-radius: 4px;
686 box-sizing: border-box;
687 height: 100%;
688 }
689 }
690
691 .dashboard-num, .dashboard-text {
692 text-align: center;
693 font-size: 130%;
694 line-height: 21px;
695 color: var(--mainForegroundColor);
696 line-height: 30px;
697 margin-bottom: 20px;
698 }
699
700 .dashboard-label {
701 font-size: 90%;
702 color: var(--inputPlaceholderColor);
703 text-align: center;
704 }
705}