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