aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+admin
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/+admin')
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html15
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html90
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts12
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts8
-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.scss17
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html6
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html30
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html29
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html52
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html10
-rw-r--r--client/src/app/+admin/follows/following-list/follow-modal.component.ts9
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.html8
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html2
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.html6
-rw-r--r--client/src/app/+admin/overview/comments/video-comment-list.component.html2
-rw-r--r--client/src/app/+admin/overview/comments/video-comment-list.component.ts9
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-edit.component.html234
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-edit.component.scss7
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-edit.ts4
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-password.component.html26
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-password.component.scss12
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.html16
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.scss12
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.ts92
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.html26
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.scss6
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.ts41
-rw-r--r--client/src/app/+admin/plugins/plugin-search/plugin-search.component.html6
-rw-r--r--client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss5
-rw-r--r--client/src/app/+admin/plugins/shared/plugin-card.component.scss3
-rw-r--r--client/src/app/+admin/system/debug/debug.component.scss10
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.html8
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.scss8
34 files changed, 434 insertions, 391 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html
index 0ab80e5a9..a17b13fdf 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-advanced-configuration.component.html
@@ -1,15 +1,15 @@
1<ng-container [formGroup]="form"> 1<ng-container [formGroup]="form">
2 2
3 <div class="form-row mt-5"> <!-- cache grid --> 3 <div class="row mt-5"> <!-- cache grid -->
4 <div class="form-group col-12 col-lg-4 col-xl-3"> 4
5 <div class="col-12 col-lg-4 col-xl-3">
5 <div i18n class="inner-form-title">CACHE</div> 6 <div i18n class="inner-form-title">CACHE</div>
6 <div i18n class="inner-form-description"> 7 <div i18n class="inner-form-description">
7 Some files are not federated, and fetched when necessary. Define their caching policies. 8 Some files are not federated, and fetched when necessary. Define their caching policies.
8 </div> 9 </div>
9 </div> 10 </div>
10 11
11 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 12 <div class="col-12 col-lg-8 col-xl-9">
12
13 <ng-container formGroupName="cache"> 13 <ng-container formGroupName="cache">
14 <div class="form-group" formGroupName="previews"> 14 <div class="form-group" formGroupName="previews">
15 <label i18n for="cachePreviewsSize">Number of previews to keep in cache</label> 15 <label i18n for="cachePreviewsSize">Number of previews to keep in cache</label>
@@ -57,8 +57,8 @@
57 </div> 57 </div>
58 </div> 58 </div>
59 59
60 <div class="form-row mt-4"> <!-- cache grid --> 60 <div class="row mt-4"> <!-- cache grid -->
61 <div class="form-group col-12 col-lg-4 col-xl-3"> 61 <div class="col-12 col-lg-4 col-xl-3">
62 <div class="anchor" id="customizations"></div> <!-- customizations anchor --> 62 <div class="anchor" id="customizations"></div> <!-- customizations anchor -->
63 <div i18n class="inner-form-title">CUSTOMIZATIONS</div> 63 <div i18n class="inner-form-title">CUSTOMIZATIONS</div>
64 <div i18n class="inner-form-description"> 64 <div i18n class="inner-form-description">
@@ -66,8 +66,7 @@
66 </div> 66 </div>
67 </div> 67 </div>
68 68
69 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 69 <div class="col-12 col-lg-8 col-xl-9">
70
71 <ng-container formGroupName="instance"> 70 <ng-container formGroupName="instance">
72 <ng-container formGroupName="customizations"> 71 <ng-container formGroupName="customizations">
73 <div class="form-group"> 72 <div class="form-group">
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html
index bae9d9775..68126281f 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html
@@ -1,13 +1,13 @@
1<ng-container [formGroup]="form"> 1<ng-container [formGroup]="form">
2 <div class="form-row mt-5"> <!-- appearance grid --> 2 <div class="row mt-5"> <!-- appearance grid -->
3 <div class="form-group col-12 col-lg-4 col-xl-3"> 3 <div class="col-12 col-lg-4 col-xl-3">
4 <div i18n class="inner-form-title">APPEARANCE</div> 4 <div i18n class="inner-form-title">APPEARANCE</div>
5 <div i18n class="inner-form-description"> 5 <div i18n class="inner-form-description">
6 Use <a routerLink="/admin/plugins">plugins & themes</a> for more involved changes, or <a routerLink="/admin/config/edit-custom" fragment="advanced-configuration">add slight customizations</a>. 6 Use <a class="link-orange" routerLink="/admin/plugins">plugins & themes</a> for more involved changes, or add slight <a class="link-orange" routerLink="/admin/config/edit-custom" fragment="advanced-configuration">customizations</a>.
7 </div> 7 </div>
8 </div> 8 </div>
9 9
10 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 10 <div class="col-12 col-lg-8 col-xl-9">
11 11
12 <ng-container formGroupName="theme"> 12 <ng-container formGroupName="theme">
13 <div class="form-group"> 13 <div class="form-group">
@@ -15,7 +15,7 @@
15 15
16 <div class="peertube-select-container"> 16 <div class="peertube-select-container">
17 <select formControlName="default" id="themeDefault" class="form-control"> 17 <select formControlName="default" id="themeDefault" class="form-control">
18 <option i18n value="default">default</option> 18 <option i18n value="default">{{ getDefaultThemeLabel() }}</option>
19 19
20 <option *ngFor="let theme of getAvailableThemes()" [value]="theme">{{ theme }}</option> 20 <option *ngFor="let theme of getAvailableThemes()" [value]="theme">{{ theme }}</option>
21 </select> 21 </select>
@@ -88,15 +88,15 @@
88 </div> 88 </div>
89 </div> 89 </div>
90 90
91 <div class="form-row mt-4"> <!-- broadcast grid --> 91 <div class="row mt-4"> <!-- broadcast grid -->
92 <div class="form-group col-12 col-lg-4 col-xl-3"> 92 <div class="col-12 col-lg-4 col-xl-3">
93 <div i18n class="inner-form-title">BROADCAST MESSAGE</div> 93 <div i18n class="inner-form-title">BROADCAST MESSAGE</div>
94 <div i18n class="inner-for-description"> 94 <div i18n class="inner-form-description">
95 Display a message on your instance 95 Display a message on your instance
96 </div> 96 </div>
97 </div> 97 </div>
98 98
99 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 99 <div class="col-12 col-lg-8 col-xl-9">
100 100
101 <ng-container formGroupName="broadcastMessage"> 101 <ng-container formGroupName="broadcastMessage">
102 102
@@ -144,15 +144,15 @@
144 </div> 144 </div>
145 </div> 145 </div>
146 146
147 <div class="form-row mt-4"> <!-- new users grid --> 147 <div class="row mt-4"> <!-- new users grid -->
148 <div class="form-group col-12 col-lg-4 col-xl-3"> 148 <div class="col-12 col-lg-4 col-xl-3">
149 <div i18n class="inner-form-title">NEW USERS</div> 149 <div i18n class="inner-form-title">NEW USERS</div>
150 <div i18n class="inner-for-description"> 150 <div i18n class="inner-form-description">
151 Manage <a routerLink="/admin/users">users</a> to set their quota individually. 151 Manage <a class="link-orange" routerLink="/admin/users">users</a> to set their quota individually.
152 </div> 152 </div>
153 </div> 153 </div>
154 154
155 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 155 <div class="col-12 col-lg-8 col-xl-9">
156 156
157 <ng-container formGroupName="signup"> 157 <ng-container formGroupName="signup">
158 <div class="form-group"> 158 <div class="form-group">
@@ -163,7 +163,7 @@
163 <ng-container ngProjectAs="description"> 163 <ng-container ngProjectAs="description">
164 <span i18n>⚠️ This functionality requires a lot of attention and extra moderation.</span> 164 <span i18n>⚠️ This functionality requires a lot of attention and extra moderation.</span>
165 165
166 <div class="alert alert-info alert-signup" *ngIf="signupAlertMessage">{{ signupAlertMessage }}</div> 166 <div class="alert pt-alert-primary alert-signup" *ngIf="signupAlertMessage">{{ signupAlertMessage }}</div>
167 </ng-container> 167 </ng-container>
168 168
169 <ng-container ngProjectAs="extra"> 169 <ng-container ngProjectAs="extra">
@@ -185,7 +185,7 @@
185 185
186 <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div> 186 <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div>
187 187
188 <small i18n *ngIf="hasUnlimitedSignup()" class="muted">Signup won't be limited to a fixed number of users.</small> 188 <small i18n *ngIf="hasUnlimitedSignup()" class="muted small">Signup won't be limited to a fixed number of users.</small>
189 </div> 189 </div>
190 190
191 <div [ngClass]="getDisabledSignupClass()" class="mt-3"> 191 <div [ngClass]="getDisabledSignupClass()" class="mt-3">
@@ -239,20 +239,20 @@
239 </div> 239 </div>
240 </div> 240 </div>
241 241
242 <div class="form-row mt-4"> <!-- videos grid --> 242 <div class="row mt-4"> <!-- videos grid -->
243 <div class="form-group col-12 col-lg-4 col-xl-3"> 243 <div class="col-12 col-lg-4 col-xl-3">
244 <div i18n class="inner-form-title">VIDEOS</div> 244 <div i18n class="inner-form-title">VIDEOS</div>
245 </div> 245 </div>
246 246
247 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 247 <div class="col-12 col-lg-8 col-xl-9">
248 248
249 <ng-container formGroupName="import"> 249 <ng-container formGroupName="import">
250 250
251 <ng-container formGroupName="videos"> 251 <ng-container formGroupName="videos">
252 252
253 <div class="form-group mt-4"> 253 <div class="form-group">
254 <label i18n for="importConcurrency">Import jobs concurrency</label> 254 <label i18n for="importConcurrency">Import jobs concurrency</label>
255 <span i18n class="muted ml-1">allows to import multiple videos in parallel. ⚠️ Requires a PeerTube restart.</span> 255 <span i18n class="small muted ms-1">allows to import multiple videos in parallel. ⚠️ Requires a PeerTube restart.</span>
256 256
257 <div class="number-with-unit"> 257 <div class="number-with-unit">
258 <input type="number" name="importConcurrency" formControlName="concurrency" /> 258 <input type="number" name="importConcurrency" formControlName="concurrency" />
@@ -268,7 +268,7 @@
268 i18n-labelText labelText="Allow import with HTTP URL (e.g. YouTube)" 268 i18n-labelText labelText="Allow import with HTTP URL (e.g. YouTube)"
269 > 269 >
270 <ng-container ngProjectAs="description"> 270 <ng-container ngProjectAs="description">
271 <span i18n>⚠️ If enabled, we recommend to use <a href="https://docs.joinpeertube.org/maintain-configuration?id=security">a HTTP proxy</a> to prevent private URL access from your PeerTube server</span> 271 <span i18n>⚠️ If enabled, we recommend to use <a class="link-orange" href="https://docs.joinpeertube.org/maintain-configuration?id=security">a HTTP proxy</a> to prevent private URL access from your PeerTube server</span>
272 </ng-container> 272 </ng-container>
273 </my-peertube-checkbox> 273 </my-peertube-checkbox>
274 </div> 274 </div>
@@ -309,12 +309,12 @@
309 </div> 309 </div>
310 </div> 310 </div>
311 311
312 <div class="form-row mt-4"> <!-- video channels grid --> 312 <div class="row mt-4"> <!-- video channels grid -->
313 <div class="form-group col-12 col-lg-4 col-xl-3"> 313 <div class="col-12 col-lg-4 col-xl-3">
314 <div i18n class="inner-form-title">VIDEO CHANNELS</div> 314 <div i18n class="inner-form-title">VIDEO CHANNELS</div>
315 </div> 315 </div>
316 316
317 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 317 <div class="col-12 col-lg-8 col-xl-9">
318 <div class="form-group" formGroupName="videoChannels"> 318 <div class="form-group" formGroupName="videoChannels">
319 <label i18n for="videoChannelsMaxPerUser">Max video channels per user</label> 319 <label i18n for="videoChannelsMaxPerUser">Max video channels per user</label>
320 320
@@ -331,12 +331,12 @@
331 </div> 331 </div>
332 </div> 332 </div>
333 333
334 <div class="form-row mt-4"> <!-- search grid --> 334 <div class="row mt-4"> <!-- search grid -->
335 <div class="form-group col-12 col-lg-4 col-xl-3"> 335 <div class="col-12 col-lg-4 col-xl-3">
336 <div i18n class="inner-form-title">SEARCH</div> 336 <div i18n class="inner-form-title">SEARCH</div>
337 </div> 337 </div>
338 338
339 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 339 <div class="col-12 col-lg-8 col-xl-9">
340 340
341 <ng-container formGroupName="search"> 341 <ng-container formGroupName="search">
342 <ng-container formGroupName="remoteUri"> 342 <ng-container formGroupName="remoteUri">
@@ -372,11 +372,11 @@
372 i18n-labelText labelText="Enable global search" 372 i18n-labelText labelText="Enable global search"
373 > 373 >
374 <ng-container ngProjectAs="description"> 374 <ng-container ngProjectAs="description">
375 <p i18n>⚠️ This functionality depends heavily on the moderation of instances followed by the search index you select.</p> 375 <div i18n>⚠️ This functionality depends heavily on the moderation of instances followed by the search index you select.</div>
376 376
377 <span i18n> 377 <div i18n>
378 You should only use moderated search indexes in production, or <a href="https://framagit.org/framasoft/peertube/search-index">host your own</a>. 378 You should only use moderated search indexes in production, or <a class="link-orange" href="https://framagit.org/framasoft/peertube/search-index">host your own</a>.
379 </span> 379 </div>
380 </ng-container> 380 </ng-container>
381 381
382 <ng-container ngProjectAs="extra"> 382 <ng-container ngProjectAs="extra">
@@ -420,15 +420,15 @@
420 </div> 420 </div>
421 </div> 421 </div>
422 422
423 <div class="form-row mt-4"> <!-- federation grid --> 423 <div class="row mt-4"> <!-- federation grid -->
424 <div class="form-group col-12 col-lg-4 col-xl-3"> 424 <div class="col-12 col-lg-4 col-xl-3">
425 <div i18n class="inner-form-title">FEDERATION</div> 425 <div i18n class="inner-form-title">FEDERATION</div>
426 <div i18n class="inner-form-description"> 426 <div i18n class="inner-form-description">
427 Manage <a routerLink="/admin/follows">relations</a> with other instances. 427 Manage <a class="link-orange" routerLink="/admin/follows">relations</a> with other instances.
428 </div> 428 </div>
429 </div> 429 </div>
430 430
431 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 431 <div class="col-12 col-lg-8 col-xl-9">
432 432
433 <ng-container formGroupName="followers"> 433 <ng-container formGroupName="followers">
434 <ng-container formGroupName="instance"> 434 <ng-container formGroupName="instance">
@@ -472,10 +472,10 @@
472 i18n-labelText labelText="Automatically follow instances of a public index" 472 i18n-labelText labelText="Automatically follow instances of a public index"
473 > 473 >
474 <ng-container ngProjectAs="description"> 474 <ng-container ngProjectAs="description">
475 <p i18n>⚠️ This functionality requires a lot of attention and extra moderation.</p> 475 <div i18n>⚠️ This functionality requires a lot of attention and extra moderation.</div>
476 476
477 <span i18n> 477 <span i18n>
478 See <a href="https://docs.joinpeertube.org/admin-following-instances?id=automatically-follow-other-instances" rel="noopener noreferrer" target="_blank">the documentation</a> for more information about the expected URL 478 See <a class="link-orange" href="https://docs.joinpeertube.org/admin-following-instances?id=automatically-follow-other-instances" rel="noopener noreferrer" target="_blank">the documentation</a> for more information about the expected URL
479 </span> 479 </span>
480 </ng-container> 480 </ng-container>
481 481
@@ -499,12 +499,12 @@
499 </div> 499 </div>
500 </div> 500 </div>
501 501
502 <div class="form-row mt-4"> <!-- administrators grid --> 502 <div class="row mt-4"> <!-- administrators grid -->
503 <div class="form-group col-12 col-lg-4 col-xl-3"> 503 <div class="col-12 col-lg-4 col-xl-3">
504 <div i18n class="inner-form-title">ADMINISTRATORS</div> 504 <div i18n class="inner-form-title">ADMINISTRATORS</div>
505 </div> 505 </div>
506 506
507 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 507 <div class="col-12 col-lg-8 col-xl-9">
508 508
509 <div class="form-group" formGroupName="admin"> 509 <div class="form-group" formGroupName="admin">
510 <label i18n for="adminEmail">Admin email</label> 510 <label i18n for="adminEmail">Admin email</label>
@@ -527,8 +527,8 @@
527 </div> 527 </div>
528 </div> 528 </div>
529 529
530 <div class="form-row mt-4"> <!-- Twitter grid --> 530 <div class="row mt-4"> <!-- Twitter grid -->
531 <div class="form-group col-12 col-lg-4 col-xl-3"> 531 <div class="col-12 col-lg-4 col-xl-3">
532 <div i18n class="inner-form-title">TWITTER</div> 532 <div i18n class="inner-form-title">TWITTER</div>
533 <div i18n class="inner-form-description"> 533 <div i18n class="inner-form-description">
534 Provide the Twitter account representing your instance to improve link previews. 534 Provide the Twitter account representing your instance to improve link previews.
@@ -536,7 +536,7 @@
536 </div> 536 </div>
537 </div> 537 </div>
538 538
539 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 539 <div class="col-12 col-lg-8 col-xl-9">
540 540
541 <ng-container formGroupName="services"> 541 <ng-container formGroupName="services">
542 <ng-container formGroupName="twitter"> 542 <ng-container formGroupName="twitter">
@@ -563,7 +563,7 @@
563 If your instance is explicitly allowed by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> 563 If your instance is explicitly allowed by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
564 If the instance is not, we use an image link card that will redirect to your PeerTube instance.<br /><br /> 564 If the instance is not, we use an image link card that will redirect to your PeerTube instance.<br /><br />
565 Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/w/blabla) on 565 Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/w/blabla) on
566 <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> 566 <a class="link-orange" target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a>
567 to see if you instance is allowed. 567 to see if you instance is allowed.
568 </ng-container> 568 </ng-container>
569 </ng-template> 569 </ng-template>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts
index 81457bd36..f8f257671 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.ts
@@ -2,7 +2,7 @@ import { pairwise } from 'rxjs/operators'
2import { SelectOptionsItem } from 'src/types/select-options-item.model' 2import { SelectOptionsItem } from 'src/types/select-options-item.model'
3import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core' 3import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'
4import { FormGroup } from '@angular/forms' 4import { FormGroup } from '@angular/forms'
5import { MenuService } from '@app/core' 5import { MenuService, ThemeService } from '@app/core'
6import { HTMLServerConfig } from '@shared/models' 6import { HTMLServerConfig } from '@shared/models'
7import { ConfigService } from '../shared/config.service' 7import { ConfigService } from '../shared/config.service'
8 8
@@ -22,7 +22,8 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
22 22
23 constructor ( 23 constructor (
24 private configService: ConfigService, 24 private configService: ConfigService,
25 private menuService: MenuService 25 private menuService: MenuService,
26 private themeService: ThemeService
26 ) { } 27 ) { }
27 28
28 ngOnInit () { 29 ngOnInit () {
@@ -49,8 +50,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
49 } 50 }
50 51
51 getAvailableThemes () { 52 getAvailableThemes () {
52 return this.serverConfig.theme.registered 53 return this.themeService.getAvailableThemeLabels()
53 .map(t => t.name)
54 } 54 }
55 55
56 doesTrendingVideosAlgorithmsEnabledInclude (algorithm: string) { 56 doesTrendingVideosAlgorithmsEnabledInclude (algorithm: string) {
@@ -94,6 +94,10 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
94 })) 94 }))
95 } 95 }
96 96
97 getDefaultThemeLabel () {
98 return this.themeService.getDefaultThemeLabel()
99 }
100
97 private checkSignupField () { 101 private checkSignupField () {
98 const signupControl = this.form.get('signup.enabled') 102 const signupControl = this.form.get('signup.enabled')
99 103
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts b/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts
index 9b55cb43c..96f5b830e 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts
@@ -1,5 +1,6 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { FormGroup } from '@angular/forms' 2import { FormGroup } from '@angular/forms'
3import { prepareIcu } from '@app/helpers'
3 4
4export type ResolutionOption = { 5export type ResolutionOption = {
5 id: string 6 id: string
@@ -86,9 +87,10 @@ export class EditConfigurationService {
86 return { 87 return {
87 value, 88 value,
88 atMost: noneOnAuto, // auto switches everything to a least estimation since ffmpeg will take as many threads as possible 89 atMost: noneOnAuto, // auto switches everything to a least estimation since ffmpeg will take as many threads as possible
89 unit: value > 1 90 unit: prepareIcu($localize`{value, plural, =1 {thread} other {threads}}`)(
90 ? $localize`threads` 91 { value },
91 : $localize`thread` 92 $localize`threads`
93 )
92 } 94 }
93 } 95 }
94} 96}
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 8fef39b79..cc8e699f3 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
@@ -1,4 +1,4 @@
1<h1 class="sr-only" i18n>Configuration</h1> 1<h1 class="visually-hidden" i18n>Configuration</h1>
2 2
3<div class="alert alert-warning" *ngIf="!isUpdateAllowed()" i18n> 3<div class="alert alert-warning" *ngIf="!isUpdateAllowed()" i18n>
4 Updating instance configuration from the web interface is disabled by the system administrator. 4 Updating instance configuration from the web interface is disabled by the system administrator.
@@ -64,7 +64,7 @@
64 64
65 <div [ngbNavOutlet]="nav"></div> 65 <div [ngbNavOutlet]="nav"></div>
66 66
67 <div class="form-row mt-4"> <!-- submit placement block --> 67 <div class="row mt-4"> <!-- submit placement block -->
68 <div class="col-md-7 col-xl-5"></div> 68 <div class="col-md-7 col-xl-5"></div>
69 <div class="col-md-5 col-xl-5"> 69 <div class="col-md-5 col-xl-5">
70 70
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 0458d257f..f3bce8ee0 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
@@ -3,11 +3,6 @@
3 3
4$form-base-input-width: 340px; 4$form-base-input-width: 340px;
5 5
6label {
7 font-weight: $font-regular;
8 font-size: 100%;
9}
10
11form { 6form {
12 padding-bottom: 1.5rem; 7 padding-bottom: 1.5rem;
13} 8}
@@ -30,7 +25,7 @@ input[type=number] {
30 25
31 input[type=number] + span { 26 input[type=number] + span {
32 position: absolute; 27 position: absolute;
33 top: 5px; 28 top: 0.2em;
34 right: 2.5rem; 29 right: 2.5rem;
35 } 30 }
36 31
@@ -74,6 +69,10 @@ input[type=submit] {
74 @include settings-big-title; 69 @include settings-big-title;
75} 70}
76 71
72.inner-form-description {
73 font-size: 15px;
74}
75
77textarea { 76textarea {
78 @include peertube-textarea(500px, 150px); 77 @include peertube-textarea(500px, 150px);
79 78
@@ -88,6 +87,7 @@ textarea {
88.label-small-info { 87.label-small-info {
89 font-style: italic; 88 font-style: italic;
90 margin-bottom: 10px; 89 margin-bottom: 10px;
90 font-size: 14px;
91} 91}
92 92
93.disabled-checkbox-extra { 93.disabled-checkbox-extra {
@@ -102,11 +102,6 @@ input[disabled] {
102 opacity: 0.5; 102 opacity: 0.5;
103} 103}
104 104
105
106.form-group-right {
107 padding-top: 2px;
108}
109
110ngb-tabset:not(.previews) ::ng-deep { 105ngb-tabset:not(.previews) ::ng-deep {
111 .nav-link { 106 .nav-link {
112 font-size: 105%; 107 font-size: 105%;
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html b/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html
index 2286a5a1a..701c3c0c1 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-homepage.component.html
@@ -2,12 +2,12 @@
2 2
3 <ng-container formGroupName="instanceCustomHomepage"> 3 <ng-container formGroupName="instanceCustomHomepage">
4 4
5 <div class="form-row mt-5"> <!-- homepage grid --> 5 <div class="row mt-5"> <!-- homepage grid -->
6 <div class="form-group col-12 col-lg-4 col-xl-3"> 6 <div class="col-12 col-lg-4 col-xl-3">
7 <div i18n class="inner-form-title">INSTANCE HOMEPAGE</div> 7 <div i18n class="inner-form-title">INSTANCE HOMEPAGE</div>
8 </div> 8 </div>
9 9
10 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 10 <div class="col-12 col-lg-8 col-xl-9">
11 11
12 <div class="form-group"> 12 <div class="form-group">
13 <label i18n for="instanceCustomHomepageContent">Homepage</label> 13 <label i18n for="instanceCustomHomepageContent">Homepage</label>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html
index d806616bd..5931cb459 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html
@@ -2,13 +2,12 @@
2 2
3 <ng-container formGroupName="instance"> 3 <ng-container formGroupName="instance">
4 4
5 <div class="form-row mt-5"> <!-- instance grid --> 5 <div class="row mt-5"> <!-- instance grid -->
6 <div class="form-group col-12 col-lg-4 col-xl-3"> 6 <div class="col-12 col-lg-4 col-xl-3">
7 <div i18n class="inner-form-title">INSTANCE</div> 7 <div i18n class="inner-form-title">INSTANCE</div>
8 </div> 8 </div>
9 9
10 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 10 <div class="col-12 col-lg-8 col-xl-9">
11
12 <div class="form-group"> 11 <div class="form-group">
13 <label i18n for="instanceName">Name</label> 12 <label i18n for="instanceName">Name</label>
14 13
@@ -77,16 +76,15 @@
77 </div> 76 </div>
78 </div> 77 </div>
79 78
80 <div class="form-row mt-4"> <!-- moderation & nsfw grid --> 79 <div class="row mt-4"> <!-- moderation & nsfw grid -->
81 <div class="form-group col-12 col-lg-4 col-xl-3"> 80 <div class="col-12 col-lg-4 col-xl-3">
82 <div i18n class="inner-form-title">MODERATION & NSFW</div> 81 <div i18n class="inner-form-title">MODERATION & NSFW</div>
83 <div i18n class="inner-for-description"> 82 <div i18row="inner-form-description">
84 Manage <a routerLink="/admin/users">users</a> to build a moderation team. 83 Manage <a class="link-orange" routerLink="/admin/users">users</a> to build a moderation team.
85 </div> 84 </div>
86 </div> 85 </div>
87 86
88 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 87 <div class="col-12 col-lg-8 col-xl-9">
89
90 <div class="form-group"> 88 <div class="form-group">
91 <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW"> 89 <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW">
92 <ng-template ptTemplate="label"> 90 <ng-template ptTemplate="label">
@@ -162,12 +160,12 @@
162 </div> 160 </div>
163 </div> 161 </div>
164 162
165 <div class="form-row mt-4"> <!-- you and your instance grid --> 163 <div class="row mt-4"> <!-- you and your instance grid -->
166 <div class="form-group col-12 col-lg-4 col-xl-3"> 164 <div class="col-12 col-lg-4 col-xl-3">
167 <div i18n class="inner-form-title">YOU AND YOUR INSTANCE</div> 165 <div i18n class="inner-form-title">YOU AND YOUR INSTANCE</div>
168 </div> 166 </div>
169 167
170 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 168 <div class="col-12 col-lg-8 col-xl-9">
171 169
172 <div class="form-group"> 170 <div class="form-group">
173 <label i18n for="instanceAdministrator">Who is behind the instance?</label><my-help helpType="markdownText"></my-help> 171 <label i18n for="instanceAdministrator">Who is behind the instance?</label><my-help helpType="markdownText"></my-help>
@@ -220,12 +218,12 @@
220 </div> 218 </div>
221 </div> 219 </div>
222 220
223 <div class="form-row mt-4"> <!-- other information grid --> 221 <div class="row mt-4"> <!-- other information grid -->
224 <div class="form-group col-12 col-lg-4 col-xl-3"> 222 <div class="col-12 col-lg-4 col-xl-3">
225 <div i18n class="inner-form-title">OTHER INFORMATION</div> 223 <div i18n class="inner-form-title">OTHER INFORMATION</div>
226 </div> 224 </div>
227 225
228 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 226 <div class="col-12 col-lg-8 col-xl-9">
229 227
230 <div class="form-group"> 228 <div class="form-group">
231 <label i18n for="instanceHardwareInformation">What server/hardware does the instance run on?</label> 229 <label i18n for="instanceHardwareInformation">What server/hardware does the instance run on?</label>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html
index 71d5d91f0..ae79e54fc 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html
@@ -1,14 +1,14 @@
1<ng-container [formGroup]="form"> 1<ng-container [formGroup]="form">
2 2
3 <div class="form-row mt-5"> 3 <div class="row mt-5">
4 <div class="form-group col-12 col-lg-4 col-xl-3"> 4 <div class="col-12 col-lg-4 col-xl-3">
5 <div i18n class="inner-form-title">LIVE</div> 5 <div i18n class="inner-form-title">LIVE</div>
6 <div i18n class="inner-form-description"> 6 <div i18n class="inner-form-description">
7 Enable users of your instance to stream live. 7 Enable users of your instance to stream live.
8 </div> 8 </div>
9 </div> 9 </div>
10 10
11 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 11 <div class="col-12 col-lg-8 col-xl-9">
12 12
13 <ng-container formGroupName="live"> 13 <ng-container formGroupName="live">
14 14
@@ -46,9 +46,9 @@
46 </div> 46 </div>
47 47
48 <div class="form-group" [ngClass]="getDisabledLiveClass()"> 48 <div class="form-group" [ngClass]="getDisabledLiveClass()">
49 <label i18n for="liveMaxInstanceLives"> 49 <label i18n for="liveMaxInstanceLives">Max simultaneous lives created on your instance</label>
50 Max simultaneous lives created on your instance <span class="muted">(-1 for "unlimited")</span> 50
51 </label> 51 <span class="ms-2 small muted">(-1 for "unlimited")</span>
52 52
53 <div class="number-with-unit"> 53 <div class="number-with-unit">
54 <input type="number" name="liveMaxInstanceLives" formControlName="maxInstanceLives" /> 54 <input type="number" name="liveMaxInstanceLives" formControlName="maxInstanceLives" />
@@ -59,9 +59,8 @@
59 </div> 59 </div>
60 60
61 <div class="form-group" [ngClass]="getDisabledLiveClass()"> 61 <div class="form-group" [ngClass]="getDisabledLiveClass()">
62 <label i18n for="liveMaxUserLives"> 62 <label i18n for="liveMaxUserLives">Max simultaneous lives created per user</label>
63 Max simultaneous lives created per user <span class="muted">(-1 for "unlimited")</span> 63 <span class="ms-2 small muted">(-1 for "unlimited")</span>
64 </label>
65 64
66 <div class="number-with-unit"> 65 <div class="number-with-unit">
67 <input type="number" name="liveMaxUserLives" formControlName="maxUserLives" /> 66 <input type="number" name="liveMaxUserLives" formControlName="maxUserLives" />
@@ -89,15 +88,15 @@
89 </div> 88 </div>
90 </div> 89 </div>
91 90
92 <div class="form-row"> <!-- transcoding live streams grid --> 91 <div class="row"> <!-- transcoding live streams grid -->
93 <div class="form-group col-12 col-lg-4 col-xl-3"> 92 <div class="col-12 col-lg-4 col-xl-3">
94 <div i18n class="inner-form-title">TRANSCODING</div> 93 <div i18n class="inner-form-title">TRANSCODING</div>
95 <div i18n class="inner-form-description"> 94 <div i18n class="inner-form-description">
96 Same as VOD transcoding, transcoding live streams so that they are in a streamable form that any device can play. Requires a beefy CPU, and then some. 95 Same as VOD transcoding, transcoding live streams so that they are in a streamable form that any device can play. Requires a beefy CPU, and then some.
97 </div> 96 </div>
98 </div> 97 </div>
99 98
100 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 99 <div class="col-12 col-lg-8 col-xl-9">
101 100
102 <ng-container formGroupName="live"> 101 <ng-container formGroupName="live">
103 <ng-container formGroupName="transcoding"> 102 <ng-container formGroupName="transcoding">
@@ -115,7 +114,7 @@
115 <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()"> 114 <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()">
116 <label i18n for="liveTranscodingThreads">Live resolutions to generate</label> 115 <label i18n for="liveTranscodingThreads">Live resolutions to generate</label>
117 116
118 <div class="ml-2 mt-2 d-flex flex-column"> 117 <div class="ms-2 mt-2 d-flex flex-column">
119 <ng-container formGroupName="resolutions"> 118 <ng-container formGroupName="resolutions">
120 119
121 <div class="form-group" *ngFor="let resolution of liveResolutions"> 120 <div class="form-group" *ngFor="let resolution of liveResolutions">
@@ -136,7 +135,7 @@
136 <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()"> 135 <div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()">
137 <label i18n for="liveTranscodingThreads">Live transcoding threads</label> 136 <label i18n for="liveTranscodingThreads">Live transcoding threads</label>
138 137
139 <span class="muted ml-1"> 138 <span class="small muted ms-1">
140 <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n> 139 <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n>
141 will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with VOD transcoding 140 will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with VOD transcoding
142 </ng-container> 141 </ng-container>
@@ -157,7 +156,7 @@
157 156
158 <div class="form-group mt-4" [ngClass]="getDisabledLiveTranscodingClass()"> 157 <div class="form-group mt-4" [ngClass]="getDisabledLiveTranscodingClass()">
159 <label i18n for="liveTranscodingProfile">Live transcoding profile</label> 158 <label i18n for="liveTranscodingProfile">Live transcoding profile</label>
160 <span class="muted ml-1" i18n>new live transcoding profiles can be added by PeerTube plugins</span> 159 <span class="small muted ms-1" i18n>new live transcoding profiles can be added by PeerTube plugins</span>
161 160
162 <my-select-options 161 <my-select-options
163 id="liveTranscodingProfile" 162 id="liveTranscodingProfile"
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html
index 5c0bea4a5..66e421b16 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html
@@ -1,28 +1,23 @@
1<ng-container [formGroup]="form"> 1<ng-container [formGroup]="form">
2 2
3 <div class="form-row mt-4"> <!-- transcoding grid --> 3 <div class="row mt-4"> <!-- transcoding grid -->
4 <div class="form-group col-12 col-lg-4 col-xl-3"></div> 4 <div class="col-12 col-lg-4 col-xl-3"></div>
5 <div class="form-group form-group-right col-12 col-lg-8"> 5 <div class="col-12 col-lg-8">
6 6
7 <div class="callout callout-info"> 7 <div class="callout callout-orange">
8 <span i18n> 8 <span i18n>
9 Estimating a server's capacity to transcode and stream videos isn't easy and we can't tune PeerTube automatically. 9 Estimating a server's capacity to transcode and stream videos isn't easy and we can't tune PeerTube automatically.
10 </span> 10 </span>
11
11 <span i18n> 12 <span i18n>
12 However, you may want to read our guidelines before tweaking the following values. 13 However, you may want to read <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/admin-configuration?id=transcoding">our guidelines</a> before tweaking the following values.
13 </span> 14 </span>
14
15 <div class="callout-container">
16 <a class="callout-link" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/admin-configuration?id=transcoding" i18n>
17 Read guidelines
18 </a>
19 </div>
20 </div> 15 </div>
21 </div> 16 </div>
22 </div> 17 </div>
23 18
24 <div class="form-row mt-2"> <!-- transcoding grid --> 19 <div class="row mt-4"> <!-- transcoding grid -->
25 <div class="form-group col-12 col-lg-4 col-xl-3"> 20 <div class="col-12 col-lg-4 col-xl-3">
26 <div i18n class="inner-form-title">TRANSCODING</div> 21 <div i18n class="inner-form-title">TRANSCODING</div>
27 <div i18n class="inner-form-description"> 22 <div i18n class="inner-form-description">
28 Process uploaded videos so that they are in a streamable form that any device can play. Though costly in 23 Process uploaded videos so that they are in a streamable form that any device can play. Though costly in
@@ -30,11 +25,11 @@
30 </div> 25 </div>
31 </div> 26 </div>
32 27
33 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 28 <div class="col-12 col-lg-8 col-xl-9">
34 29
35 <ng-container formGroupName="transcoding"> 30 <ng-container formGroupName="transcoding">
36 31
37 <div class="form-group mb-0 col-12 col-xl-11"> 32 <div class="col-12 col-xl-11">
38 <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled" [recommended]="true"> 33 <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled" [recommended]="true">
39 <ng-template ptTemplate="label"> 34 <ng-template ptTemplate="label">
40 <ng-container i18n>Transcoding enabled</ng-container> 35 <ng-container i18n>Transcoding enabled</ng-container>
@@ -115,7 +110,11 @@
115 <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> 110 <div class="form-group" [ngClass]="getTranscodingDisabledClass()">
116 <label i18n>Resolutions to generate per enabled format</label> 111 <label i18n>Resolutions to generate per enabled format</label>
117 112
118 <div class="ml-2 mt-2 d-flex flex-column"> 113 <div class="ms-2 d-flex flex-column">
114 <span class="mb-3 small muted" i18n>
115 The original file resolution will be the default target if no option is selected.
116 </span>
117
119 <ng-container formGroupName="resolutions"> 118 <ng-container formGroupName="resolutions">
120 <div class="form-group" *ngFor="let resolution of resolutions"> 119 <div class="form-group" *ngFor="let resolution of resolutions">
121 <my-peertube-checkbox 120 <my-peertube-checkbox
@@ -127,10 +126,6 @@
127 </ng-template> 126 </ng-template>
128 </my-peertube-checkbox> 127 </my-peertube-checkbox>
129 </div> 128 </div>
130
131 <span class="mb-2 muted" i18n>
132 The original file resolution will be the default target if no option is selected.
133 </span>
134 </ng-container> 129 </ng-container>
135 </div> 130 </div>
136 </div> 131 </div>
@@ -142,7 +137,8 @@
142 137
143 <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()"> 138 <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()">
144 <label i18n for="transcodingThreads">Transcoding threads</label> 139 <label i18n for="transcodingThreads">Transcoding threads</label>
145 <span class="muted ml-1"> 140
141 <span class="small muted ms-1">
146 <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n> 142 <ng-container *ngIf="getTotalTranscodingThreads().atMost" i18n>
147 will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with live transcoding 143 will claim at most {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with live transcoding
148 </ng-container> 144 </ng-container>
@@ -162,9 +158,9 @@
162 <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div> 158 <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div>
163 </div> 159 </div>
164 160
165 <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()"> 161 <div class="form-group" [ngClass]="getTranscodingDisabledClass()">
166 <label i18n for="transcodingConcurrency">Transcoding jobs concurrency</label> 162 <label i18n for="transcodingConcurrency">Transcoding jobs concurrency</label>
167 <span class="muted ml-1" i18n>allows to transcode multiple files in parallel. ⚠️ Requires a PeerTube restart</span> 163 <span class="small muted ms-1" i18n>allows to transcode multiple files in parallel. ⚠️ Requires a PeerTube restart</span>
168 164
169 <div class="number-with-unit"> 165 <div class="number-with-unit">
170 <input type="number" name="transcodingConcurrency" formControlName="concurrency" /> 166 <input type="number" name="transcodingConcurrency" formControlName="concurrency" />
@@ -174,9 +170,9 @@
174 <div *ngIf="formErrors.transcoding.concurrency" class="form-error">{{ formErrors.transcoding.concurrency }}</div> 170 <div *ngIf="formErrors.transcoding.concurrency" class="form-error">{{ formErrors.transcoding.concurrency }}</div>
175 </div> 171 </div>
176 172
177 <div class="form-group mt-4" [ngClass]="getTranscodingDisabledClass()"> 173 <div class="form-group" [ngClass]="getTranscodingDisabledClass()">
178 <label i18n for="transcodingProfile">Transcoding profile</label> 174 <label i18n for="transcodingProfile">Transcoding profile</label>
179 <span class="muted ml-1" i18n>new transcoding profiles can be added by PeerTube plugins</span> 175 <span class="small muted ms-1" i18n>new transcoding profiles can be added by PeerTube plugins</span>
180 176
181 <my-select-options 177 <my-select-options
182 id="transcodingProfile" 178 id="transcodingProfile"
@@ -193,15 +189,15 @@
193 </div> 189 </div>
194 </div> 190 </div>
195 191
196 <div class="form-row mt-2"> <!-- video studio grid --> 192 <div class="row mt-2"> <!-- video studio grid -->
197 <div class="form-group col-12 col-lg-4 col-xl-3"> 193 <div class="col-12 col-lg-4 col-xl-3">
198 <div i18n class="inner-form-title">VIDEO STUDIO</div> 194 <div i18n class="inner-form-title">VIDEO STUDIO</div>
199 <div i18n class="inner-form-description"> 195 <div i18n class="inner-form-description">
200 Allows your users to edit their video (cut, add intro/outro, add a watermark etc) 196 Allows your users to edit their video (cut, add intro/outro, add a watermark etc)
201 </div> 197 </div>
202 </div> 198 </div>
203 199
204 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 200 <div class="col-12 col-lg-8 col-xl-9">
205 201
206 <ng-container formGroupName="videoStudio"> 202 <ng-container formGroupName="videoStudio">
207 <div class="form-group" [ngClass]="getTranscodingDisabledClass()"> 203 <div class="form-group" [ngClass]="getTranscodingDisabledClass()">
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index 1df7bb164..5367bf517 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -12,7 +12,7 @@
12> 12>
13 <ng-template pTemplate="caption"> 13 <ng-template pTemplate="caption">
14 <div class="caption"> 14 <div class="caption">
15 <div class="ml-auto"> 15 <div class="ms-auto">
16 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> 16 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
17 </div> 17 </div>
18 </div> 18 </div>
@@ -41,15 +41,15 @@
41 <td> 41 <td>
42 <a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer"> 42 <a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer">
43 {{ follow.follower.name + '@' + follow.follower.host }} 43 {{ follow.follower.name + '@' + follow.follower.host }}
44 <span class="glyphicon glyphicon-new-window"></span> 44 <my-global-icon iconName="external-link"></my-global-icon>
45 </a> 45 </a>
46 </td> 46 </td>
47 47
48 <td *ngIf="follow.state === 'accepted'"> 48 <td *ngIf="follow.state === 'accepted'">
49 <span class="badge badge-green" i18n>Accepted</span> 49 <span class="pt-badge badge-green" i18n>Accepted</span>
50 </td> 50 </td>
51 <td *ngIf="follow.state === 'pending'"> 51 <td *ngIf="follow.state === 'pending'">
52 <span class="badge badge-yellow" i18n>Pending</span> 52 <span class="pt-badge badge-yellow" i18n>Pending</span>
53 </td> 53 </td>
54 54
55 <td>{{ follow.score }}</td> 55 <td>{{ follow.score }}</td>
@@ -59,7 +59,7 @@
59 59
60 <ng-template pTemplate="emptymessage"> 60 <ng-template pTemplate="emptymessage">
61 <tr> 61 <tr>
62 <td colspan="6"> 62 <td colspan="5">
63 <div class="no-results"> 63 <div class="no-results">
64 <ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container> 64 <ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container>
65 <ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container> 65 <ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container>
diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.ts b/client/src/app/+admin/follows/following-list/follow-modal.component.ts
index c40b36e10..07cc75d77 100644
--- a/client/src/app/+admin/follows/following-list/follow-modal.component.ts
+++ b/client/src/app/+admin/follows/following-list/follow-modal.component.ts
@@ -1,5 +1,6 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { prepareIcu } from '@app/helpers'
3import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' 4import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 5import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
5import { InstanceFollowService } from '@app/shared/shared-instance' 6import { InstanceFollowService } from '@app/shared/shared-instance'
@@ -60,7 +61,13 @@ export class FollowModalComponent extends FormReactive implements OnInit {
60 this.followService.follow(hostsOrHandles) 61 this.followService.follow(hostsOrHandles)
61 .subscribe({ 62 .subscribe({
62 next: () => { 63 next: () => {
63 this.notifier.success($localize`Follow request(s) sent!`) 64 this.notifier.success(
65 prepareIcu($localize`{count, plural, =1 {Follow request sent!} other {Follow requests sent!}}`)(
66 { count: hostsOrHandles.length },
67 $localize`Follow request(s) sent!`
68 )
69 )
70
64 this.newFollow.emit() 71 this.newFollow.emit()
65 }, 72 },
66 73
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index 767e92d18..106e1805e 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -19,7 +19,7 @@
19 </a> 19 </a>
20 </div> 20 </div>
21 21
22 <div class="ml-auto"> 22 <div class="ms-auto">
23 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> 23 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
24 </div> 24 </div>
25 </div> 25 </div>
@@ -43,15 +43,15 @@
43 <td> 43 <td>
44 <a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> 44 <a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer">
45 {{ follow.following.name + '@' + follow.following.host }} 45 {{ follow.following.name + '@' + follow.following.host }}
46 <span class="glyphicon glyphicon-new-window"></span> 46 <my-global-icon iconName="external-link"></my-global-icon>
47 </a> 47 </a>
48 </td> 48 </td>
49 49
50 <td *ngIf="follow.state === 'accepted'"> 50 <td *ngIf="follow.state === 'accepted'">
51 <span class="badge badge-green" i18n>Accepted</span> 51 <span class="pt-badge badge-green" i18n>Accepted</span>
52 </td> 52 </td>
53 <td *ngIf="follow.state === 'pending'"> 53 <td *ngIf="follow.state === 'pending'">
54 <span class="badge badge-yellow" i18n>Pending</span> 54 <span class="pt-badge badge-yellow" i18n>Pending</span>
55 </td> 55 </td>
56 56
57 <td>{{ follow.createdAt | date: 'short' }}</td> 57 <td>{{ follow.createdAt | date: 'short' }}</td>
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
index f13a0c378..12b07da11 100644
--- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
@@ -46,7 +46,7 @@
46 <td> 46 <td>
47 <a [href]="redundancy.url" i18n-title title="Open video in a new tab" target="_blank" rel="noopener noreferrer"> 47 <a [href]="redundancy.url" i18n-title title="Open video in a new tab" target="_blank" rel="noopener noreferrer">
48 {{ redundancy.name }} 48 {{ redundancy.name }}
49 <span class="glyphicon glyphicon-new-window"></span> 49 <my-global-icon iconName="external-link"></my-global-icon>
50 </a> 50 </a>
51 </td> 51 </td>
52 52
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
index 3634951c9..b302014b6 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
@@ -13,7 +13,7 @@
13> 13>
14 <ng-template pTemplate="caption"> 14 <ng-template pTemplate="caption">
15 <div class="caption"> 15 <div class="caption">
16 <div class="ml-auto"> 16 <div class="ms-auto">
17 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> 17 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
18 </div> 18 </div>
19 </div> 19 </div>
@@ -53,11 +53,11 @@
53 </td> 53 </td>
54 54
55 <td> 55 <td>
56 <span *ngIf="videoBlock.video.nsfw" class="badge badge-red" i18n>NSFW</span> 56 <span *ngIf="videoBlock.video.nsfw" class="pt-badge badge-red" i18n>NSFW</span>
57 </td> 57 </td>
58 58
59 <td> 59 <td>
60 <span *ngIf="videoBlock.unfederated" class="badge badge-blue" i18n>Unfederated</span> 60 <span *ngIf="videoBlock.unfederated" class="pt-badge badge-blue" i18n>Unfederated</span>
61 </td> 61 </td>
62 62
63 <td> 63 <td>
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.html b/client/src/app/+admin/overview/comments/video-comment-list.component.html
index 27a5d82ff..6fdefbfe2 100644
--- a/client/src/app/+admin/overview/comments/video-comment-list.component.html
+++ b/client/src/app/+admin/overview/comments/video-comment-list.component.html
@@ -25,7 +25,7 @@
25 </my-action-dropdown> 25 </my-action-dropdown>
26 </div> 26 </div>
27 27
28 <div class="ml-auto right-form"> 28 <div class="ms-auto right-form">
29 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> 29 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
30 30
31 <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button> 31 <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button>
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.ts b/client/src/app/+admin/overview/comments/video-comment-list.component.ts
index f3f43a900..f01a1629b 100644
--- a/client/src/app/+admin/overview/comments/video-comment-list.component.ts
+++ b/client/src/app/+admin/overview/comments/video-comment-list.component.ts
@@ -7,6 +7,7 @@ import { DropdownAction } from '@app/shared/shared-main'
7import { BulkService } from '@app/shared/shared-moderation' 7import { BulkService } from '@app/shared/shared-moderation'
8import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' 8import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
9import { FeedFormat, UserRight } from '@shared/models' 9import { FeedFormat, UserRight } from '@shared/models'
10import { prepareIcu } from '@app/helpers'
10 11
11@Component({ 12@Component({
12 selector: 'my-video-comment-list', 13 selector: 'my-video-comment-list',
@@ -145,7 +146,13 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
145 this.videoCommentService.deleteVideoComments(commentArgs) 146 this.videoCommentService.deleteVideoComments(commentArgs)
146 .subscribe({ 147 .subscribe({
147 next: () => { 148 next: () => {
148 this.notifier.success($localize`${commentArgs.length} comments deleted.`) 149 this.notifier.success(
150 prepareIcu($localize`{count, plural, =1 {1 comment deleted.} other {{count} comments deleted.}}`)(
151 { count: commentArgs.length },
152 $localize`${commentArgs.length} comment(s) deleted.`
153 )
154 )
155
149 this.reloadData() 156 this.reloadData()
150 }, 157 },
151 158
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html
index 772ebf272..bfa414164 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html
+++ b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html
@@ -57,7 +57,7 @@
57 </div> 57 </div>
58</ng-template> 58</ng-template>
59 59
60<div class="form-row" *ngIf="!isInBigView()"> <!-- hidden on large screens, as it is then displayed on the right side of the form --> 60<div class="row d-xxl-none"> <!-- hidden on large screens, as it is then displayed on the right side of the form -->
61 <div class="col-12 col-xl-3"></div> 61 <div class="col-12 col-xl-3"></div>
62 62
63 <div class="col-12 col-xl-9"> 63 <div class="col-12 col-xl-9">
@@ -67,8 +67,8 @@
67 67
68<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 68<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
69 69
70<div class="form-row mt-4"> <!-- user grid --> 70<div class="row mt-4"> <!-- user grid -->
71 <div class="form-group col-12 col-lg-4 col-xl-3"> 71 <div class="col-12 col-lg-4 col-xl-3">
72 <div class="anchor" id="user"></div> <!-- user anchor --> 72 <div class="anchor" id="user"></div> <!-- user anchor -->
73 <div *ngIf="isCreation()" class="account-title" i18n>NEW USER</div> 73 <div *ngIf="isCreation()" class="account-title" i18n>NEW USER</div>
74 <div *ngIf="!isCreation() && user" class="account-title"> 74 <div *ngIf="!isCreation() && user" class="account-title">
@@ -76,150 +76,150 @@
76 </div> 76 </div>
77 </div> 77 </div>
78 78
79 <div class="form-group col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }"> 79 <div class="col-12 col-lg-8 col-xl-9">
80 80 <div class="row">
81 <form role="form" (ngSubmit)="formValidated()" [formGroup]="form" [ngClass]="{ 'col-5': isInBigView() }"> 81 <form class="col" role="form" (ngSubmit)="formValidated()" [formGroup]="form">
82 <div class="form-group" *ngIf="isCreation()"> 82 <div class="form-group" *ngIf="isCreation()">
83 <label i18n for="username">Username</label> 83 <label i18n for="username">Username</label>
84 <input 84 <input
85 type="text" id="username" i18n-placeholder placeholder="john" class="form-control" 85 type="text" id="username" i18n-placeholder placeholder="john" class="form-control"
86 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }" 86 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
87 > 87 >
88 <div *ngIf="formErrors.username" class="form-error"> 88 <div *ngIf="formErrors.username" class="form-error">
89 {{ formErrors.username }} 89 {{ formErrors.username }}
90 </div>
90 </div> 91 </div>
91 </div>
92 92
93 <div class="form-group" *ngIf="isCreation()"> 93 <div class="form-group" *ngIf="isCreation()">
94 <label i18n for="channelName">Channel name</label> 94 <label i18n for="channelName">Channel name</label>
95 <input 95 <input
96 type="text" id="channelName" i18n-placeholder placeholder="john_channel" class="form-control" 96 type="text" id="channelName" i18n-placeholder placeholder="john_channel" class="form-control"
97 formControlName="channelName" [ngClass]="{ 'input-error': formErrors['channelName'] }" 97 formControlName="channelName" [ngClass]="{ 'input-error': formErrors['channelName'] }"
98 > 98 >
99 <div *ngIf="formErrors.channelName" class="form-error"> 99 <div *ngIf="formErrors.channelName" class="form-error">
100 {{ formErrors.channelName }} 100 {{ formErrors.channelName }}
101 </div>
101 </div> 102 </div>
102 </div>
103 103
104 <div class="form-group"> 104 <div class="form-group">
105 <label i18n for="email">Email</label> 105 <label i18n for="email">Email</label>
106 <input 106 <input
107 type="text" id="email" i18n-placeholder placeholder="mail@example.com" class="form-control" 107 type="text" id="email" i18n-placeholder placeholder="mail@example.com" class="form-control"
108 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }" 108 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
109 autocomplete="off" [readonly]="user && user.pluginAuth !== null" 109 autocomplete="off" [readonly]="user && user.pluginAuth !== null"
110 > 110 >
111 <div *ngIf="formErrors.email" class="form-error"> 111 <div *ngIf="formErrors.email" class="form-error">
112 {{ formErrors.email }} 112 {{ formErrors.email }}
113 </div>
113 </div> 114 </div>
114 </div>
115 115
116 <div class="form-group" *ngIf="isCreation()"> 116 <div class="form-group" *ngIf="isCreation()">
117 <label i18n for="password">Password</label> 117 <label i18n for="password">Password</label>
118 <my-help *ngIf="isPasswordOptional()"> 118 <my-help *ngIf="isPasswordOptional()">
119 <ng-template ptTemplate="customHtml"> 119 <ng-template ptTemplate="customHtml">
120 <ng-container i18n> 120 <ng-container i18n>
121 If you leave the password empty, an email will be sent to the user. 121 If you leave the password empty, an email will be sent to the user.
122 </ng-container> 122 </ng-container>
123 </ng-template> 123 </ng-template>
124 </my-help> 124 </my-help>
125 125
126 <my-input-toggle-hidden 126 <my-input-text
127 formControlName="password" inputId="password" [ngClass]="{ 'input-error': formErrors['password'] }" autocomplete="new-password" 127 formControlName="password" inputId="password" [ngClass]="{ 'input-error': formErrors['password'] }" autocomplete="new-password"
128 ></my-input-toggle-hidden> 128 ></my-input-text>
129 129
130 <div *ngIf="formErrors.password" class="form-error"> 130 <div *ngIf="formErrors.password" class="form-error">
131 {{ formErrors.password }} 131 {{ formErrors.password }}
132 </div>
132 </div> 133 </div>
133 </div>
134 134
135 <div class="form-group"> 135 <div class="form-group">
136 <label i18n for="role">Role</label> 136 <label i18n for="role">Role</label>
137 <div class="peertube-select-container"> 137 <div class="peertube-select-container">
138 <select id="role" formControlName="role" class="form-control"> 138 <select id="role" formControlName="role" class="form-control">
139 <option *ngFor="let role of roles" [value]="role.value"> 139 <option *ngFor="let role of roles" [value]="role.value">
140 {{ role.label }} 140 {{ role.label }}
141 </option> 141 </option>
142 </select> 142 </select>
143 </div>
144
145 <div *ngIf="formErrors.role" class="form-error">
146 {{ formErrors.role }}
147 </div>
143 </div> 148 </div>
144 149
145 <div *ngIf="formErrors.role" class="form-error"> 150 <div class="form-group">
146 {{ formErrors.role }} 151 <label i18n for="videoQuota">Video quota</label>
152
153 <my-select-custom-value
154 id="videoQuota"
155 [items]="videoQuotaOptions"
156 formControlName="videoQuota"
157 i18n-inputSuffix inputSuffix="bytes" inputType="number"
158 [clearable]="false"
159 ></my-select-custom-value>
160
161 <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
162 Transcoding is enabled. The video quota only takes into account <strong>original</strong> video size. <br />
163 At most, this user could upload ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
164 </div>
165
166 <div *ngIf="formErrors.videoQuota" class="form-error">
167 {{ formErrors.videoQuota }}
168 </div>
147 </div> 169 </div>
148 </div>
149 170
150 <div class="form-group"> 171 <div class="form-group">
151 <label i18n for="videoQuota">Video quota</label> 172 <label i18n for="videoQuotaDaily">Daily video quota</label>
152
153 <my-select-custom-value
154 id="videoQuota"
155 [items]="videoQuotaOptions"
156 formControlName="videoQuota"
157 i18n-inputSuffix inputSuffix="bytes" inputType="number"
158 [clearable]="false"
159 ></my-select-custom-value>
160
161 <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
162 Transcoding is enabled. The video quota only takes into account <strong>original</strong> video size. <br />
163 At most, this user could upload ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
164 </div>
165 173
166 <div *ngIf="formErrors.videoQuota" class="form-error"> 174 <my-select-custom-value
167 {{ formErrors.videoQuota }} 175 id="videoQuotaDaily"
168 </div> 176 [items]="videoQuotaDailyOptions"
169 </div> 177 formControlName="videoQuotaDaily"
178 i18n-inputSuffix inputSuffix="bytes" inputType="number"
179 [clearable]="false"
180 ></my-select-custom-value>
170 181
171 <div class="form-group"> 182 <div *ngIf="formErrors.videoQuotaDaily" class="form-error">
172 <label i18n for="videoQuotaDaily">Daily video quota</label> 183 {{ formErrors.videoQuotaDaily }}
173 184 </div>
174 <my-select-custom-value
175 id="videoQuotaDaily"
176 [items]="videoQuotaDailyOptions"
177 formControlName="videoQuotaDaily"
178 i18n-inputSuffix inputSuffix="bytes" inputType="number"
179 [clearable]="false"
180 ></my-select-custom-value>
181
182 <div *ngIf="formErrors.videoQuotaDaily" class="form-error">
183 {{ formErrors.videoQuotaDaily }}
184 </div> 185 </div>
185 </div>
186 186
187 <div class="form-group" *ngIf="!isCreation() && getAuthPlugins().length !== 0"> 187 <div class="form-group" *ngIf="!isCreation() && getAuthPlugins().length !== 0">
188 <label i18n for="pluginAuth">Auth plugin</label> 188 <label i18n for="pluginAuth">Auth plugin</label>
189 189
190 <div class="peertube-select-container"> 190 <div class="peertube-select-container">
191 <select id="pluginAuth" formControlName="pluginAuth" class="form-control"> 191 <select id="pluginAuth" formControlName="pluginAuth" class="form-control">
192 <option [value]="null" i18n>None (local authentication)</option> 192 <option [value]="null" i18n>None (local authentication)</option>
193 <option *ngFor="let authPlugin of getAuthPlugins()" [value]="authPlugin">{{ authPlugin }}</option> 193 <option *ngFor="let authPlugin of getAuthPlugins()" [value]="authPlugin">{{ authPlugin }}</option>
194 </select> 194 </select>
195 </div>
195 </div> 196 </div>
196 </div>
197 197
198 <div class="form-group"> 198 <div class="form-group">
199 <my-peertube-checkbox 199 <my-peertube-checkbox
200 inputName="byPassAutoBlock" formControlName="byPassAutoBlock" 200 inputName="byPassAutoBlock" formControlName="byPassAutoBlock"
201 i18n-labelText labelText="Doesn't need review before a video goes public" 201 i18n-labelText labelText="Doesn't need review before a video goes public"
202 ></my-peertube-checkbox> 202 ></my-peertube-checkbox>
203 </div> 203 </div>
204 204
205 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> 205 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
206 </form> 206 </form>
207 207
208 <div *ngIf="isInBigView()" class="col-7"> 208 <div class="d-none d-xxl-block col-7">
209 <ng-template *ngTemplateOutlet="dashboard"></ng-template> 209 <ng-template *ngTemplateOutlet="dashboard"></ng-template>
210 </div>
210 </div> 211 </div>
211
212 </div> 212 </div>
213</div> 213</div>
214 214
215 215
216<div *ngIf="!isCreation() && user && user.pluginAuth === null" class="form-row mt-4"> <!-- danger zone grid --> 216<div *ngIf="!isCreation() && user && user.pluginAuth === null" class="row mt-4"> <!-- danger zone grid -->
217 <div class="form-group col-12 col-lg-4 col-xl-3"> 217 <div class="col-12 col-lg-4 col-xl-3">
218 <div class="anchor" id="danger"></div> <!-- danger zone anchor --> 218 <div class="anchor" id="danger"></div> <!-- danger zone anchor -->
219 <div i18n class="account-title account-title-danger">DANGER ZONE</div> 219 <div i18n class="account-title account-title-danger">DANGER ZONE</div>
220 </div> 220 </div>
221 221
222 <div class="form-group col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }"> 222 <div class="col-12 col-lg-8 col-xl-9">
223 223
224 <div class="danger-zone"> 224 <div class="danger-zone">
225 <div class="form-group reset-password-email"> 225 <div class="form-group reset-password-email">
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss b/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss
index d7932154b..254286ae3 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss
+++ b/client/src/app/+admin/overview/users/user-edit/user-edit.component.scss
@@ -4,11 +4,6 @@
4 4
5$form-base-input-width: 340px; 5$form-base-input-width: 340px;
6 6
7label {
8 font-weight: $font-regular;
9 font-size: 100%;
10}
11
12.account-title { 7.account-title {
13 @include settings-big-title; 8 @include settings-big-title;
14 9
@@ -22,7 +17,7 @@ input:not([type=submit]) {
22 display: block; 17 display: block;
23} 18}
24 19
25my-input-toggle-hidden { 20my-input-text {
26 @include responsive-width($form-base-input-width); 21 @include responsive-width($form-base-input-width);
27 22
28 display: block; 23 display: block;
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.ts b/client/src/app/+admin/overview/users/user-edit/user-edit.ts
index 069b62a53..395d07423 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-edit.ts
+++ b/client/src/app/+admin/overview/users/user-edit/user-edit.ts
@@ -46,10 +46,6 @@ export abstract class UserEdit extends FormReactive implements OnInit {
46 .concat(this.serverConfig.plugin.registeredExternalAuths.map(p => p.npmName)) 46 .concat(this.serverConfig.plugin.registeredExternalAuths.map(p => p.npmName))
47 } 47 }
48 48
49 isInBigView () {
50 return this.screenService.getWindowInnerWidth() > 1600
51 }
52
53 buildRoles () { 49 buildRoles () {
54 const authUser = this.auth.getUser() 50 const authUser = this.auth.getUser()
55 51
diff --git a/client/src/app/+admin/overview/users/user-edit/user-password.component.html b/client/src/app/+admin/overview/users/user-edit/user-password.component.html
index 1238d1839..35f36e465 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-password.component.html
+++ b/client/src/app/+admin/overview/users/user-edit/user-password.component.html
@@ -1,20 +1,16 @@
1<form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> 1<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
2 <div class="form-group"> 2 <div class="input-group">
3 <input id="password" [attr.type]="showPassword ? 'text' : 'password'" class="form-control"
4 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
5 >
6 <button class="btn btn-sm btn-outline-secondary" (click)="togglePasswordVisibility()" type="button">
7 <ng-container *ngIf="!showPassword" i18n>Show</ng-container>
8 <ng-container *ngIf="!!showPassword" i18n>Hide</ng-container>
9 </button>
10 </div>
3 11
4 <div class="input-group"> 12 <div *ngIf="formErrors.password" class="form-error">
5 <input id="password" [attr.type]="showPassword ? 'text' : 'password'" class="form-control" 13 {{ formErrors.password }}
6 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
7 >
8 <div class="input-group-append">
9 <button class="btn btn-sm btn-outline-secondary" (click)="togglePasswordVisibility()" type="button">
10 <ng-container *ngIf="!showPassword" i18n>Show</ng-container>
11 <ng-container *ngIf="!!showPassword" i18n>Hide</ng-container>
12 </button>
13 </div>
14 </div>
15 <div *ngIf="formErrors.password" class="form-error">
16 {{ formErrors.password }}
17 </div>
18 </div> 14 </div>
19 15
20 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> 16 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
diff --git a/client/src/app/+admin/overview/users/user-edit/user-password.component.scss b/client/src/app/+admin/overview/users/user-edit/user-password.component.scss
index acb680682..54f782086 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-password.component.scss
+++ b/client/src/app/+admin/overview/users/user-edit/user-password.component.scss
@@ -1,13 +1,9 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4input:not([type=submit]):not([type=checkbox]) { 4input[type=text],
5input[type=password] {
5 @include peertube-input-text(340px); 6 @include peertube-input-text(340px);
6
7 display: block;
8 border-top-right-radius: 0;
9 border-bottom-right-radius: 0;
10 border-right: 0;
11} 7}
12 8
13input[type=submit] { 9input[type=submit] {
@@ -17,7 +13,3 @@ input[type=submit] {
17 13
18 margin-top: 10px; 14 margin-top: 10px;
19} 15}
20
21.input-group-append {
22 height: 30px;
23}
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html
index 30d10e3cf..4da46dc7b 100644
--- a/client/src/app/+admin/overview/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html
@@ -5,7 +5,7 @@
5 5
6<p-table 6<p-table
7 [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 7 [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
8 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers" 8 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers"
9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" 9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
@@ -26,7 +26,7 @@
26 </a> 26 </a>
27 </div> 27 </div>
28 28
29 <div class="ml-auto"> 29 <div class="ms-auto">
30 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> 30 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
31 </div> 31 </div>
32 32
@@ -90,7 +90,7 @@
90 </my-user-moderation-dropdown> 90 </my-user-moderation-dropdown>
91 </td> 91 </td>
92 92
93 <td *ngIf="isSelected('username')"> 93 <td *ngIf="isSelected('username')" class="cell-username">
94 <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/a/' + user.username ]"> 94 <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/a/' + user.username ]">
95 <div class="chip two-lines"> 95 <div class="chip two-lines">
96 <my-actor-avatar [account]="user?.account" size="32"></my-actor-avatar> 96 <my-actor-avatar [account]="user?.account" size="32"></my-actor-avatar>
@@ -101,13 +101,13 @@
101 </div> 101 </div>
102 </a> 102 </a>
103 103
104 <div *ngIf="user.accountMutedStatus.mutedByInstance" class="badges-username badge badge-red" i18n>Muted</div> 104 <div *ngIf="user.accountMutedStatus.mutedByInstance" class="pt-badge badge-red" i18n>Muted</div>
105 <div *ngIf="user.blocked" class="badges-username badge badge-red" i18n>Banned</div> 105 <div *ngIf="user.blocked" class="pt-badge badge-red" i18n>Banned</div>
106 </td> 106 </td>
107 107
108 <td *ngIf="isSelected('role')"> 108 <td *ngIf="isSelected('role')">
109 <span *ngIf="user.blocked" class="badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span> 109 <span *ngIf="user.blocked" class="pt-badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span>
110 <span *ngIf="!user.blocked" class="badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span> 110 <span *ngIf="!user.blocked" class="pt-badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span>
111 </td> 111 </td>
112 112
113 <td *ngIf="isSelected('email')" [title]="user.email"> 113 <td *ngIf="isSelected('email')" [title]="user.email">
@@ -139,7 +139,7 @@
139 139
140 <td *ngIf="isSelected('quotaDaily')"> 140 <td *ngIf="isSelected('quotaDaily')">
141 <div class="progress" i18n-title title="Total daily video quota"> 141 <div class="progress" i18n-title title="Total daily video quota">
142 <div class="progress-bar secondary" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }" 142 <div class="progress-bar" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }"
143 [attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily"> 143 [attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily">
144 </div> 144 </div>
145 <span>{{ user.videoQuotaUsedDaily }}</span> 145 <span>{{ user.videoQuotaUsedDaily }}</span>
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.scss b/client/src/app/+admin/overview/users/user-list/user-list.component.scss
index 8160703f0..3c775cac5 100644
--- a/client/src/app/+admin/overview/users/user-list/user-list.component.scss
+++ b/client/src/app/+admin/overview/users/user-list/user-list.component.scss
@@ -1,5 +1,6 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3@use '~bootstrap/scss/functions' as *;
3 4
4.add-button { 5.add-button {
5 @include create-button; 6 @include create-button;
@@ -23,15 +24,8 @@ tr.banned > td {
23 font-weight: $font-semibold; 24 font-weight: $font-semibold;
24} 25}
25 26
26.badges-username { 27.cell-username .pt-badge {
27 margin-left: 15px; 28 @include margin-left(15px);
28}
29
30.user-table-primary-text .glyphicon {
31 @include margin-left(0.1rem);
32
33 font-size: 80%;
34 color: #808080;
35} 29}
36 30
37p-tableCheckbox { 31p-tableCheckbox {
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
index d22e1355e..91f42546b 100644
--- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
@@ -1,8 +1,8 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 4import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
5import { getAPIHost } from '@app/helpers' 5import { prepareIcu, getAPIHost } from '@app/helpers'
6import { AdvancedInputFilter } from '@app/shared/shared-forms' 6import { AdvancedInputFilter } from '@app/shared/shared-forms'
7import { Actor, DropdownAction } from '@app/shared/shared-main' 7import { Actor, DropdownAction } from '@app/shared/shared-main'
8import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' 8import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation'
@@ -22,6 +22,8 @@ type UserForList = User & {
22 styleUrls: [ './user-list.component.scss' ] 22 styleUrls: [ './user-list.component.scss' ]
23}) 23})
24export class UserListComponent extends RestTable implements OnInit { 24export class UserListComponent extends RestTable implements OnInit {
25 private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns'
26
25 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent 27 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
26 28
27 users: (User & { accountMutedStatus: AccountMutedStatus })[] = [] 29 users: (User & { accountMutedStatus: AccountMutedStatus })[] = []
@@ -56,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit {
56 58
57 requiresEmailVerification = false 59 requiresEmailVerification = false
58 60
59 private _selectedColumns: string[] 61 private _selectedColumns: string[] = []
60 62
61 constructor ( 63 constructor (
62 protected route: ActivatedRoute, 64 protected route: ActivatedRoute,
@@ -66,7 +68,8 @@ export class UserListComponent extends RestTable implements OnInit {
66 private serverService: ServerService, 68 private serverService: ServerService,
67 private auth: AuthService, 69 private auth: AuthService,
68 private blocklist: BlocklistService, 70 private blocklist: BlocklistService,
69 private userAdminService: UserAdminService 71 private userAdminService: UserAdminService,
72 private peertubeLocalStorage: LocalStorageService
70 ) { 73 ) {
71 super() 74 super()
72 } 75 }
@@ -76,11 +79,13 @@ export class UserListComponent extends RestTable implements OnInit {
76 } 79 }
77 80
78 get selectedColumns () { 81 get selectedColumns () {
79 return this._selectedColumns 82 return this._selectedColumns || []
80 } 83 }
81 84
82 set selectedColumns (val: string[]) { 85 set selectedColumns (val: string[]) {
83 this._selectedColumns = val 86 this._selectedColumns = val
87
88 this.saveSelectedColumns()
84 } 89 }
85 90
86 ngOnInit () { 91 ngOnInit () {
@@ -126,14 +131,35 @@ export class UserListComponent extends RestTable implements OnInit {
126 { id: 'role', label: $localize`Role` }, 131 { id: 'role', label: $localize`Role` },
127 { id: 'email', label: $localize`Email` }, 132 { id: 'email', label: $localize`Email` },
128 { id: 'quota', label: $localize`Video quota` }, 133 { id: 'quota', label: $localize`Video quota` },
129 { id: 'createdAt', label: $localize`Created` } 134 { id: 'createdAt', label: $localize`Created` },
135 { id: 'lastLoginDate', label: $localize`Last login` },
136
137 { id: 'quotaDaily', label: $localize`Daily quota` },
138 { id: 'pluginAuth', label: $localize`Auth plugin` }
130 ] 139 ]
131 140
132 this.selectedColumns = this.columns.map(c => c.id) 141 this.loadSelectedColumns()
142 }
143
144 loadSelectedColumns () {
145 const result = this.peertubeLocalStorage.getItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY)
133 146
134 this.columns.push({ id: 'quotaDaily', label: $localize`Daily quota` }) 147 if (result) {
135 this.columns.push({ id: 'pluginAuth', label: $localize`Auth plugin` }) 148 try {
136 this.columns.push({ id: 'lastLoginDate', label: $localize`Last login` }) 149 this.selectedColumns = JSON.parse(result)
150 return
151 } catch (err) {
152 console.error('Cannot load selected columns.', err)
153 }
154 }
155
156 // Default behaviour
157 this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'createdAt', 'lastLoginDate' ]
158 return
159 }
160
161 saveSelectedColumns () {
162 this.peertubeLocalStorage.setItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns))
137 } 163 }
138 164
139 getIdentifier () { 165 getIdentifier () {
@@ -164,6 +190,7 @@ export class UserListComponent extends RestTable implements OnInit {
164 } 190 }
165 191
166 getUserVideoQuotaDailyPercentage (user: UserForList) { 192 getUserVideoQuotaDailyPercentage (user: UserForList) {
193 console.log(user.rawVideoQuotaUsedDaily * 100 / user.rawVideoQuotaDaily)
167 return user.rawVideoQuotaUsedDaily * 100 / user.rawVideoQuotaDaily 194 return user.rawVideoQuotaUsedDaily * 100 / user.rawVideoQuotaDaily
168 } 195 }
169 196
@@ -183,13 +210,25 @@ export class UserListComponent extends RestTable implements OnInit {
183 } 210 }
184 211
185 async unbanUsers (users: User[]) { 212 async unbanUsers (users: User[]) {
186 const res = await this.confirmService.confirm($localize`Do you really want to unban ${users.length} users?`, $localize`Unban`) 213 const res = await this.confirmService.confirm(
214 prepareIcu($localize`Do you really want to unban {count, plural, =1 {1 user} other {{count} users}}?`)(
215 { count: users.length },
216 $localize`Do you really want to unban ${users.length} users?`
217 ),
218 $localize`Unban`
219 )
220
187 if (res === false) return 221 if (res === false) return
188 222
189 this.userAdminService.unbanUsers(users) 223 this.userAdminService.unbanUsers(users)
190 .subscribe({ 224 .subscribe({
191 next: () => { 225 next: () => {
192 this.notifier.success($localize`${users.length} users unbanned.`) 226 this.notifier.success(
227 prepareIcu($localize`{count, plural, =1 {1 user unbanned.} other {{count} users unbanned.}}`)(
228 { count: users.length },
229 $localize`${users.length} users unbanned.`
230 )
231 )
193 this.reloadData() 232 this.reloadData()
194 }, 233 },
195 234
@@ -198,21 +237,28 @@ export class UserListComponent extends RestTable implements OnInit {
198 } 237 }
199 238
200 async removeUsers (users: User[]) { 239 async removeUsers (users: User[]) {
201 for (const user of users) { 240 if (users.some(u => u.username === 'root')) {
202 if (user.username === 'root') { 241 this.notifier.error($localize`You cannot delete root.`)
203 this.notifier.error($localize`You cannot delete root.`) 242 return
204 return
205 }
206 } 243 }
207 244
208 const message = $localize`If you remove these users, you will not be able to create others with the same username!` 245 const message = $localize`<p>You can't create users or channels with a username that already used by a deleted user/channel.</p>` +
246 $localize`It means the following usernames will be permanently deleted and cannot be recovered:` +
247 '<ul>' + users.map(u => '<li>' + u.username + '</li>').join('') + '</ul>'
248
209 const res = await this.confirmService.confirm(message, $localize`Delete`) 249 const res = await this.confirmService.confirm(message, $localize`Delete`)
210 if (res === false) return 250 if (res === false) return
211 251
212 this.userAdminService.removeUser(users) 252 this.userAdminService.removeUser(users)
213 .subscribe({ 253 .subscribe({
214 next: () => { 254 next: () => {
215 this.notifier.success($localize`${users.length} users deleted.`) 255 this.notifier.success(
256 prepareIcu($localize`{count, plural, =1 {1 user deleted.} other {{count} users deleted.}}`)(
257 { count: users.length },
258 $localize`${users.length} users deleted.`
259 )
260 )
261
216 this.reloadData() 262 this.reloadData()
217 }, 263 },
218 264
@@ -224,7 +270,13 @@ export class UserListComponent extends RestTable implements OnInit {
224 this.userAdminService.updateUsers(users, { emailVerified: true }) 270 this.userAdminService.updateUsers(users, { emailVerified: true })
225 .subscribe({ 271 .subscribe({
226 next: () => { 272 next: () => {
227 this.notifier.success($localize`${users.length} users email set as verified.`) 273 this.notifier.success(
274 prepareIcu($localize`{count, plural, =1 {1 user email set as verified.} other {{count} user emails set as verified.}}`)(
275 { count: users.length },
276 $localize`${users.length} users email set as verified.`
277 )
278 )
279
228 this.reloadData() 280 this.reloadData()
229 }, 281 },
230 282
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html
index 75d9be5f1..2f36c27b7 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.html
+++ b/client/src/app/+admin/overview/videos/video-list.component.html
@@ -21,7 +21,7 @@
21 </my-action-dropdown> 21 </my-action-dropdown>
22 </div> 22 </div>
23 23
24 <div class="ml-auto right-form"> 24 <div class="ms-auto right-form">
25 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter> 25 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
26 26
27 <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button> 27 <my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button>
@@ -67,25 +67,25 @@
67 </td> 67 </td>
68 68
69 <td> 69 <td>
70 <span class="badge badge-blue" *ngIf="video.isLocal">Local</span> 70 <span class="pt-badge badge-blue" *ngIf="video.isLocal">Local</span>
71 <span class="badge badge-purple" *ngIf="!video.isLocal">Remote</span> 71 <span class="pt-badge badge-purple" *ngIf="!video.isLocal">Remote</span>
72 72
73 <span [ngClass]="getPrivacyBadgeClass(video)" class="badge">{{ video.privacy.label }}</span> 73 <span [ngClass]="getPrivacyBadgeClass(video)" class="pt-badge">{{ video.privacy.label }}</span>
74 74
75 <span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span> 75 <span *ngIf="video.nsfw" class="pt-badge badge-red" i18n>NSFW</span>
76 76
77 <span *ngIf="isUnpublished(video)" class="badge badge-yellow" i18n>{{ video.state.label }}</span> 77 <span *ngIf="isUnpublished(video)" class="pt-badge badge-yellow" i18n>{{ video.state.label }}</span>
78 78
79 <span *ngIf="isAccountBlocked(video)" class="badge badge-red" i18n>Account muted</span> 79 <span *ngIf="isAccountBlocked(video)" class="pt-badge badge-red" i18n>Account muted</span>
80 <span *ngIf="isServerBlocked(video)" class="badge badge-red" i18n>Server muted</span> 80 <span *ngIf="isServerBlocked(video)" class="pt-badge badge-red" i18n>Server muted</span>
81 81
82 <span *ngIf="isVideoBlocked(video)" class="badge badge-red" i18n>Blocked</span> 82 <span *ngIf="isVideoBlocked(video)" class="pt-badge badge-red" i18n>Blocked</span>
83 </td> 83 </td>
84 84
85 <td> 85 <td>
86 <span *ngIf="isHLS(video)" class="badge badge-blue">HLS</span> 86 <span *ngIf="isHLS(video)" class="pt-badge badge-blue">HLS</span>
87 <span *ngIf="isWebTorrent(video)" class="badge badge-blue">WebTorrent ({{ video.files.length }})</span> 87 <span *ngIf="isWebTorrent(video)" class="pt-badge badge-blue">WebTorrent ({{ video.files.length }})</span>
88 <span *ngIf="video.isLive" class="badge badge-blue">Live</span> 88 <span *ngIf="video.isLive" class="pt-badge badge-blue">Live</span>
89 89
90 <span *ngIf="!isImport(video) && !video.isLive && video.isLocal">{{ getFilesSize(video) | bytes: 1 }}</span> 90 <span *ngIf="!isImport(video) && !video.isLive && video.isLocal">{{ getFilesSize(video) | bytes: 1 }}</span>
91 </td> 91 </td>
@@ -121,7 +121,7 @@
121 </ul> 121 </ul>
122 </div> 122 </div>
123 123
124 <my-embed class="ml-auto" [video]="video"></my-embed> 124 <my-embed class="ms-auto" [video]="video"></my-embed>
125 </div> 125 </div>
126 </td> 126 </td>
127 </tr> 127 </tr>
diff --git a/client/src/app/+admin/overview/videos/video-list.component.scss b/client/src/app/+admin/overview/videos/video-list.component.scss
index cb47b6548..dcd41a1b4 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.scss
+++ b/client/src/app/+admin/overview/videos/video-list.component.scss
@@ -7,10 +7,8 @@ my-embed {
7 width: 50%; 7 width: 50%;
8} 8}
9 9
10.badge { 10.pt-badge {
11 @include peertube-badge; 11 @include margin-right(5px);
12
13 margin-right: 5px;
14} 12}
15 13
16.video-info > div { 14.video-info > div {
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts
index 82ff372aa..67e52d100 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.ts
+++ b/client/src/app/+admin/overview/videos/video-list.component.ts
@@ -3,6 +3,7 @@ import { finalize } from 'rxjs/operators'
3import { Component, OnInit, ViewChild } from '@angular/core' 3import { Component, OnInit, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' 5import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
6import { prepareIcu } from '@app/helpers'
6import { AdvancedInputFilter } from '@app/shared/shared-forms' 7import { AdvancedInputFilter } from '@app/shared/shared-forms'
7import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 8import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
8import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' 9import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation'
@@ -196,14 +197,24 @@ export class VideoListComponent extends RestTable implements OnInit {
196 } 197 }
197 198
198 private async removeVideos (videos: Video[]) { 199 private async removeVideos (videos: Video[]) {
199 const message = $localize`Are you sure you want to delete these ${videos.length} videos?` 200 const message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`)(
201 { count: videos.length },
202 $localize`Are you sure you want to delete these ${videos.length} videos?`
203 )
204
200 const res = await this.confirmService.confirm(message, $localize`Delete`) 205 const res = await this.confirmService.confirm(message, $localize`Delete`)
201 if (res === false) return 206 if (res === false) return
202 207
203 this.videoService.removeVideo(videos.map(v => v.id)) 208 this.videoService.removeVideo(videos.map(v => v.id))
204 .subscribe({ 209 .subscribe({
205 next: () => { 210 next: () => {
206 this.notifier.success($localize`Deleted ${videos.length} videos.`) 211 this.notifier.success(
212 prepareIcu($localize`Deleted {count, plural, =1 {1 video} other {{count} videos}}.`)(
213 { count: videos.length },
214 $localize`Deleted ${videos.length} videos.`
215 )
216 )
217
207 this.reloadData() 218 this.reloadData()
208 }, 219 },
209 220
@@ -215,7 +226,13 @@ export class VideoListComponent extends RestTable implements OnInit {
215 this.videoBlockService.unblockVideo(videos.map(v => v.id)) 226 this.videoBlockService.unblockVideo(videos.map(v => v.id))
216 .subscribe({ 227 .subscribe({
217 next: () => { 228 next: () => {
218 this.notifier.success($localize`Unblocked ${videos.length} videos.`) 229 this.notifier.success(
230 prepareIcu($localize`Unblocked {count, plural, =1 {1 video} other {{count} videos}}.`)(
231 { count: videos.length },
232 $localize`Unblocked ${videos.length} videos.`
233 )
234 )
235
219 this.reloadData() 236 this.reloadData()
220 }, 237 },
221 238
@@ -224,9 +241,21 @@ export class VideoListComponent extends RestTable implements OnInit {
224 } 241 }
225 242
226 private async removeVideoFiles (videos: Video[], type: 'hls' | 'webtorrent') { 243 private async removeVideoFiles (videos: Video[], type: 'hls' | 'webtorrent') {
227 const message = type === 'hls' 244 let message: string
228 ? $localize`Are you sure you want to delete ${videos.length} HLS streaming playlists?` 245
229 : $localize`Are you sure you want to delete WebTorrent files of ${videos.length} videos?` 246 if (type === 'hls') {
247 // eslint-disable-next-line max-len
248 message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {{count} HLS streaming playlists}}?`)(
249 { count: videos.length },
250 $localize`Are you sure you want to delete ${videos.length} HLS streaming playlists?`
251 )
252 } else {
253 // eslint-disable-next-line max-len
254 message = prepareIcu($localize`Are you sure you want to delete WebTorrent files of {count, plural, =1 {1 video} other {{count} videos}}?`)(
255 { count: videos.length },
256 $localize`Are you sure you want to delete WebTorrent files of ${videos.length} videos?`
257 )
258 }
230 259
231 const res = await this.confirmService.confirm(message, $localize`Delete`) 260 const res = await this.confirmService.confirm(message, $localize`Delete`)
232 if (res === false) return 261 if (res === false) return
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
index 33575ef52..c989d2e38 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
@@ -1,6 +1,6 @@
1<my-plugin-navigation [pluginType]="pluginType"></my-plugin-navigation> 1<my-plugin-navigation [pluginType]="pluginType"></my-plugin-navigation>
2 2
3<div class="alert alert-info" i18n *ngIf="pluginInstalled"> 3<div class="alert pt-alert-primary" i18n *ngIf="pluginInstalled">
4 To load your new installed plugins or themes, refresh the page. 4 To load your new installed plugins or themes, refresh the page.
5</div> 5</div>
6 6
@@ -32,9 +32,9 @@
32 <ng-container *ngFor="let plugin of plugins" > 32 <ng-container *ngFor="let plugin of plugins" >
33 <my-plugin-card [plugin]="plugin" [version]="plugin.latestVersion" [pluginType]="pluginType"> 33 <my-plugin-card [plugin]="plugin" [version]="plugin.latestVersion" [pluginType]="pluginType">
34 <div ngProjectAs="badges"> 34 <div ngProjectAs="badges">
35 <span i18n *ngIf="plugin.installed" class="badge badge-success">Installed</span> 35 <span i18n *ngIf="plugin.installed" class="pt-badge badge-success">Installed</span>
36 36
37 <span *ngIf="plugin.official" class="badge badge-primary" i18n i18n-title title="This plugin is developed by Framasoft"> 37 <span *ngIf="plugin.official" class="pt-badge badge-primary" i18n i18n-title title="This plugin is developed by Framasoft">
38 Official 38 Official
39 </span> 39 </span>
40 </div> 40 </div>
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss
index 10401e9df..d7b41f4d8 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss
@@ -17,16 +17,13 @@
17 17
18 input { 18 input {
19 @include peertube-input-text(500px); 19 @include peertube-input-text(500px);
20
21 height: 35px;
22 } 20 }
23} 21}
24 22
25.badge { 23.pt-badge {
26 @include margin-left(15px); 24 @include margin-left(15px);
27 25
28 font-size: 13px; 26 font-size: 13px;
29 font-weight: $font-semibold;
30} 27}
31 28
32.alert { 29.alert {
diff --git a/client/src/app/+admin/plugins/shared/plugin-card.component.scss b/client/src/app/+admin/plugins/shared/plugin-card.component.scss
index 608064722..7ee3376a7 100644
--- a/client/src/app/+admin/plugins/shared/plugin-card.component.scss
+++ b/client/src/app/+admin/plugins/shared/plugin-card.component.scss
@@ -14,12 +14,11 @@
14 .plugin-name { 14 .plugin-name {
15 @include margin-right(10px); 15 @include margin-right(10px);
16 16
17 font-size: 16px;
18 font-weight: $font-semibold; 17 font-weight: $font-semibold;
19 } 18 }
20 19
21 .plugin-version { 20 .plugin-version {
22 opacity: 0.6; 21 opacity: 0.7;
23 } 22 }
24 23
25 .plugin-icon { 24 .plugin-icon {
diff --git a/client/src/app/+admin/system/debug/debug.component.scss b/client/src/app/+admin/system/debug/debug.component.scss
index 7f5e861e2..bcbc3fc7c 100644
--- a/client/src/app/+admin/system/debug/debug.component.scss
+++ b/client/src/app/+admin/system/debug/debug.component.scss
@@ -1,11 +1,7 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4.root { 4code {
5 font-size: 15px; 5 font-size: 14px;
6 6 font-weight: $font-semibold;
7 code {
8 font-size: 14px;
9 font-weight: $font-semibold;
10 }
11} 7}
diff --git a/client/src/app/+admin/system/jobs/jobs.component.html b/client/src/app/+admin/system/jobs/jobs.component.html
index 301591786..8068fe626 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.html
+++ b/client/src/app/+admin/system/jobs/jobs.component.html
@@ -21,7 +21,7 @@
21 <span i18n="Selector for the list displaying jobs, filtering by their state">any</span> 21 <span i18n="Selector for the list displaying jobs, filtering by their state">any</span>
22 </ng-option> 22 </ng-option>
23 <ng-option *ngFor="let state of jobStates" [value]="state"> 23 <ng-option *ngFor="let state of jobStates" [value]="state">
24 <span class="badge" [ngClass]="getJobStateClass(state)">{{ state }}</span> 24 <span class="pt-badge" [ngClass]="getJobStateClass(state)">{{ state }}</span>
25 </ng-option> 25 </ng-option>
26 </ng-select> 26 </ng-select>
27 </div> 27 </div>
@@ -62,7 +62,7 @@
62 <td class="job-priority c-hand" [pRowToggler]="job">{{ job.priority }}</td> 62 <td class="job-priority c-hand" [pRowToggler]="job">{{ job.priority }}</td>
63 63
64 <td class="job-state c-hand" [pRowToggler]="job" *ngIf="jobState === 'all'"> 64 <td class="job-state c-hand" [pRowToggler]="job" *ngIf="jobState === 'all'">
65 <span class="badge" [ngClass]="getJobStateClass(job.state)">{{ job.state }}</span> 65 <span class="pt-badge" [ngClass]="getJobStateClass(job.state)">{{ job.state }}</span>
66 </td> 66 </td>
67 67
68 <td *ngIf="hasGlobalProgress()" class="job-progress c-hand" [pRowToggler]="job"> 68 <td *ngIf="hasGlobalProgress()" class="job-progress c-hand" [pRowToggler]="job">
@@ -107,8 +107,8 @@
107 </ng-container> 107 </ng-container>
108 108
109 <ng-container *ngIf="jobState !== 'all'"> 109 <ng-container *ngIf="jobState !== 'all'">
110 <ng-container *ngIf="jobType === 'all'" i18n>No <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span> jobs found.</ng-container> 110 <ng-container *ngIf="jobType === 'all'" i18n>No <span class="pt-badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span> jobs found.</ng-container>
111 <ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found that are <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span>.</ng-container> 111 <ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found that are <span class="pt-badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span>.</ng-container>
112 </ng-container> 112 </ng-container>
113 </div> 113 </div>
114 </div> 114 </div>
diff --git a/client/src/app/+admin/system/jobs/jobs.component.scss b/client/src/app/+admin/system/jobs/jobs.component.scss
index a9e5e8d4b..4a76f1783 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.scss
+++ b/client/src/app/+admin/system/jobs/jobs.component.scss
@@ -44,10 +44,6 @@
44 } 44 }
45} 45}
46 46
47td .glyphicon {
48 @include margin-right(10px);
49}
50
51pre { 47pre {
52 font-size: 11px; 48 font-size: 11px;
53} 49}
@@ -55,7 +51,3 @@ pre {
55.job-error { 51.job-error {
56 color: #ff0000; 52 color: #ff0000;
57} 53}
58
59.select-filter-block .badge {
60 @include peertube-badge;
61}