diff options
author | Chocobozzz <me@florianbigard.com> | 2019-02-11 11:52:34 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-02-11 11:52:34 +0100 |
commit | 88108880bbdba473cfe36ecbebc1c3c4f972e102 (patch) | |
tree | b242efb3b4f0d7e49d88f2d1f2063b5b3b0489c0 /client/src/app/+admin | |
parent | 53a94c7cfa8368da4cd248d65df8346905938f0c (diff) | |
parent | 9b712a2017e4ab3cf12cd6bd58278905520159d0 (diff) | |
download | PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.gz PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.zst PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.zip |
Merge branch 'develop' into pr/1217
Diffstat (limited to 'client/src/app/+admin')
42 files changed, 1028 insertions, 521 deletions
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index 8c6db98d9..f7f347105 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts | |||
@@ -10,11 +10,12 @@ import { FollowingListComponent } from './follows/following-list/following-list. | |||
10 | import { JobsComponent } from './jobs/job.component' | 10 | import { JobsComponent } from './jobs/job.component' |
11 | import { JobsListComponent } from './jobs/jobs-list/jobs-list.component' | 11 | import { JobsListComponent } from './jobs/jobs-list/jobs-list.component' |
12 | import { JobService } from './jobs/shared/job.service' | 12 | import { JobService } from './jobs/shared/job.service' |
13 | import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent } from './users' | 13 | import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent, UserPasswordComponent } from './users' |
14 | import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation' | 14 | import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation' |
15 | import { ModerationComponent } from '@app/+admin/moderation/moderation.component' | 15 | import { ModerationComponent } from '@app/+admin/moderation/moderation.component' |
16 | import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component' | 16 | import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component' |
17 | import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' | 17 | import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' |
18 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' | ||
18 | 19 | ||
19 | @NgModule({ | 20 | @NgModule({ |
20 | imports: [ | 21 | imports: [ |
@@ -35,12 +36,15 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service | |||
35 | UsersComponent, | 36 | UsersComponent, |
36 | UserCreateComponent, | 37 | UserCreateComponent, |
37 | UserUpdateComponent, | 38 | UserUpdateComponent, |
39 | UserPasswordComponent, | ||
38 | UserListComponent, | 40 | UserListComponent, |
39 | 41 | ||
40 | ModerationComponent, | 42 | ModerationComponent, |
41 | VideoBlacklistListComponent, | 43 | VideoBlacklistListComponent, |
42 | VideoAbuseListComponent, | 44 | VideoAbuseListComponent, |
43 | ModerationCommentModalComponent, | 45 | ModerationCommentModalComponent, |
46 | InstanceServerBlocklistComponent, | ||
47 | InstanceAccountBlocklistComponent, | ||
44 | 48 | ||
45 | JobsComponent, | 49 | JobsComponent, |
46 | JobsListComponent, | 50 | JobsListComponent, |
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 e2cbd35ca..52eb00d93 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 | |||
@@ -7,161 +7,169 @@ | |||
7 | 7 | ||
8 | <div i18n class="inner-form-title">Instance</div> | 8 | <div i18n class="inner-form-title">Instance</div> |
9 | 9 | ||
10 | <div class="form-group"> | 10 | <ng-container formGroupName="instance"> |
11 | <label i18n for="instanceName">Name</label> | 11 | <div class="form-group"> |
12 | <input | 12 | <label i18n for="instanceName">Name</label> |
13 | type="text" id="instanceName" | 13 | <input |
14 | formControlName="instanceName" [ngClass]="{ 'input-error': formErrors['instanceName'] }" | 14 | type="text" id="instanceName" |
15 | > | 15 | formControlName="name" [ngClass]="{ 'input-error': formErrors.instance.name }" |
16 | <div *ngIf="formErrors.instanceName" class="form-error"> | 16 | > |
17 | {{ formErrors.instanceName }} | 17 | <div *ngIf="formErrors.instance.name" class="form-error">{{ formErrors.instance.name }}</div> |
18 | </div> | 18 | </div> |
19 | </div> | ||
20 | 19 | ||
21 | <div class="form-group"> | 20 | <div class="form-group"> |
22 | <label i18n for="instanceShortDescription">Short description</label> | 21 | <label i18n for="instanceShortDescription">Short description</label> |
23 | <textarea | 22 | <textarea |
24 | id="instanceShortDescription" formControlName="instanceShortDescription" | 23 | id="instanceShortDescription" formControlName="shortDescription" |
25 | [ngClass]="{ 'input-error': formErrors['instanceShortDescription'] }" | 24 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" |
26 | ></textarea> | 25 | ></textarea> |
27 | <div *ngIf="formErrors.instanceShortDescription" class="form-error"> | 26 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> |
28 | {{ formErrors.instanceShortDescription }} | ||
29 | </div> | 27 | </div> |
30 | </div> | ||
31 | 28 | ||
32 | <div class="form-group"> | 29 | <div class="form-group"> |
33 | <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help> | 30 | <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help> |
34 | <my-markdown-textarea | 31 | <my-markdown-textarea |
35 | id="instanceDescription" formControlName="instanceDescription" textareaWidth="500px" [previewColumn]="true" | 32 | id="instanceDescription" formControlName="description" textareaWidth="500px" [previewColumn]="true" |
36 | [classes]="{ 'input-error': formErrors['instanceDescription'] }" | 33 | [classes]="{ 'input-error': formErrors['instance.description'] }" |
37 | ></my-markdown-textarea> | 34 | ></my-markdown-textarea> |
38 | <div *ngIf="formErrors.instanceDescription" class="form-error"> | 35 | <div *ngIf="formErrors.instance.description" class="form-error">{{ formErrors.instance.description }}</div> |
39 | {{ formErrors.instanceDescription }} | ||
40 | </div> | 36 | </div> |
41 | </div> | ||
42 | 37 | ||
43 | <div class="form-group"> | 38 | <div class="form-group"> |
44 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | 39 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> |
45 | <my-markdown-textarea | 40 | <my-markdown-textarea |
46 | id="instanceTerms" formControlName="instanceTerms" textareaWidth="500px" [previewColumn]="true" | 41 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" |
47 | [ngClass]="{ 'input-error': formErrors['instanceTerms'] }" | 42 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" |
48 | ></my-markdown-textarea> | 43 | ></my-markdown-textarea> |
49 | <div *ngIf="formErrors.instanceTerms" class="form-error"> | 44 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> |
50 | {{ formErrors.instanceTerms }} | ||
51 | </div> | 45 | </div> |
52 | </div> | ||
53 | 46 | ||
54 | <div class="form-group"> | 47 | <div class="form-group"> |
55 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | 48 | <label i18n for="instanceDefaultClientRoute">Default client route</label> |
56 | <div class="peertube-select-container"> | 49 | <div class="peertube-select-container"> |
57 | <select id="instanceDefaultClientRoute" formControlName="instanceDefaultClientRoute"> | 50 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> |
58 | <option i18n value="/videos/overview">Videos Overview</option> | 51 | <option i18n value="/videos/overview">Videos Overview</option> |
59 | <option i18n value="/videos/trending">Videos Trending</option> | 52 | <option i18n value="/videos/trending">Videos Trending</option> |
60 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | 53 | <option i18n value="/videos/recently-added">Videos Recently Added</option> |
61 | <option i18n value="/videos/local">Local videos</option> | 54 | <option i18n value="/videos/local">Local videos</option> |
62 | </select> | 55 | </select> |
56 | </div> | ||
57 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | ||
63 | </div> | 58 | </div> |
64 | <div *ngIf="formErrors.instanceDefaultClientRoute" class="form-error"> | 59 | |
65 | {{ formErrors.instanceDefaultClientRoute }} | 60 | <div class="form-group"> |
61 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | ||
62 | <my-help | ||
63 | helpType="custom" i18n-customHtml | ||
64 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | ||
65 | ></my-help> | ||
66 | |||
67 | <div class="peertube-select-container"> | ||
68 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> | ||
69 | <option i18n value="do_not_list">Do not list</option> | ||
70 | <option i18n value="blur">Blur thumbnails</option> | ||
71 | <option i18n value="display">Display</option> | ||
72 | </select> | ||
73 | </div> | ||
74 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> | ||
66 | </div> | 75 | </div> |
67 | </div> | 76 | </ng-container> |
68 | 77 | ||
69 | <div class="form-group"> | 78 | <div i18n class="inner-form-title">Signup</div> |
70 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | ||
71 | <my-help | ||
72 | helpType="custom" i18n-customHtml | ||
73 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | ||
74 | ></my-help> | ||
75 | 79 | ||
76 | <div class="peertube-select-container"> | 80 | <ng-container formGroupName="signup"> |
77 | <select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy"> | 81 | <div class="form-group"> |
78 | <option i18n value="do_not_list">Do not list</option> | 82 | <my-peertube-checkbox |
79 | <option i18n value="blur">Blur thumbnails</option> | 83 | inputName="signupEnabled" formControlName="enabled" |
80 | <option i18n value="display">Display</option> | 84 | i18n-labelText labelText="Signup enabled" |
81 | </select> | 85 | ></my-peertube-checkbox> |
82 | </div> | 86 | </div> |
83 | <div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error"> | 87 | |
84 | {{ formErrors.instanceDefaultNSFWPolicy }} | 88 | <div class="form-group"> |
89 | <my-peertube-checkbox *ngIf="isSignupEnabled()" | ||
90 | inputName="signupRequiresEmailVerification" formControlName="requiresEmailVerification" | ||
91 | i18n-labelText labelText="Signup requires email verification" | ||
92 | ></my-peertube-checkbox> | ||
85 | </div> | 93 | </div> |
86 | </div> | ||
87 | 94 | ||
88 | <div i18n class="inner-form-title">Signup</div> | 95 | <div *ngIf="isSignupEnabled()" class="form-group"> |
96 | <label i18n for="signupLimit">Signup limit</label> | ||
97 | <input | ||
98 | type="text" id="signupLimit" | ||
99 | formControlName="limit" [ngClass]="{ 'input-error': formErrors['signup.limit'] }" | ||
100 | > | ||
101 | <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div> | ||
102 | </div> | ||
103 | </ng-container> | ||
89 | 104 | ||
90 | <my-peertube-checkbox | 105 | <div i18n class="inner-form-title">Users</div> |
91 | inputName="signupEnabled" formControlName="signupEnabled" | ||
92 | i18n-labelText labelText="Signup enabled" | ||
93 | ></my-peertube-checkbox> | ||
94 | 106 | ||
95 | <my-peertube-checkbox *ngIf="isSignupEnabled()" | 107 | <ng-container formGroupName="user"> |
96 | inputName="signupRequiresEmailVerification" formControlName="signupRequiresEmailVerification" | 108 | <div class="form-group"> |
97 | i18n-labelText labelText="Signup requires email verification" | 109 | <label i18n for="userVideoQuota">User default video quota</label> |
98 | ></my-peertube-checkbox> | 110 | <div class="peertube-select-container"> |
111 | <select id="userVideoQuota" formControlName="videoQuota"> | ||
112 | <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> | ||
113 | {{ videoQuotaOption.label }} | ||
114 | </option> | ||
115 | </select> | ||
116 | </div> | ||
117 | <div *ngIf="formErrors.user.videoQuota" class="form-error">{{ formErrors.user.videoQuota }}</div> | ||
118 | </div> | ||
99 | 119 | ||
100 | <div *ngIf="isSignupEnabled()" class="form-group"> | 120 | <div class="form-group"> |
101 | <label i18n for="signupLimit">Signup limit</label> | 121 | <label i18n for="userVideoQuotaDaily">User default daily upload limit</label> |
102 | <input | 122 | <div class="peertube-select-container"> |
103 | type="text" id="signupLimit" | 123 | <select id="userVideoQuotaDaily" formControlName="videoQuotaDaily"> |
104 | formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }" | 124 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> |
105 | > | 125 | {{ videoQuotaDailyOption.label }} |
106 | <div *ngIf="formErrors.signupLimit" class="form-error"> | 126 | </option> |
107 | {{ formErrors.signupLimit }} | 127 | </select> |
128 | </div> | ||
129 | <div *ngIf="formErrors.user.videoQuotaDaily" class="form-error">{{ formErrors.user.videoQuotaDaily }}</div> | ||
108 | </div> | 130 | </div> |
109 | </div> | 131 | </ng-container> |
110 | 132 | ||
111 | <div i18n class="inner-form-title">Import</div> | 133 | <div i18n class="inner-form-title">Import</div> |
112 | 134 | ||
113 | <my-peertube-checkbox | 135 | <ng-container formGroupName="import"> |
114 | inputName="importVideosHttpEnabled" formControlName="importVideosHttpEnabled" | 136 | <ng-container formGroupName="videos"> |
115 | i18n-labelText labelText="Video import with HTTP enabled" | ||
116 | ></my-peertube-checkbox> | ||
117 | 137 | ||
118 | <my-peertube-checkbox | 138 | <div class="form-group" formGroupName="http"> |
119 | inputName="importVideosTorrentEnabled" formControlName="importVideosTorrentEnabled" | 139 | <my-peertube-checkbox |
120 | i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled" | 140 | inputName="importVideosHttpEnabled" formControlName="enabled" |
121 | ></my-peertube-checkbox> | 141 | i18n-labelText labelText="Video import with HTTP URL (i.e. YouTube) enabled" |
142 | ></my-peertube-checkbox> | ||
143 | </div> | ||
144 | |||
145 | <div class="form-group" formGroupName="torrent"> | ||
146 | <my-peertube-checkbox | ||
147 | inputName="importVideosTorrentEnabled" formControlName="enabled" | ||
148 | i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled" | ||
149 | ></my-peertube-checkbox> | ||
150 | </div> | ||
151 | |||
152 | </ng-container> | ||
153 | </ng-container> | ||
122 | 154 | ||
123 | <div i18n class="inner-form-title">Administrator</div> | 155 | <div i18n class="inner-form-title">Administrator</div> |
124 | 156 | ||
125 | <div class="form-group"> | 157 | <div class="form-group" formGroupName="admin"> |
126 | <label i18n for="adminEmail">Admin email</label> | 158 | <label i18n for="adminEmail">Admin email</label> |
127 | <input | 159 | <input |
128 | type="text" id="adminEmail" | 160 | type="text" id="adminEmail" |
129 | formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }" | 161 | formControlName="email" [ngClass]="{ 'input-error': formErrors['admin.email'] }" |
130 | > | 162 | > |
131 | <div *ngIf="formErrors.adminEmail" class="form-error"> | 163 | <div *ngIf="formErrors.admin.email" class="form-error">{{ formErrors.admin.email }}</div> |
132 | {{ formErrors.adminEmail }} | ||
133 | </div> | ||
134 | </div> | 164 | </div> |
135 | 165 | ||
136 | <div i18n class="inner-form-title">Users</div> | 166 | <div class="form-group" formGroupName="contactForm"> |
137 | 167 | <my-peertube-checkbox | |
138 | <div class="form-group"> | 168 | inputName="enableContactForm" formControlName="enabled" |
139 | <label i18n for="userVideoQuota">User default video quota</label> | 169 | i18n-labelText labelText="Enable contact form" |
140 | <div class="peertube-select-container"> | 170 | ></my-peertube-checkbox> |
141 | <select id="userVideoQuota" formControlName="userVideoQuota"> | ||
142 | <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> | ||
143 | {{ videoQuotaOption.label }} | ||
144 | </option> | ||
145 | </select> | ||
146 | </div> | ||
147 | <div *ngIf="formErrors.userVideoQuota" class="form-error"> | ||
148 | {{ formErrors.userVideoQuota }} | ||
149 | </div> | ||
150 | </div> | 171 | </div> |
151 | 172 | ||
152 | <div class="form-group"> | ||
153 | <label i18n for="userVideoQuotaDaily">User default daily upload limit</label> | ||
154 | <div class="peertube-select-container"> | ||
155 | <select id="userVideoQuotaDaily" formControlName="userVideoQuotaDaily"> | ||
156 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> | ||
157 | {{ videoQuotaDailyOption.label }} | ||
158 | </option> | ||
159 | </select> | ||
160 | </div> | ||
161 | <div *ngIf="formErrors.userVideoQuotaDaily" class="form-error"> | ||
162 | {{ formErrors.userVideoQuotaDaily }} | ||
163 | </div> | ||
164 | </div> | ||
165 | </ng-template> | 173 | </ng-template> |
166 | </ngb-tab> | 174 | </ngb-tab> |
167 | 175 | ||
@@ -169,28 +177,35 @@ | |||
169 | <ng-template ngbTabContent> | 177 | <ng-template ngbTabContent> |
170 | <div i18n class="inner-form-title">Twitter</div> | 178 | <div i18n class="inner-form-title">Twitter</div> |
171 | 179 | ||
172 | <div class="form-group"> | 180 | <ng-container formGroupName="services"> |
173 | <label i18n for="signupLimit">Your Twitter username</label> | 181 | <ng-container formGroupName="twitter"> |
174 | <my-help | 182 | |
175 | helpType="custom" i18n-customHtml | 183 | <div class="form-group"> |
176 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." | 184 | <label i18n for="signupLimit">Your Twitter username</label> |
177 | ></my-help> | 185 | <my-help |
178 | <input | 186 | helpType="custom" i18n-customHtml |
179 | type="text" id="servicesTwitterUsername" | 187 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." |
180 | formControlName="servicesTwitterUsername" [ngClass]="{ 'input-error': formErrors['servicesTwitterUsername'] }" | 188 | ></my-help> |
181 | > | 189 | <input |
182 | <div *ngIf="formErrors.servicesTwitterUsername" class="form-error"> | 190 | type="text" id="servicesTwitterUsername" |
183 | {{ formErrors.servicesTwitterUsername }} | 191 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" |
184 | </div> | 192 | > |
185 | </div> | 193 | <div *ngIf="formErrors.services.twitter.username" class="form-error">{{ formErrors.services.twitter.username }}</div> |
194 | </div> | ||
195 | |||
196 | <div class="form-group"> | ||
197 | <my-peertube-checkbox | ||
198 | inputName="servicesTwitterWhitelisted" formControlName="whitelisted" | ||
199 | i18n-labelText labelText="Instance whitelisted by Twitter" | ||
200 | i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | ||
201 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
202 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." | ||
203 | ></my-peertube-checkbox> | ||
204 | </div> | ||
205 | |||
206 | </ng-container> | ||
207 | </ng-container> | ||
186 | 208 | ||
187 | <my-peertube-checkbox | ||
188 | inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted" | ||
189 | i18n-labelText labelText="Instance whitelisted by Twitter" | ||
190 | i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | ||
191 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
192 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." | ||
193 | ></my-peertube-checkbox> | ||
194 | </ng-template> | 209 | </ng-template> |
195 | </ngb-tab> | 210 | </ngb-tab> |
196 | 211 | ||
@@ -199,36 +214,48 @@ | |||
199 | 214 | ||
200 | <div i18n class="inner-form-title">Transcoding</div> | 215 | <div i18n class="inner-form-title">Transcoding</div> |
201 | 216 | ||
202 | <my-peertube-checkbox | 217 | <ng-container formGroupName="transcoding"> |
203 | inputName="transcodingEnabled" formControlName="transcodingEnabled" | 218 | <div class="form-group"> |
204 | i18n-labelText labelText="Transcoding enabled" | 219 | <my-peertube-checkbox |
205 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" | 220 | inputName="transcodingEnabled" formControlName="enabled" |
206 | ></my-peertube-checkbox> | 221 | i18n-labelText labelText="Transcoding enabled" |
222 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" | ||
223 | ></my-peertube-checkbox> | ||
224 | </div> | ||
207 | 225 | ||
208 | <ng-template [ngIf]="isTranscodingEnabled()"> | 226 | <ng-container *ngIf="isTranscodingEnabled()"> |
209 | 227 | ||
210 | <div class="form-group"> | 228 | <div class="form-group"> |
211 | <label i18n for="transcodingThreads">Transcoding threads</label> | 229 | <my-peertube-checkbox |
212 | <div class="peertube-select-container"> | 230 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" |
213 | <select id="transcodingThreads" formControlName="transcodingThreads"> | 231 | i18n-labelText labelText="Allow additional extensions" |
214 | <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> | 232 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" |
215 | {{ transcodingThreadOption.label }} | 233 | ></my-peertube-checkbox> |
216 | </option> | ||
217 | </select> | ||
218 | </div> | 234 | </div> |
219 | <div *ngIf="formErrors.transcodingThreads" class="form-error"> | 235 | |
220 | {{ formErrors.transcodingThreads }} | 236 | <div class="form-group"> |
237 | <label i18n for="transcodingThreads">Transcoding threads</label> | ||
238 | <div class="peertube-select-container"> | ||
239 | <select id="transcodingThreads" formControlName="threads"> | ||
240 | <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> | ||
241 | {{ transcodingThreadOption.label }} | ||
242 | </option> | ||
243 | </select> | ||
244 | </div> | ||
245 | <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div> | ||
221 | </div> | 246 | </div> |
222 | </div> | ||
223 | 247 | ||
224 | <div class="form-group" *ngFor="let resolution of resolutions"> | 248 | <ng-container formGroupName="resolutions"> |
225 | <my-peertube-checkbox | 249 | <div class="form-group" *ngFor="let resolution of resolutions"> |
226 | [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)" | 250 | <my-peertube-checkbox |
227 | i18n-labelText labelText="Resolution {{resolution}} enabled" | 251 | [inputName]="getResolutionKey(resolution)" [formControlName]="resolution" |
228 | ></my-peertube-checkbox> | 252 | i18n-labelText labelText="Resolution {{resolution}} enabled" |
253 | ></my-peertube-checkbox> | ||
254 | </div> | ||
255 | </ng-container> | ||
229 | 256 | ||
230 | </div> | 257 | </ng-container> |
231 | </ng-template> | 258 | </ng-container> |
232 | 259 | ||
233 | <div i18n class="inner-form-title"> | 260 | <div i18n class="inner-form-title"> |
234 | Cache | 261 | Cache |
@@ -239,74 +266,73 @@ | |||
239 | ></my-help> | 266 | ></my-help> |
240 | </div> | 267 | </div> |
241 | 268 | ||
242 | <div class="form-group"> | 269 | <ng-container formGroupName="cache"> |
243 | <label i18n for="cachePreviewsSize">Previews cache size</label> | 270 | <div class="form-group" formGroupName="previews"> |
244 | <input | 271 | <label i18n for="cachePreviewsSize">Previews cache size</label> |
245 | type="text" id="cachePreviewsSize" | 272 | <input |
246 | formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }" | 273 | type="text" id="cachePreviewsSize" |
247 | > | 274 | formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.previews.size'] }" |
248 | <div *ngIf="formErrors.cachePreviewsSize" class="form-error"> | 275 | > |
249 | {{ formErrors.cachePreviewsSize }} | 276 | <div *ngIf="formErrors.cache.previews.size" class="form-error">{{ formErrors.cache.previews.size }}</div> |
250 | </div> | 277 | </div> |
251 | </div> | ||
252 | 278 | ||
253 | <div class="form-group"> | 279 | <div class="form-group" formGroupName="captions"> |
254 | <label i18n for="cachePreviewsSize">Video captions cache size</label> | 280 | <label i18n for="cacheCaptionsSize">Video captions cache size</label> |
255 | <input | 281 | <input |
256 | type="text" id="cacheCaptionsSize" | 282 | type="text" id="cacheCaptionsSize" |
257 | formControlName="cacheCaptionsSize" [ngClass]="{ 'input-error': formErrors['cacheCaptionsSize'] }" | 283 | formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.captions.size'] }" |
258 | > | 284 | > |
259 | <div *ngIf="formErrors.cacheCaptionsSize" class="form-error"> | 285 | <div *ngIf="formErrors.cache.captions.size" class="form-error">{{ formErrors.cache.captions.size }}</div> |
260 | {{ formErrors.cacheCaptionsSize }} | ||
261 | </div> | 286 | </div> |
262 | </div> | 287 | </ng-container> |
263 | 288 | ||
264 | <div i18n class="inner-form-title">Customizations</div> | 289 | <div i18n class="inner-form-title">Customizations</div> |
265 | 290 | ||
266 | <div class="form-group"> | 291 | <ng-container formGroupName="instance"> |
267 | <label i18n for="customizationJavascript">JavaScript</label> | 292 | <ng-container formGroupName="customizations"> |
268 | <my-help | 293 | <div class="form-group"> |
269 | helpType="custom" i18n-customHtml | 294 | <label i18n for="customizationJavascript">JavaScript</label> |
270 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" | 295 | <my-help |
271 | ></my-help> | 296 | helpType="custom" i18n-customHtml |
272 | <textarea | 297 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" |
273 | id="customizationJavascript" formControlName="customizationJavascript" | 298 | ></my-help> |
274 | [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }" | 299 | <textarea |
275 | ></textarea> | 300 | id="customizationJavascript" formControlName="javascript" |
276 | <div *ngIf="formErrors.customizationJavascript" class="form-error"> | 301 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" |
277 | {{ formErrors.customizationJavascript }} | 302 | ></textarea> |
278 | </div> | 303 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> |
279 | </div> | 304 | </div> |
305 | |||
306 | <div class="form-group"> | ||
307 | <label for="customizationCSS">CSS</label> | ||
308 | <my-help | ||
309 | helpType="custom" | ||
310 | i18n-customHtml | ||
311 | customHtml=" | ||
312 | Write directly CSS code. Example:<br /> | ||
313 | <pre> | ||
314 | body {{ '{' }} | ||
315 | background-color: red; | ||
316 | {{ '}' }} | ||
317 | </pre> | ||
318 | |||
319 | Prepend with <em>#custom-css</em> to override styles. Example: | ||
320 | <pre> | ||
321 | #custom-css .logged-in-email {{ '{' }} | ||
322 | color: red; | ||
323 | {{ '}' }} | ||
324 | </pre> | ||
325 | " | ||
326 | ></my-help> | ||
327 | <textarea | ||
328 | id="customizationCSS" formControlName="css" | ||
329 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" | ||
330 | ></textarea> | ||
331 | <div *ngIf="formErrors.instance.customizations.css" class="form-error">{{ formErrors.instance.customizations.css }}</div> | ||
332 | </div> | ||
333 | </ng-container> | ||
334 | </ng-container> | ||
280 | 335 | ||
281 | <div class="form-group"> | ||
282 | <label for="customizationCSS">CSS</label> | ||
283 | <my-help | ||
284 | helpType="custom" | ||
285 | i18n-customHtml | ||
286 | customHtml=" | ||
287 | Write directly CSS code. Example:<br /> | ||
288 | <pre> | ||
289 | body {{ '{' }} | ||
290 | background-color: red; | ||
291 | {{ '}' }} | ||
292 | </pre> | ||
293 | |||
294 | Prepend with <em>#custom-css</em> to override styles. Example: | ||
295 | <pre> | ||
296 | #custom-css .logged-in-email {{ '{' }} | ||
297 | color: red; | ||
298 | {{ '}' }} | ||
299 | </pre> | ||
300 | " | ||
301 | ></my-help> | ||
302 | <textarea | ||
303 | id="customizationCSS" formControlName="customizationCSS" | ||
304 | [ngClass]="{ 'input-error': formErrors['customizationCSS'] }" | ||
305 | ></textarea> | ||
306 | <div *ngIf="formErrors.customizationCSS" class="form-error"> | ||
307 | {{ formErrors.customizationCSS }} | ||
308 | </div> | ||
309 | </div> | ||
310 | </ng-template> | 336 | </ng-template> |
311 | </ngb-tab> | 337 | </ngb-tab> |
312 | </ngb-tabset> | 338 | </ngb-tabset> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 4983b0425..654a076b0 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ConfigService } from '@app/+admin/config/shared/config.service' | 2 | import { ConfigService } from '@app/+admin/config/shared/config.service' |
3 | import { ConfirmService } from '@app/core' | ||
4 | import { ServerService } from '@app/core/server/server.service' | 3 | import { ServerService } from '@app/core/server/server.service' |
5 | import { CustomConfigValidatorsService, FormReactive, UserValidatorsService } from '@app/shared' | 4 | import { CustomConfigValidatorsService, FormReactive, UserValidatorsService } from '@app/shared' |
6 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
7 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' | 6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' |
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
9 | import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
@@ -19,17 +18,13 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
19 | resolutions: string[] = [] | 18 | resolutions: string[] = [] |
20 | transcodingThreadOptions: { label: string, value: number }[] = [] | 19 | transcodingThreadOptions: { label: string, value: number }[] = [] |
21 | 20 | ||
22 | private oldCustomJavascript: string | ||
23 | private oldCustomCSS: string | ||
24 | |||
25 | constructor ( | 21 | constructor ( |
26 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
27 | private customConfigValidatorsService: CustomConfigValidatorsService, | 23 | private customConfigValidatorsService: CustomConfigValidatorsService, |
28 | private userValidatorsService: UserValidatorsService, | 24 | private userValidatorsService: UserValidatorsService, |
29 | private notificationsService: NotificationsService, | 25 | private notifier: Notifier, |
30 | private configService: ConfigService, | 26 | private configService: ConfigService, |
31 | private serverService: ServerService, | 27 | private serverService: ServerService, |
32 | private confirmService: ConfirmService, | ||
33 | private i18n: I18n | 28 | private i18n: I18n |
34 | ) { | 29 | ) { |
35 | super() | 30 | super() |
@@ -60,40 +55,78 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
60 | } | 55 | } |
61 | 56 | ||
62 | getResolutionKey (resolution: string) { | 57 | getResolutionKey (resolution: string) { |
63 | return 'transcodingResolution' + resolution | 58 | return 'transcoding.resolutions.' + resolution |
64 | } | 59 | } |
65 | 60 | ||
66 | ngOnInit () { | 61 | ngOnInit () { |
67 | const formGroupData = { | 62 | const formGroupData: { [key in keyof CustomConfig ]: any } = { |
68 | instanceName: this.customConfigValidatorsService.INSTANCE_NAME, | 63 | instance: { |
69 | instanceShortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, | 64 | name: this.customConfigValidatorsService.INSTANCE_NAME, |
70 | instanceDescription: null, | 65 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, |
71 | instanceTerms: null, | 66 | description: null, |
72 | instanceDefaultClientRoute: null, | 67 | terms: null, |
73 | instanceDefaultNSFWPolicy: null, | 68 | defaultClientRoute: null, |
74 | servicesTwitterUsername: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, | 69 | defaultNSFWPolicy: null, |
75 | servicesTwitterWhitelisted: null, | 70 | customizations: { |
76 | cachePreviewsSize: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE, | 71 | javascript: null, |
77 | cacheCaptionsSize: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE, | 72 | css: null |
78 | signupEnabled: null, | 73 | } |
79 | signupLimit: this.customConfigValidatorsService.SIGNUP_LIMIT, | 74 | }, |
80 | signupRequiresEmailVerification: null, | 75 | services: { |
81 | importVideosHttpEnabled: null, | 76 | twitter: { |
82 | importVideosTorrentEnabled: null, | 77 | username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, |
83 | adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL, | 78 | whitelisted: null |
84 | userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 79 | } |
85 | userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, | 80 | }, |
86 | transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, | 81 | cache: { |
87 | transcodingEnabled: null, | 82 | previews: { |
88 | customizationJavascript: null, | 83 | size: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE |
89 | customizationCSS: null | 84 | }, |
85 | captions: { | ||
86 | size: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE | ||
87 | } | ||
88 | }, | ||
89 | signup: { | ||
90 | enabled: null, | ||
91 | limit: this.customConfigValidatorsService.SIGNUP_LIMIT, | ||
92 | requiresEmailVerification: null | ||
93 | }, | ||
94 | import: { | ||
95 | videos: { | ||
96 | http: { | ||
97 | enabled: null | ||
98 | }, | ||
99 | torrent: { | ||
100 | enabled: null | ||
101 | } | ||
102 | } | ||
103 | }, | ||
104 | admin: { | ||
105 | email: this.customConfigValidatorsService.ADMIN_EMAIL | ||
106 | }, | ||
107 | contactForm: { | ||
108 | enabled: null | ||
109 | }, | ||
110 | user: { | ||
111 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | ||
112 | videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY | ||
113 | }, | ||
114 | transcoding: { | ||
115 | enabled: null, | ||
116 | threads: this.customConfigValidatorsService.TRANSCODING_THREADS, | ||
117 | allowAdditionalExtensions: null, | ||
118 | resolutions: {} | ||
119 | } | ||
90 | } | 120 | } |
91 | 121 | ||
92 | const defaultValues: BuildFormDefaultValues = {} | 122 | const defaultValues = { |
123 | transcoding: { | ||
124 | resolutions: {} | ||
125 | } | ||
126 | } | ||
93 | for (const resolution of this.resolutions) { | 127 | for (const resolution of this.resolutions) { |
94 | const key = this.getResolutionKey(resolution) | 128 | defaultValues.transcoding.resolutions[resolution] = 'false' |
95 | defaultValues[key] = 'false' | 129 | formGroupData.transcoding.resolutions[resolution] = null |
96 | formGroupData[key] = null | ||
97 | } | 130 | } |
98 | 131 | ||
99 | this.buildForm(formGroupData) | 132 | this.buildForm(formGroupData) |
@@ -103,112 +136,25 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
103 | res => { | 136 | res => { |
104 | this.customConfig = res | 137 | this.customConfig = res |
105 | 138 | ||
106 | this.oldCustomCSS = this.customConfig.instance.customizations.css | ||
107 | this.oldCustomJavascript = this.customConfig.instance.customizations.javascript | ||
108 | |||
109 | this.updateForm() | 139 | this.updateForm() |
110 | // Force form validation | 140 | // Force form validation |
111 | this.forceCheck() | 141 | this.forceCheck() |
112 | }, | 142 | }, |
113 | 143 | ||
114 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 144 | err => this.notifier.error(err.message) |
115 | ) | 145 | ) |
116 | } | 146 | } |
117 | 147 | ||
118 | isTranscodingEnabled () { | 148 | isTranscodingEnabled () { |
119 | return this.form.value['transcodingEnabled'] === true | 149 | return this.form.value['transcoding']['enabled'] === true |
120 | } | 150 | } |
121 | 151 | ||
122 | isSignupEnabled () { | 152 | isSignupEnabled () { |
123 | return this.form.value['signupEnabled'] === true | 153 | return this.form.value['signup']['enabled'] === true |
124 | } | 154 | } |
125 | 155 | ||
126 | async formValidated () { | 156 | async formValidated () { |
127 | const newCustomizationJavascript = this.form.value['customizationJavascript'] | 157 | this.configService.updateCustomConfig(this.form.value) |
128 | const newCustomizationCSS = this.form.value['customizationCSS'] | ||
129 | |||
130 | const customizations = [] | ||
131 | if (newCustomizationJavascript && newCustomizationJavascript !== this.oldCustomJavascript) customizations.push('JavaScript') | ||
132 | if (newCustomizationCSS && newCustomizationCSS !== this.oldCustomCSS) customizations.push('CSS') | ||
133 | |||
134 | if (customizations.length !== 0) { | ||
135 | const customizationsText = customizations.join('/') | ||
136 | |||
137 | // FIXME: i18n service does not support string concatenation | ||
138 | const message = this.i18n('You set custom {{customizationsText}}. ', { customizationsText }) + | ||
139 | this.i18n('This could lead to security issues or bugs if you do not understand it. ') + | ||
140 | this.i18n('Are you sure you want to update the configuration?') | ||
141 | |||
142 | const label = this.i18n('Please type') + ` "I understand the ${customizationsText} I set" ` + this.i18n('to confirm.') | ||
143 | const expectedInputValue = `I understand the ${customizationsText} I set` | ||
144 | |||
145 | const confirmRes = await this.confirmService.confirmWithInput(message, label, expectedInputValue) | ||
146 | if (confirmRes === false) return | ||
147 | } | ||
148 | |||
149 | const data: CustomConfig = { | ||
150 | instance: { | ||
151 | name: this.form.value['instanceName'], | ||
152 | shortDescription: this.form.value['instanceShortDescription'], | ||
153 | description: this.form.value['instanceDescription'], | ||
154 | terms: this.form.value['instanceTerms'], | ||
155 | defaultClientRoute: this.form.value['instanceDefaultClientRoute'], | ||
156 | defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'], | ||
157 | customizations: { | ||
158 | javascript: this.form.value['customizationJavascript'], | ||
159 | css: this.form.value['customizationCSS'] | ||
160 | } | ||
161 | }, | ||
162 | services: { | ||
163 | twitter: { | ||
164 | username: this.form.value['servicesTwitterUsername'], | ||
165 | whitelisted: this.form.value['servicesTwitterWhitelisted'] | ||
166 | } | ||
167 | }, | ||
168 | cache: { | ||
169 | previews: { | ||
170 | size: this.form.value['cachePreviewsSize'] | ||
171 | }, | ||
172 | captions: { | ||
173 | size: this.form.value['cacheCaptionsSize'] | ||
174 | } | ||
175 | }, | ||
176 | signup: { | ||
177 | enabled: this.form.value['signupEnabled'], | ||
178 | limit: this.form.value['signupLimit'], | ||
179 | requiresEmailVerification: this.form.value['signupRequiresEmailVerification'] | ||
180 | }, | ||
181 | admin: { | ||
182 | email: this.form.value['adminEmail'] | ||
183 | }, | ||
184 | user: { | ||
185 | videoQuota: this.form.value['userVideoQuota'], | ||
186 | videoQuotaDaily: this.form.value['userVideoQuotaDaily'] | ||
187 | }, | ||
188 | transcoding: { | ||
189 | enabled: this.form.value['transcodingEnabled'], | ||
190 | threads: this.form.value['transcodingThreads'], | ||
191 | resolutions: { | ||
192 | '240p': this.form.value[this.getResolutionKey('240p')], | ||
193 | '360p': this.form.value[this.getResolutionKey('360p')], | ||
194 | '480p': this.form.value[this.getResolutionKey('480p')], | ||
195 | '720p': this.form.value[this.getResolutionKey('720p')], | ||
196 | '1080p': this.form.value[this.getResolutionKey('1080p')] | ||
197 | } | ||
198 | }, | ||
199 | import: { | ||
200 | videos: { | ||
201 | http: { | ||
202 | enabled: this.form.value['importVideosHttpEnabled'] | ||
203 | }, | ||
204 | torrent: { | ||
205 | enabled: this.form.value['importVideosTorrentEnabled'] | ||
206 | } | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | this.configService.updateCustomConfig(data) | ||
212 | .subscribe( | 158 | .subscribe( |
213 | res => { | 159 | res => { |
214 | this.customConfig = res | 160 | this.customConfig = res |
@@ -218,45 +164,15 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
218 | 164 | ||
219 | this.updateForm() | 165 | this.updateForm() |
220 | 166 | ||
221 | this.notificationsService.success(this.i18n('Success'), this.i18n('Configuration updated.')) | 167 | this.notifier.success(this.i18n('Configuration updated.')) |
222 | }, | 168 | }, |
223 | 169 | ||
224 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 170 | err => this.notifier.error(err.message) |
225 | ) | 171 | ) |
226 | } | 172 | } |
227 | 173 | ||
228 | private updateForm () { | 174 | private updateForm () { |
229 | const data = { | 175 | this.form.patchValue(this.customConfig) |
230 | instanceName: this.customConfig.instance.name, | ||
231 | instanceShortDescription: this.customConfig.instance.shortDescription, | ||
232 | instanceDescription: this.customConfig.instance.description, | ||
233 | instanceTerms: this.customConfig.instance.terms, | ||
234 | instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute, | ||
235 | instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy, | ||
236 | servicesTwitterUsername: this.customConfig.services.twitter.username, | ||
237 | servicesTwitterWhitelisted: this.customConfig.services.twitter.whitelisted, | ||
238 | cachePreviewsSize: this.customConfig.cache.previews.size, | ||
239 | cacheCaptionsSize: this.customConfig.cache.captions.size, | ||
240 | signupEnabled: this.customConfig.signup.enabled, | ||
241 | signupLimit: this.customConfig.signup.limit, | ||
242 | signupRequiresEmailVerification: this.customConfig.signup.requiresEmailVerification, | ||
243 | adminEmail: this.customConfig.admin.email, | ||
244 | userVideoQuota: this.customConfig.user.videoQuota, | ||
245 | userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily, | ||
246 | transcodingThreads: this.customConfig.transcoding.threads, | ||
247 | transcodingEnabled: this.customConfig.transcoding.enabled, | ||
248 | customizationJavascript: this.customConfig.instance.customizations.javascript, | ||
249 | customizationCSS: this.customConfig.instance.customizations.css, | ||
250 | importVideosHttpEnabled: this.customConfig.import.videos.http.enabled, | ||
251 | importVideosTorrentEnabled: this.customConfig.import.videos.torrent.enabled | ||
252 | } | ||
253 | |||
254 | for (const resolution of this.resolutions) { | ||
255 | const key = this.getResolutionKey(resolution) | ||
256 | data[key] = this.customConfig.transcoding.resolutions[resolution] | ||
257 | } | ||
258 | |||
259 | this.form.patchValue(data) | ||
260 | } | 176 | } |
261 | 177 | ||
262 | } | 178 | } |
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 5645a60cc..fc022bdb4 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 | |||
@@ -2,6 +2,15 @@ | |||
2 | [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 2 | [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" |
4 | > | 4 | > |
5 | <ng-template pTemplate="caption"> | ||
6 | <div class="caption"> | ||
7 | <input | ||
8 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | ||
9 | (keyup)="onSearch($event.target.value)" | ||
10 | > | ||
11 | </div> | ||
12 | </ng-template> | ||
13 | |||
5 | <ng-template pTemplate="header"> | 14 | <ng-template pTemplate="header"> |
6 | <tr> | 15 | <tr> |
7 | <th i18n style="width: 60px">ID</th> | 16 | <th i18n style="width: 60px">ID</th> |
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.scss b/client/src/app/+admin/follows/followers-list/followers-list.component.scss index e69de29bb..a6f0656b8 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.scss +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.scss | |||
@@ -0,0 +1,10 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .caption { | ||
5 | justify-content: flex-end; | ||
6 | |||
7 | input { | ||
8 | @include peertube-input-text(250px); | ||
9 | } | ||
10 | } \ No newline at end of file | ||
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts index ca993dcd3..9a8848bfb 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | 2 | ||
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { SortMeta } from 'primeng/primeng' | 4 | import { SortMeta } from 'primeng/primeng' |
5 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' | 5 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' |
6 | import { RestPagination, RestTable } from '../../../shared' | 6 | import { RestPagination, RestTable } from '../../../shared' |
@@ -20,7 +20,7 @@ export class FollowersListComponent extends RestTable implements OnInit { | |||
20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
21 | 21 | ||
22 | constructor ( | 22 | constructor ( |
23 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
24 | private followService: FollowService, | 24 | private followService: FollowService, |
25 | private i18n: I18n | 25 | private i18n: I18n |
26 | ) { | 26 | ) { |
@@ -28,18 +28,18 @@ export class FollowersListComponent extends RestTable implements OnInit { | |||
28 | } | 28 | } |
29 | 29 | ||
30 | ngOnInit () { | 30 | ngOnInit () { |
31 | this.loadSort() | 31 | this.initialize() |
32 | } | 32 | } |
33 | 33 | ||
34 | protected loadData () { | 34 | protected loadData () { |
35 | this.followService.getFollowers(this.pagination, this.sort) | 35 | this.followService.getFollowers(this.pagination, this.sort, this.search) |
36 | .subscribe( | 36 | .subscribe( |
37 | resultList => { | 37 | resultList => { |
38 | this.followers = resultList.data | 38 | this.followers = resultList.data |
39 | this.totalRecords = resultList.total | 39 | this.totalRecords = resultList.total |
40 | }, | 40 | }, |
41 | 41 | ||
42 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 42 | err => this.notifier.error(err.message) |
43 | ) | 43 | ) |
44 | } | 44 | } |
45 | } | 45 | } |
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.ts b/client/src/app/+admin/follows/following-add/following-add.component.ts index bd9cc022b..2bb249746 100644 --- a/client/src/app/+admin/follows/following-add/following-add.component.ts +++ b/client/src/app/+admin/follows/following-add/following-add.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { ConfirmService } from '../../../core' | 4 | import { ConfirmService } from '../../../core' |
5 | import { validateHost } from '../../../shared' | 5 | import { validateHost } from '../../../shared' |
6 | import { FollowService } from '../shared' | 6 | import { FollowService } from '../shared' |
@@ -18,7 +18,7 @@ export class FollowingAddComponent { | |||
18 | 18 | ||
19 | constructor ( | 19 | constructor ( |
20 | private router: Router, | 20 | private router: Router, |
21 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
22 | private confirmService: ConfirmService, | 22 | private confirmService: ConfirmService, |
23 | private followService: FollowService, | 23 | private followService: FollowService, |
24 | private i18n: I18n | 24 | private i18n: I18n |
@@ -64,12 +64,12 @@ export class FollowingAddComponent { | |||
64 | 64 | ||
65 | this.followService.follow(hosts).subscribe( | 65 | this.followService.follow(hosts).subscribe( |
66 | () => { | 66 | () => { |
67 | this.notificationsService.success(this.i18n('Success'), this.i18n('Follow request(s) sent!')) | 67 | this.notifier.success(this.i18n('Follow request(s) sent!')) |
68 | 68 | ||
69 | setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500) | 69 | setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500) |
70 | }, | 70 | }, |
71 | 71 | ||
72 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 72 | err => this.notifier.error(err.message) |
73 | ) | 73 | ) |
74 | } | 74 | } |
75 | 75 | ||
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 8af624ac5..5bc8fbc2d 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 | |||
@@ -2,6 +2,17 @@ | |||
2 | [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 2 | [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" |
4 | > | 4 | > |
5 | <ng-template pTemplate="caption"> | ||
6 | <div class="caption"> | ||
7 | <div> | ||
8 | <input | ||
9 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | ||
10 | (keyup)="onSearch($event.target.value)" | ||
11 | > | ||
12 | </div> | ||
13 | </div> | ||
14 | </ng-template> | ||
15 | |||
5 | <ng-template pTemplate="header"> | 16 | <ng-template pTemplate="header"> |
6 | <tr> | 17 | <tr> |
7 | <th i18n style="width: 60px">ID</th> | 18 | <th i18n style="width: 60px">ID</th> |
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.scss b/client/src/app/+admin/follows/following-list/following-list.component.scss index bfcdcaa49..a6f0656b8 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.scss +++ b/client/src/app/+admin/follows/following-list/following-list.component.scss | |||
@@ -1,13 +1,10 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | my-redundancy-checkbox /deep/ my-peertube-checkbox { | 4 | .caption { |
5 | .form-group { | 5 | justify-content: flex-end; |
6 | margin-bottom: 0; | ||
7 | align-items: center; | ||
8 | } | ||
9 | 6 | ||
10 | label { | 7 | input { |
11 | margin: 0; | 8 | @include peertube-input-text(250px); |
12 | } | 9 | } |
13 | } \ No newline at end of file | 10 | } \ No newline at end of file |
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts index dd57884c6..4517a721e 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.ts +++ b/client/src/app/+admin/follows/following-list/following-list.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { SortMeta } from 'primeng/primeng' | 3 | import { SortMeta } from 'primeng/primeng' |
4 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' | 4 | import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' |
5 | import { ConfirmService } from '../../../core/confirm/confirm.service' | 5 | import { ConfirmService } from '../../../core/confirm/confirm.service' |
@@ -20,7 +20,7 @@ export class FollowingListComponent extends RestTable implements OnInit { | |||
20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
21 | 21 | ||
22 | constructor ( | 22 | constructor ( |
23 | private notificationsService: NotificationsService, | 23 | private notifier: Notifier, |
24 | private confirmService: ConfirmService, | 24 | private confirmService: ConfirmService, |
25 | private followService: FollowService, | 25 | private followService: FollowService, |
26 | private i18n: I18n | 26 | private i18n: I18n |
@@ -29,7 +29,7 @@ export class FollowingListComponent extends RestTable implements OnInit { | |||
29 | } | 29 | } |
30 | 30 | ||
31 | ngOnInit () { | 31 | ngOnInit () { |
32 | this.loadSort() | 32 | this.initialize() |
33 | } | 33 | } |
34 | 34 | ||
35 | async removeFollowing (follow: ActorFollow) { | 35 | async removeFollowing (follow: ActorFollow) { |
@@ -41,26 +41,23 @@ export class FollowingListComponent extends RestTable implements OnInit { | |||
41 | 41 | ||
42 | this.followService.unfollow(follow).subscribe( | 42 | this.followService.unfollow(follow).subscribe( |
43 | () => { | 43 | () => { |
44 | this.notificationsService.success( | 44 | this.notifier.success(this.i18n('You are not following {{host}} anymore.', { host: follow.following.host })) |
45 | this.i18n('Success'), | ||
46 | this.i18n('You are not following {{host}} anymore.', { host: follow.following.host }) | ||
47 | ) | ||
48 | this.loadData() | 45 | this.loadData() |
49 | }, | 46 | }, |
50 | 47 | ||
51 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 48 | err => this.notifier.error(err.message) |
52 | ) | 49 | ) |
53 | } | 50 | } |
54 | 51 | ||
55 | protected loadData () { | 52 | protected loadData () { |
56 | this.followService.getFollowing(this.pagination, this.sort) | 53 | this.followService.getFollowing(this.pagination, this.sort, this.search) |
57 | .subscribe( | 54 | .subscribe( |
58 | resultList => { | 55 | resultList => { |
59 | this.following = resultList.data | 56 | this.following = resultList.data |
60 | this.totalRecords = resultList.total | 57 | this.totalRecords = resultList.total |
61 | }, | 58 | }, |
62 | 59 | ||
63 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 60 | err => this.notifier.error(err.message) |
64 | ) | 61 | ) |
65 | } | 62 | } |
66 | } | 63 | } |
diff --git a/client/src/app/+admin/follows/shared/follow.service.ts b/client/src/app/+admin/follows/shared/follow.service.ts index 27169a9cd..a2904179e 100644 --- a/client/src/app/+admin/follows/shared/follow.service.ts +++ b/client/src/app/+admin/follows/shared/follow.service.ts | |||
@@ -18,10 +18,12 @@ export class FollowService { | |||
18 | ) { | 18 | ) { |
19 | } | 19 | } |
20 | 20 | ||
21 | getFollowing (pagination: RestPagination, sort: SortMeta): Observable<ResultList<ActorFollow>> { | 21 | getFollowing (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> { |
22 | let params = new HttpParams() | 22 | let params = new HttpParams() |
23 | params = this.restService.addRestGetParams(params, pagination, sort) | 23 | params = this.restService.addRestGetParams(params, pagination, sort) |
24 | 24 | ||
25 | if (search) params = params.append('search', search) | ||
26 | |||
25 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params }) | 27 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params }) |
26 | .pipe( | 28 | .pipe( |
27 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | 29 | map(res => this.restExtractor.convertResultListDateToHuman(res)), |
@@ -29,10 +31,12 @@ export class FollowService { | |||
29 | ) | 31 | ) |
30 | } | 32 | } |
31 | 33 | ||
32 | getFollowers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<ActorFollow>> { | 34 | getFollowers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> { |
33 | let params = new HttpParams() | 35 | let params = new HttpParams() |
34 | params = this.restService.addRestGetParams(params, pagination, sort) | 36 | params = this.restService.addRestGetParams(params, pagination, sort) |
35 | 37 | ||
38 | if (search) params = params.append('search', search) | ||
39 | |||
36 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params }) | 40 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params }) |
37 | .pipe( | 41 | .pipe( |
38 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | 42 | map(res => this.restExtractor.convertResultListDateToHuman(res)), |
diff --git a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts index 6d77a0eb4..fa1da26bf 100644 --- a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts +++ b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' | 4 | import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' |
5 | 5 | ||
@@ -13,24 +13,21 @@ export class RedundancyCheckboxComponent { | |||
13 | @Input() host: string | 13 | @Input() host: string |
14 | 14 | ||
15 | constructor ( | 15 | constructor ( |
16 | private notificationsService: NotificationsService, | 16 | private notifier: Notifier, |
17 | private redundancyService: RedundancyService, | 17 | private redundancyService: RedundancyService, |
18 | private i18n: I18n | 18 | private i18n: I18n |
19 | ) { } | 19 | ) { } |
20 | 20 | ||
21 | updateRedundancyState () { | 21 | updateRedundancyState () { |
22 | this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed) | 22 | this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed) |
23 | .subscribe( | 23 | .subscribe( |
24 | () => { | 24 | () => { |
25 | const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled') | 25 | const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled') |
26 | 26 | ||
27 | this.notificationsService.success( | 27 | this.notifier.success(this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel })) |
28 | this.i18n('Success'), | 28 | }, |
29 | this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel }) | ||
30 | ) | ||
31 | }, | ||
32 | 29 | ||
33 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 30 | err => this.notifier.error(err.message) |
34 | ) | 31 | ) |
35 | } | 32 | } |
36 | } | 33 | } |
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts index 866ba1b23..b265e1dd6 100644 --- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts +++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 2 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { SortMeta } from 'primeng/primeng' | 4 | import { SortMeta } from 'primeng/primeng' |
5 | import { Job } from '../../../../../../shared/index' | 5 | import { Job } from '../../../../../../shared/index' |
6 | import { JobState } from '../../../../../../shared/models' | 6 | import { JobState } from '../../../../../../shared/models' |
@@ -25,7 +25,7 @@ export class JobsListComponent extends RestTable implements OnInit { | |||
25 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 25 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
26 | 26 | ||
27 | constructor ( | 27 | constructor ( |
28 | private notificationsService: NotificationsService, | 28 | private notifier: Notifier, |
29 | private jobsService: JobService, | 29 | private jobsService: JobService, |
30 | private i18n: I18n | 30 | private i18n: I18n |
31 | ) { | 31 | ) { |
@@ -34,7 +34,7 @@ export class JobsListComponent extends RestTable implements OnInit { | |||
34 | 34 | ||
35 | ngOnInit () { | 35 | ngOnInit () { |
36 | this.loadJobState() | 36 | this.loadJobState() |
37 | this.loadSort() | 37 | this.initialize() |
38 | } | 38 | } |
39 | 39 | ||
40 | onJobStateChanged () { | 40 | onJobStateChanged () { |
@@ -53,7 +53,7 @@ export class JobsListComponent extends RestTable implements OnInit { | |||
53 | this.totalRecords = resultList.total | 53 | this.totalRecords = resultList.total |
54 | }, | 54 | }, |
55 | 55 | ||
56 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 56 | err => this.notifier.error(err.message) |
57 | ) | 57 | ) |
58 | } | 58 | } |
59 | 59 | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/index.ts b/client/src/app/+admin/moderation/instance-blocklist/index.ts new file mode 100644 index 000000000..3e7a344bb --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './instance-account-blocklist.component' | ||
2 | export * from './instance-server-blocklist.component' | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html new file mode 100644 index 000000000..7797bc56e --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html | |||
@@ -0,0 +1,22 @@ | |||
1 | <p-table | ||
2 | [value]="blockedAccounts" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | ||
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" | ||
4 | > | ||
5 | |||
6 | <ng-template pTemplate="header"> | ||
7 | <tr> | ||
8 | <th i18n>Account</th> | ||
9 | <th i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th> | ||
10 | </tr> | ||
11 | </ng-template> | ||
12 | |||
13 | <ng-template pTemplate="body" let-accountBlock> | ||
14 | <tr> | ||
15 | <td>{{ accountBlock.blockedAccount.nameWithHost }}</td> | ||
16 | <td>{{ accountBlock.createdAt }}</td> | ||
17 | <td class="action-cell"> | ||
18 | <button class="unblock-button" (click)="unblockAccount(accountBlock)" i18n>Unmute</button> | ||
19 | </td> | ||
20 | </tr> | ||
21 | </ng-template> | ||
22 | </p-table> | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss new file mode 100644 index 000000000..6028b75ea --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss | |||
@@ -0,0 +1,7 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .unblock-button { | ||
5 | @include peertube-button; | ||
6 | @include grey-button; | ||
7 | } \ No newline at end of file | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts new file mode 100644 index 000000000..032bf745a --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts | |||
@@ -0,0 +1,58 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { RestPagination, RestTable } from '@app/shared' | ||
5 | import { SortMeta } from 'primeng/components/common/sortmeta' | ||
6 | import { AccountBlock, BlocklistService } from '@app/shared/blocklist' | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-instance-account-blocklist', | ||
10 | styleUrls: [ './instance-account-blocklist.component.scss' ], | ||
11 | templateUrl: './instance-account-blocklist.component.html' | ||
12 | }) | ||
13 | export class InstanceAccountBlocklistComponent extends RestTable implements OnInit { | ||
14 | blockedAccounts: AccountBlock[] = [] | ||
15 | totalRecords = 0 | ||
16 | rowsPerPage = 10 | ||
17 | sort: SortMeta = { field: 'createdAt', order: -1 } | ||
18 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | ||
19 | |||
20 | constructor ( | ||
21 | private notifier: Notifier, | ||
22 | private blocklistService: BlocklistService, | ||
23 | private i18n: I18n | ||
24 | ) { | ||
25 | super() | ||
26 | } | ||
27 | |||
28 | ngOnInit () { | ||
29 | this.initialize() | ||
30 | } | ||
31 | |||
32 | unblockAccount (accountBlock: AccountBlock) { | ||
33 | const blockedAccount = accountBlock.blockedAccount | ||
34 | |||
35 | this.blocklistService.unblockAccountByInstance(blockedAccount) | ||
36 | .subscribe( | ||
37 | () => { | ||
38 | this.notifier.success( | ||
39 | this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost }) | ||
40 | ) | ||
41 | |||
42 | this.loadData() | ||
43 | } | ||
44 | ) | ||
45 | } | ||
46 | |||
47 | protected loadData () { | ||
48 | return this.blocklistService.getInstanceAccountBlocklist(this.pagination, this.sort) | ||
49 | .subscribe( | ||
50 | resultList => { | ||
51 | this.blockedAccounts = resultList.data | ||
52 | this.totalRecords = resultList.total | ||
53 | }, | ||
54 | |||
55 | err => this.notifier.error(err.message) | ||
56 | ) | ||
57 | } | ||
58 | } | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html new file mode 100644 index 000000000..f634ba834 --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html | |||
@@ -0,0 +1,23 @@ | |||
1 | <p-table | ||
2 | [value]="blockedServers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | ||
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" | ||
4 | > | ||
5 | |||
6 | <ng-template pTemplate="header"> | ||
7 | <tr> | ||
8 | <th i18n>Instance</th> | ||
9 | <th i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th> | ||
10 | <th></th> | ||
11 | </tr> | ||
12 | </ng-template> | ||
13 | |||
14 | <ng-template pTemplate="body" let-serverBlock> | ||
15 | <tr> | ||
16 | <td>{{ serverBlock.blockedServer.host }}</td> | ||
17 | <td>{{ serverBlock.createdAt }}</td> | ||
18 | <td class="action-cell"> | ||
19 | <button class="unblock-button" (click)="unblockServer(serverBlock)" i18n>Unmute</button> | ||
20 | </td> | ||
21 | </tr> | ||
22 | </ng-template> | ||
23 | </p-table> | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss new file mode 100644 index 000000000..6028b75ea --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss | |||
@@ -0,0 +1,7 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .unblock-button { | ||
5 | @include peertube-button; | ||
6 | @include grey-button; | ||
7 | } \ No newline at end of file | ||
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts new file mode 100644 index 000000000..db3dfcd1c --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts | |||
@@ -0,0 +1,57 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { RestPagination, RestTable } from '@app/shared' | ||
5 | import { SortMeta } from 'primeng/components/common/sortmeta' | ||
6 | import { BlocklistService } from '@app/shared/blocklist' | ||
7 | import { ServerBlock } from '../../../../../../shared' | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-instance-server-blocklist', | ||
11 | styleUrls: [ './instance-server-blocklist.component.scss' ], | ||
12 | templateUrl: './instance-server-blocklist.component.html' | ||
13 | }) | ||
14 | export class InstanceServerBlocklistComponent extends RestTable implements OnInit { | ||
15 | blockedServers: ServerBlock[] = [] | ||
16 | totalRecords = 0 | ||
17 | rowsPerPage = 10 | ||
18 | sort: SortMeta = { field: 'createdAt', order: -1 } | ||
19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | ||
20 | |||
21 | constructor ( | ||
22 | private notifier: Notifier, | ||
23 | private blocklistService: BlocklistService, | ||
24 | private i18n: I18n | ||
25 | ) { | ||
26 | super() | ||
27 | } | ||
28 | |||
29 | ngOnInit () { | ||
30 | this.initialize() | ||
31 | } | ||
32 | |||
33 | unblockServer (serverBlock: ServerBlock) { | ||
34 | const host = serverBlock.blockedServer.host | ||
35 | |||
36 | this.blocklistService.unblockServerByInstance(host) | ||
37 | .subscribe( | ||
38 | () => { | ||
39 | this.notifier.success(this.i18n('Instance {{host}} unmuted by your instance.', { host })) | ||
40 | |||
41 | this.loadData() | ||
42 | } | ||
43 | ) | ||
44 | } | ||
45 | |||
46 | protected loadData () { | ||
47 | return this.blocklistService.getInstanceServerBlocklist(this.pagination, this.sort) | ||
48 | .subscribe( | ||
49 | resultList => { | ||
50 | this.blockedServers = resultList.data | ||
51 | this.totalRecords = resultList.total | ||
52 | }, | ||
53 | |||
54 | err => this.notifier.error(err.message) | ||
55 | ) | ||
56 | } | ||
57 | } | ||
diff --git a/client/src/app/+admin/moderation/moderation.component.html b/client/src/app/+admin/moderation/moderation.component.html index 91e87fcd4..01457936c 100644 --- a/client/src/app/+admin/moderation/moderation.component.html +++ b/client/src/app/+admin/moderation/moderation.component.html | |||
@@ -5,6 +5,10 @@ | |||
5 | <a *ngIf="hasVideoAbusesRight()" i18n routerLink="video-abuses/list" routerLinkActive="active">Video abuses</a> | 5 | <a *ngIf="hasVideoAbusesRight()" i18n routerLink="video-abuses/list" routerLinkActive="active">Video abuses</a> |
6 | 6 | ||
7 | <a *ngIf="hasVideoBlacklistRight()" i18n routerLink="video-blacklist/list" routerLinkActive="active">Blacklisted videos</a> | 7 | <a *ngIf="hasVideoBlacklistRight()" i18n routerLink="video-blacklist/list" routerLinkActive="active">Blacklisted videos</a> |
8 | |||
9 | <a *ngIf="hasAccountsBlocklistRight()" i18n routerLink="blocklist/accounts" routerLinkActive="active">Muted accounts</a> | ||
10 | |||
11 | <a *ngIf="hasServersBlocklistRight()" i18n routerLink="blocklist/servers" routerLinkActive="active">Muted servers</a> | ||
8 | </div> | 12 | </div> |
9 | </div> | 13 | </div> |
10 | 14 | ||
diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index 02ccfc8ca..13b019c5b 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss | |||
@@ -10,6 +10,7 @@ | |||
10 | font-weight: $font-semibold; | 10 | font-weight: $font-semibold; |
11 | min-width: 200px; | 11 | min-width: 200px; |
12 | display: inline-block; | 12 | display: inline-block; |
13 | vertical-align: top; | ||
13 | } | 14 | } |
14 | 15 | ||
15 | .moderation-expanded-text { | 16 | .moderation-expanded-text { |
diff --git a/client/src/app/+admin/moderation/moderation.component.ts b/client/src/app/+admin/moderation/moderation.component.ts index 0f4efb970..2b2618933 100644 --- a/client/src/app/+admin/moderation/moderation.component.ts +++ b/client/src/app/+admin/moderation/moderation.component.ts | |||
@@ -16,4 +16,12 @@ export class ModerationComponent { | |||
16 | hasVideoBlacklistRight () { | 16 | hasVideoBlacklistRight () { |
17 | return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) | 17 | return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) |
18 | } | 18 | } |
19 | |||
20 | hasAccountsBlocklistRight () { | ||
21 | return this.auth.getUser().hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST) | ||
22 | } | ||
23 | |||
24 | hasServersBlocklistRight () { | ||
25 | return this.auth.getUser().hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST) | ||
26 | } | ||
19 | } | 27 | } |
diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts index 6d81b9b36..bc6dd49d5 100644 --- a/client/src/app/+admin/moderation/moderation.routes.ts +++ b/client/src/app/+admin/moderation/moderation.routes.ts | |||
@@ -4,6 +4,7 @@ import { UserRightGuard } from '@app/core' | |||
4 | import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list' | 4 | import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list' |
5 | import { VideoBlacklistListComponent } from '@app/+admin/moderation/video-blacklist-list' | 5 | import { VideoBlacklistListComponent } from '@app/+admin/moderation/video-blacklist-list' |
6 | import { ModerationComponent } from '@app/+admin/moderation/moderation.component' | 6 | import { ModerationComponent } from '@app/+admin/moderation/moderation.component' |
7 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' | ||
7 | 8 | ||
8 | export const ModerationRoutes: Routes = [ | 9 | export const ModerationRoutes: Routes = [ |
9 | { | 10 | { |
@@ -46,6 +47,28 @@ export const ModerationRoutes: Routes = [ | |||
46 | title: 'Blacklisted videos' | 47 | title: 'Blacklisted videos' |
47 | } | 48 | } |
48 | } | 49 | } |
50 | }, | ||
51 | { | ||
52 | path: 'blocklist/accounts', | ||
53 | component: InstanceAccountBlocklistComponent, | ||
54 | canActivate: [ UserRightGuard ], | ||
55 | data: { | ||
56 | userRight: UserRight.MANAGE_ACCOUNTS_BLOCKLIST, | ||
57 | meta: { | ||
58 | title: 'Muted accounts' | ||
59 | } | ||
60 | } | ||
61 | }, | ||
62 | { | ||
63 | path: 'blocklist/servers', | ||
64 | component: InstanceServerBlocklistComponent, | ||
65 | canActivate: [ UserRightGuard ], | ||
66 | data: { | ||
67 | userRight: UserRight.MANAGE_SERVER_REDUNDANCY, | ||
68 | meta: { | ||
69 | title: 'Muted instances' | ||
70 | } | ||
71 | } | ||
49 | } | 72 | } |
50 | ] | 73 | ] |
51 | } | 74 | } |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html index 3a8424f68..303a788d2 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html | |||
@@ -1,7 +1,8 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Moderation comment</h4> | 3 | <h4 i18n class="modal-title">Moderation comment</h4> |
4 | <span class="close" aria-hidden="true" (click)="hideModerationCommentModal()"></span> | 4 | |
5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | 6 | </div> |
6 | 7 | ||
7 | <div class="modal-body"> | 8 | <div class="modal-body"> |
@@ -14,12 +15,12 @@ | |||
14 | </div> | 15 | </div> |
15 | </div> | 16 | </div> |
16 | 17 | ||
17 | <div i18n> | 18 | <div class="form-group" i18n> |
18 | This comment can only be seen by you or the other moderators. | 19 | This comment can only be seen by you or the other moderators. |
19 | </div> | 20 | </div> |
20 | 21 | ||
21 | <div class="form-group inputs"> | 22 | <div class="form-group inputs"> |
22 | <span i18n class="action-button action-button-cancel" (click)="hideModerationCommentModal()">Cancel</span> | 23 | <span i18n class="action-button action-button-cancel" (click)="hide()">Cancel</span> |
23 | 24 | ||
24 | <input | 25 | <input |
25 | type="submit" i18n-value value="Update this comment" class="action-button-submit" | 26 | type="submit" i18n-value value="Update this comment" class="action-button-submit" |
@@ -29,4 +30,4 @@ | |||
29 | </form> | 30 | </form> |
30 | </div> | 31 | </div> |
31 | 32 | ||
32 | </ng-template> \ No newline at end of file | 33 | </ng-template> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts index 34ab384d1..f915978ee 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared' | 3 | import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
@@ -22,7 +22,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
22 | constructor ( | 22 | constructor ( |
23 | protected formValidatorService: FormValidatorService, | 23 | protected formValidatorService: FormValidatorService, |
24 | private modalService: NgbModal, | 24 | private modalService: NgbModal, |
25 | private notificationsService: NotificationsService, | 25 | private notifier: Notifier, |
26 | private videoAbuseService: VideoAbuseService, | 26 | private videoAbuseService: VideoAbuseService, |
27 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, | 27 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, |
28 | private i18n: I18n | 28 | private i18n: I18n |
@@ -45,29 +45,26 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
45 | }) | 45 | }) |
46 | } | 46 | } |
47 | 47 | ||
48 | hideModerationCommentModal () { | 48 | hide () { |
49 | this.abuseToComment = undefined | 49 | this.abuseToComment = undefined |
50 | this.openedModal.close() | 50 | this.openedModal.close() |
51 | this.form.reset() | 51 | this.form.reset() |
52 | } | 52 | } |
53 | 53 | ||
54 | async banUser () { | 54 | async banUser () { |
55 | const moderationComment: string = this.form.value['moderationComment'] | 55 | const moderationComment: string = this.form.value[ 'moderationComment' ] |
56 | 56 | ||
57 | this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) | 57 | this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) |
58 | .subscribe( | 58 | .subscribe( |
59 | () => { | 59 | () => { |
60 | this.notificationsService.success( | 60 | this.notifier.success(this.i18n('Comment updated.')) |
61 | this.i18n('Success'), | ||
62 | this.i18n('Comment updated.') | ||
63 | ) | ||
64 | 61 | ||
65 | this.commentUpdated.emit(moderationComment) | 62 | this.commentUpdated.emit(moderationComment) |
66 | this.hideModerationCommentModal() | 63 | this.hide() |
67 | }, | 64 | }, |
68 | 65 | ||
69 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 66 | err => this.notifier.error(err.message) |
70 | ) | 67 | ) |
71 | } | 68 | } |
72 | 69 | ||
73 | } | 70 | } |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html index 287ab3e46..05b549de6 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> | 9 | <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> |
10 | <th i18n>Video</th> | 10 | <th i18n>Video</th> |
11 | <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th> | 11 | <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th> |
12 | <th style="width: 50px;"></th> | 12 | <th style="width: 120px;"></th> |
13 | </tr> | 13 | </tr> |
14 | </ng-template> | 14 | </ng-template> |
15 | 15 | ||
@@ -41,7 +41,7 @@ | |||
41 | </td> | 41 | </td> |
42 | 42 | ||
43 | <td class="action-cell"> | 43 | <td class="action-cell"> |
44 | <my-action-dropdown i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown> | 44 | <my-action-dropdown placement="bottom-right" i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown> |
45 | </td> | 45 | </td> |
46 | </tr> | 46 | </tr> |
47 | </ng-template> | 47 | </ng-template> |
@@ -51,15 +51,15 @@ | |||
51 | <td class="moderation-expanded" colspan="6"> | 51 | <td class="moderation-expanded" colspan="6"> |
52 | <div> | 52 | <div> |
53 | <span i18n class="moderation-expanded-label">Reason:</span> | 53 | <span i18n class="moderation-expanded-label">Reason:</span> |
54 | <span class="moderation-expanded-text">{{ videoAbuse.reason }}</span> | 54 | <span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.reason)"></span> |
55 | </div> | 55 | </div> |
56 | <div *ngIf="videoAbuse.moderationComment"> | 56 | <div *ngIf="videoAbuse.moderationComment"> |
57 | <span i18n class="moderation-expanded-label">Moderation comment:</span> | 57 | <span i18n class="moderation-expanded-label">Moderation comment:</span> |
58 | <span class="moderation-expanded-text">{{ videoAbuse.moderationComment }}</span> | 58 | <span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.moderationComment)"></span> |
59 | </div> | 59 | </div> |
60 | </td> | 60 | </td> |
61 | </tr> | 61 | </tr> |
62 | </ng-template> | 62 | </ng-template> |
63 | </p-table> | 63 | </p-table> |
64 | 64 | ||
65 | <my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal> \ No newline at end of file | 65 | <my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index 681db7434..00c871659 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { Account } from '../../../shared/account/account.model' | 2 | import { Account } from '../../../shared/account/account.model' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { SortMeta } from 'primeng/components/common/sortmeta' | 4 | import { SortMeta } from 'primeng/components/common/sortmeta' |
5 | import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' | 5 | import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' |
6 | import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' | 6 | import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' |
@@ -9,6 +9,7 @@ import { DropdownAction } from '../../../shared/buttons/action-dropdown.componen | |||
9 | import { ConfirmService } from '../../../core/index' | 9 | import { ConfirmService } from '../../../core/index' |
10 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' | 10 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' |
11 | import { Video } from '../../../shared/video/video.model' | 11 | import { Video } from '../../../shared/video/video.model' |
12 | import { MarkdownService } from '@app/shared/renderer' | ||
12 | 13 | ||
13 | @Component({ | 14 | @Component({ |
14 | selector: 'my-video-abuse-list', | 15 | selector: 'my-video-abuse-list', |
@@ -27,16 +28,17 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
27 | videoAbuseActions: DropdownAction<VideoAbuse>[] = [] | 28 | videoAbuseActions: DropdownAction<VideoAbuse>[] = [] |
28 | 29 | ||
29 | constructor ( | 30 | constructor ( |
30 | private notificationsService: NotificationsService, | 31 | private notifier: Notifier, |
31 | private videoAbuseService: VideoAbuseService, | 32 | private videoAbuseService: VideoAbuseService, |
32 | private confirmService: ConfirmService, | 33 | private confirmService: ConfirmService, |
33 | private i18n: I18n | 34 | private i18n: I18n, |
35 | private markdownRenderer: MarkdownService | ||
34 | ) { | 36 | ) { |
35 | super() | 37 | super() |
36 | 38 | ||
37 | this.videoAbuseActions = [ | 39 | this.videoAbuseActions = [ |
38 | { | 40 | { |
39 | label: this.i18n('Delete'), | 41 | label: this.i18n('Delete this report'), |
40 | handler: videoAbuse => this.removeVideoAbuse(videoAbuse) | 42 | handler: videoAbuse => this.removeVideoAbuse(videoAbuse) |
41 | }, | 43 | }, |
42 | { | 44 | { |
@@ -57,7 +59,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
57 | } | 59 | } |
58 | 60 | ||
59 | ngOnInit () { | 61 | ngOnInit () { |
60 | this.loadSort() | 62 | this.initialize() |
61 | } | 63 | } |
62 | 64 | ||
63 | openModerationCommentModal (videoAbuse: VideoAbuse) { | 65 | openModerationCommentModal (videoAbuse: VideoAbuse) { |
@@ -85,19 +87,16 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
85 | } | 87 | } |
86 | 88 | ||
87 | async removeVideoAbuse (videoAbuse: VideoAbuse) { | 89 | async removeVideoAbuse (videoAbuse: VideoAbuse) { |
88 | const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse?'), this.i18n('Delete')) | 90 | const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete')) |
89 | if (res === false) return | 91 | if (res === false) return |
90 | 92 | ||
91 | this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( | 93 | this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( |
92 | () => { | 94 | () => { |
93 | this.notificationsService.success( | 95 | this.notifier.success(this.i18n('Abuse deleted.')) |
94 | this.i18n('Success'), | ||
95 | this.i18n('Abuse deleted.') | ||
96 | ) | ||
97 | this.loadData() | 96 | this.loadData() |
98 | }, | 97 | }, |
99 | 98 | ||
100 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 99 | err => this.notifier.error(err.message) |
101 | ) | 100 | ) |
102 | } | 101 | } |
103 | 102 | ||
@@ -106,11 +105,15 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
106 | .subscribe( | 105 | .subscribe( |
107 | () => this.loadData(), | 106 | () => this.loadData(), |
108 | 107 | ||
109 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 108 | err => this.notifier.error(err.message) |
110 | ) | 109 | ) |
111 | 110 | ||
112 | } | 111 | } |
113 | 112 | ||
113 | toHtml (text: string) { | ||
114 | return this.markdownRenderer.textMarkdownToHTML(text) | ||
115 | } | ||
116 | |||
114 | protected loadData () { | 117 | protected loadData () { |
115 | return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) | 118 | return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) |
116 | .subscribe( | 119 | .subscribe( |
@@ -119,7 +122,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { | |||
119 | this.totalRecords = resultList.total | 122 | this.totalRecords = resultList.total |
120 | }, | 123 | }, |
121 | 124 | ||
122 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 125 | err => this.notifier.error(err.message) |
123 | ) | 126 | ) |
124 | } | 127 | } |
125 | } | 128 | } |
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html index 0585e0490..247f441c1 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html | |||
@@ -7,8 +7,9 @@ | |||
7 | <th style="width: 40px"></th> | 7 | <th style="width: 40px"></th> |
8 | <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th> | 8 | <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th> |
9 | <th i18n>Sensitive</th> | 9 | <th i18n>Sensitive</th> |
10 | <th i18n>Unfederated</th> | ||
10 | <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> | 11 | <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> |
11 | <th style="width: 50px;"></th> | 12 | <th style="width: 120px;"></th> |
12 | </tr> | 13 | </tr> |
13 | </ng-template> | 14 | </ng-template> |
14 | 15 | ||
@@ -26,20 +27,21 @@ | |||
26 | </a> | 27 | </a> |
27 | </td> | 28 | </td> |
28 | 29 | ||
29 | <td>{{ videoBlacklist.video.nsfw }}</td> | 30 | <td>{{ booleanToText(videoBlacklist.video.nsfw) }}</td> |
31 | <td>{{ booleanToText(videoBlacklist.unfederated) }}</td> | ||
30 | <td>{{ videoBlacklist.createdAt }}</td> | 32 | <td>{{ videoBlacklist.createdAt }}</td> |
31 | 33 | ||
32 | <td class="action-cell"> | 34 | <td class="action-cell"> |
33 | <my-action-dropdown i18n-label label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown> | 35 | <my-action-dropdown i18n-label placement="bottom-right" label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown> |
34 | </td> | 36 | </td> |
35 | </tr> | 37 | </tr> |
36 | </ng-template> | 38 | </ng-template> |
37 | 39 | ||
38 | <ng-template pTemplate="rowexpansion" let-videoBlacklist> | 40 | <ng-template pTemplate="rowexpansion" let-videoBlacklist> |
39 | <tr> | 41 | <tr> |
40 | <td class="moderation-expanded" colspan="5"> | 42 | <td class="moderation-expanded" colspan="6"> |
41 | <span i18n class="moderation-expanded-label">Blacklist reason:</span> | 43 | <span i18n class="moderation-expanded-label">Blacklist reason:</span> |
42 | <span class="moderation-expanded-text">{{ videoBlacklist.reason }}</span> | 44 | <span class="moderation-expanded-text" [innerHTML]="toHtml(videoBlacklist.reason)"></span> |
43 | </td> | 45 | </td> |
44 | </tr> | 46 | </tr> |
45 | </ng-template> | 47 | </ng-template> |
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts index bb051d00f..b27bbbfef 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { SortMeta } from 'primeng/components/common/sortmeta' | 2 | import { SortMeta } from 'primeng/components/common/sortmeta' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { ConfirmService } from '../../../core' | 4 | import { ConfirmService } from '../../../core' |
5 | import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared' | 5 | import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared' |
6 | import { VideoBlacklist } from '../../../../../../shared' | 6 | import { VideoBlacklist } from '../../../../../../shared' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { DropdownAction } from '../../../shared/buttons/action-dropdown.component' | 8 | import { DropdownAction } from '../../../shared/buttons/action-dropdown.component' |
9 | import { Video } from '../../../shared/video/video.model' | 9 | import { Video } from '../../../shared/video/video.model' |
10 | import { MarkdownService } from '@app/shared/renderer' | ||
10 | 11 | ||
11 | @Component({ | 12 | @Component({ |
12 | selector: 'my-video-blacklist-list', | 13 | selector: 'my-video-blacklist-list', |
@@ -23,9 +24,10 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
23 | videoBlacklistActions: DropdownAction<VideoBlacklist>[] = [] | 24 | videoBlacklistActions: DropdownAction<VideoBlacklist>[] = [] |
24 | 25 | ||
25 | constructor ( | 26 | constructor ( |
26 | private notificationsService: NotificationsService, | 27 | private notifier: Notifier, |
27 | private confirmService: ConfirmService, | 28 | private confirmService: ConfirmService, |
28 | private videoBlacklistService: VideoBlacklistService, | 29 | private videoBlacklistService: VideoBlacklistService, |
30 | private markdownRenderer: MarkdownService, | ||
29 | private i18n: I18n | 31 | private i18n: I18n |
30 | ) { | 32 | ) { |
31 | super() | 33 | super() |
@@ -39,13 +41,23 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
39 | } | 41 | } |
40 | 42 | ||
41 | ngOnInit () { | 43 | ngOnInit () { |
42 | this.loadSort() | 44 | this.initialize() |
43 | } | 45 | } |
44 | 46 | ||
45 | getVideoUrl (videoBlacklist: VideoBlacklist) { | 47 | getVideoUrl (videoBlacklist: VideoBlacklist) { |
46 | return Video.buildClientUrl(videoBlacklist.video.uuid) | 48 | return Video.buildClientUrl(videoBlacklist.video.uuid) |
47 | } | 49 | } |
48 | 50 | ||
51 | booleanToText (value: boolean) { | ||
52 | if (value === true) return this.i18n('yes') | ||
53 | |||
54 | return this.i18n('no') | ||
55 | } | ||
56 | |||
57 | toHtml (text: string) { | ||
58 | return this.markdownRenderer.textMarkdownToHTML(text) | ||
59 | } | ||
60 | |||
49 | async removeVideoFromBlacklist (entry: VideoBlacklist) { | 61 | async removeVideoFromBlacklist (entry: VideoBlacklist) { |
50 | const confirmMessage = this.i18n( | 62 | const confirmMessage = this.i18n( |
51 | 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' | 63 | 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' |
@@ -56,14 +68,11 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
56 | 68 | ||
57 | this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe( | 69 | this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe( |
58 | () => { | 70 | () => { |
59 | this.notificationsService.success( | 71 | this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name })) |
60 | this.i18n('Success'), | ||
61 | this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name }) | ||
62 | ) | ||
63 | this.loadData() | 72 | this.loadData() |
64 | }, | 73 | }, |
65 | 74 | ||
66 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 75 | err => this.notifier.error(err.message) |
67 | ) | 76 | ) |
68 | } | 77 | } |
69 | 78 | ||
@@ -75,7 +84,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { | |||
75 | this.totalRecords = resultList.total | 84 | this.totalRecords = resultList.total |
76 | }, | 85 | }, |
77 | 86 | ||
78 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 87 | err => this.notifier.error(err.message) |
79 | ) | 88 | ) |
80 | } | 89 | } |
81 | } | 90 | } |
diff --git a/client/src/app/+admin/users/user-edit/index.ts b/client/src/app/+admin/users/user-edit/index.ts index fd80a02e0..ec734ef92 100644 --- a/client/src/app/+admin/users/user-edit/index.ts +++ b/client/src/app/+admin/users/user-edit/index.ts | |||
@@ -1,2 +1,3 @@ | |||
1 | export * from './user-create.component' | 1 | export * from './user-create.component' |
2 | export * from './user-update.component' | 2 | export * from './user-update.component' |
3 | export * from './user-password.component' | ||
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 dd8e4efd5..137ecfcbd 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 | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier, ServerService } from '@app/core' |
4 | import { ServerService } from '../../../core' | ||
5 | import { UserCreate, UserRole } from '../../../../../../shared' | 4 | import { UserCreate, UserRole } from '../../../../../../shared' |
6 | import { UserEdit } from './user-edit' | 5 | import { UserEdit } from './user-edit' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -24,7 +23,7 @@ export class UserCreateComponent extends UserEdit implements OnInit { | |||
24 | protected configService: ConfigService, | 23 | protected configService: ConfigService, |
25 | private userValidatorsService: UserValidatorsService, | 24 | private userValidatorsService: UserValidatorsService, |
26 | private router: Router, | 25 | private router: Router, |
27 | private notificationsService: NotificationsService, | 26 | private notifier: Notifier, |
28 | private userService: UserService, | 27 | private userService: UserService, |
29 | private i18n: I18n | 28 | private i18n: I18n |
30 | ) { | 29 | ) { |
@@ -60,10 +59,7 @@ export class UserCreateComponent extends UserEdit implements OnInit { | |||
60 | 59 | ||
61 | this.userService.addUser(userCreate).subscribe( | 60 | this.userService.addUser(userCreate).subscribe( |
62 | () => { | 61 | () => { |
63 | this.notificationsService.success( | 62 | this.notifier.success(this.i18n('User {{username}} created.', { username: userCreate.username })) |
64 | this.i18n('Success'), | ||
65 | this.i18n('User {{username}} created.', { username: userCreate.username }) | ||
66 | ) | ||
67 | this.router.navigate([ '/admin/users/list' ]) | 63 | this.router.navigate([ '/admin/users/list' ]) |
68 | }, | 64 | }, |
69 | 65 | ||
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 56cf7d17d..c6566da24 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 | |||
@@ -81,3 +81,17 @@ | |||
81 | 81 | ||
82 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> | 82 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> |
83 | </form> | 83 | </form> |
84 | |||
85 | <div *ngIf="!isCreation()" class="danger-zone"> | ||
86 | <div class="account-title" i18n>Danger Zone</div> | ||
87 | |||
88 | <div class="form-group reset-password-email"> | ||
89 | <label i18n>Send a link to reset the password by email to the user</label> | ||
90 | <button (click)="resetPassword()" i18n>Ask for new password</button> | ||
91 | </div> | ||
92 | |||
93 | <div class="form-group"> | ||
94 | <label i18n>Manually set the user password</label> | ||
95 | <my-user-password [userId]="userId"></my-user-password> | ||
96 | </div> | ||
97 | </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 6675f65cc..c1cc4ca45 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 | |||
@@ -14,7 +14,7 @@ input:not([type=submit]) { | |||
14 | @include peertube-select-container(340px); | 14 | @include peertube-select-container(340px); |
15 | } | 15 | } |
16 | 16 | ||
17 | input[type=submit] { | 17 | input[type=submit], button { |
18 | @include peertube-button; | 18 | @include peertube-button; |
19 | @include orange-button; | 19 | @include orange-button; |
20 | 20 | ||
@@ -25,3 +25,23 @@ input[type=submit] { | |||
25 | margin-top: 5px; | 25 | margin-top: 5px; |
26 | font-size: 11px; | 26 | font-size: 11px; |
27 | } | 27 | } |
28 | |||
29 | .account-title { | ||
30 | @include in-content-small-title; | ||
31 | |||
32 | margin-top: 55px; | ||
33 | margin-bottom: 30px; | ||
34 | } | ||
35 | |||
36 | .danger-zone { | ||
37 | .reset-password-email { | ||
38 | margin-bottom: 30px; | ||
39 | padding-bottom: 30px; | ||
40 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); | ||
41 | |||
42 | button { | ||
43 | display: block; | ||
44 | margin-top: 0; | ||
45 | } | ||
46 | } | ||
47 | } | ||
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 07b087b5b..649b35b0c 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.ts +++ b/client/src/app/+admin/users/user-edit/user-edit.ts | |||
@@ -1,14 +1,14 @@ | |||
1 | import { ServerService } from '../../../core' | 1 | import { ServerService } from '../../../core' |
2 | import { FormReactive } from '../../../shared' | 2 | import { FormReactive } from '../../../shared' |
3 | import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared' | 3 | import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared' |
4 | import { EditCustomConfigComponent } from '../../../+admin/config/edit-custom-config/' | ||
5 | import { ConfigService } from '@app/+admin/config/shared/config.service' | 4 | import { ConfigService } from '@app/+admin/config/shared/config.service' |
6 | 5 | ||
7 | export abstract class UserEdit extends FormReactive { | 6 | export abstract class UserEdit extends FormReactive { |
8 | |||
9 | videoQuotaOptions: { value: string, label: string }[] = [] | 7 | videoQuotaOptions: { value: string, label: string }[] = [] |
10 | videoQuotaDailyOptions: { value: string, label: string }[] = [] | 8 | videoQuotaDailyOptions: { value: string, label: string }[] = [] |
11 | roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] })) | 9 | roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] })) |
10 | username: string | ||
11 | userId: number | ||
12 | 12 | ||
13 | protected abstract serverService: ServerService | 13 | protected abstract serverService: ServerService |
14 | protected abstract configService: ConfigService | 14 | protected abstract configService: ConfigService |
@@ -23,7 +23,9 @@ export abstract class UserEdit extends FormReactive { | |||
23 | } | 23 | } |
24 | 24 | ||
25 | computeQuotaWithTranscoding () { | 25 | computeQuotaWithTranscoding () { |
26 | const resolutions = this.serverService.getConfig().transcoding.enabledResolutions | 26 | const transcodingConfig = this.serverService.getConfig().transcoding |
27 | |||
28 | const resolutions = transcodingConfig.enabledResolutions | ||
27 | const higherResolution = VideoResolution.H_1080P | 29 | const higherResolution = VideoResolution.H_1080P |
28 | let multiplier = 0 | 30 | let multiplier = 0 |
29 | 31 | ||
@@ -31,9 +33,15 @@ export abstract class UserEdit extends FormReactive { | |||
31 | multiplier += resolution / higherResolution | 33 | multiplier += resolution / higherResolution |
32 | } | 34 | } |
33 | 35 | ||
36 | if (transcodingConfig.hls.enabled) multiplier *= 2 | ||
37 | |||
34 | return multiplier * parseInt(this.form.value['videoQuota'], 10) | 38 | return multiplier * parseInt(this.form.value['videoQuota'], 10) |
35 | } | 39 | } |
36 | 40 | ||
41 | resetPassword () { | ||
42 | return | ||
43 | } | ||
44 | |||
37 | protected buildQuotaOptions () { | 45 | protected buildQuotaOptions () { |
38 | // These are used by a HTML select, so convert key into strings | 46 | // These are used by a HTML select, so convert key into strings |
39 | this.videoQuotaOptions = this.configService | 47 | this.videoQuotaOptions = this.configService |
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.html b/client/src/app/+admin/users/user-edit/user-password.component.html new file mode 100644 index 000000000..a1e1f6216 --- /dev/null +++ b/client/src/app/+admin/users/user-edit/user-password.component.html | |||
@@ -0,0 +1,21 @@ | |||
1 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> | ||
2 | <div class="form-group"> | ||
3 | |||
4 | <div class="input-group"> | ||
5 | <input id="password" [attr.type]="showPassword ? 'text' : '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> | ||
19 | |||
20 | <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> | ||
21 | </form> | ||
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.scss b/client/src/app/+admin/users/user-edit/user-password.component.scss new file mode 100644 index 000000000..217d585af --- /dev/null +++ b/client/src/app/+admin/users/user-edit/user-password.component.scss | |||
@@ -0,0 +1,22 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | input:not([type=submit]):not([type=checkbox]) { | ||
5 | @include peertube-input-text(340px); | ||
6 | |||
7 | display: block; | ||
8 | border-top-right-radius: 0; | ||
9 | border-bottom-right-radius: 0; | ||
10 | border-right: none; | ||
11 | } | ||
12 | |||
13 | input[type=submit] { | ||
14 | @include peertube-button; | ||
15 | @include orange-button; | ||
16 | |||
17 | margin-top: 10px; | ||
18 | } | ||
19 | |||
20 | .input-group-append { | ||
21 | height: 30px; | ||
22 | } | ||
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 new file mode 100644 index 000000000..5b3040440 --- /dev/null +++ b/client/src/app/+admin/users/user-edit/user-password.component.ts | |||
@@ -0,0 +1,64 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { UserService } from '@app/shared/users/user.service' | ||
4 | import { Notifier } from '../../../core' | ||
5 | import { User, UserUpdate } from '../../../../../../shared' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
8 | import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' | ||
9 | import { FormReactive } from '../../../shared' | ||
10 | |||
11 | @Component({ | ||
12 | selector: 'my-user-password', | ||
13 | templateUrl: './user-password.component.html', | ||
14 | styleUrls: [ './user-password.component.scss' ] | ||
15 | }) | ||
16 | export class UserPasswordComponent extends FormReactive implements OnInit { | ||
17 | error: string | ||
18 | username: string | ||
19 | showPassword = false | ||
20 | |||
21 | @Input() userId: number | ||
22 | |||
23 | constructor ( | ||
24 | protected formValidatorService: FormValidatorService, | ||
25 | private userValidatorsService: UserValidatorsService, | ||
26 | private route: ActivatedRoute, | ||
27 | private router: Router, | ||
28 | private notifier: Notifier, | ||
29 | private userService: UserService, | ||
30 | private i18n: I18n | ||
31 | ) { | ||
32 | super() | ||
33 | } | ||
34 | |||
35 | ngOnInit () { | ||
36 | this.buildForm({ | ||
37 | password: this.userValidatorsService.USER_PASSWORD | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | formValidated () { | ||
42 | this.error = undefined | ||
43 | |||
44 | const userUpdate: UserUpdate = this.form.value | ||
45 | |||
46 | this.userService.updateUser(this.userId, userUpdate).subscribe( | ||
47 | () => { | ||
48 | this.notifier.success( | ||
49 | this.i18n('Password changed for user {{username}}.', { username: this.username }) | ||
50 | ) | ||
51 | }, | ||
52 | |||
53 | err => this.error = err.message | ||
54 | ) | ||
55 | } | ||
56 | |||
57 | togglePasswordVisibility () { | ||
58 | this.showPassword = !this.showPassword | ||
59 | } | ||
60 | |||
61 | getFormButtonTitle () { | ||
62 | return this.i18n('Update user password') | ||
63 | } | ||
64 | } | ||
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 cd3885a99..94ef87b08 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 | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Subscription } from 'rxjs' | 3 | import { Subscription } from 'rxjs' |
4 | import { NotificationsService } from 'angular2-notifications' | 4 | import { 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, UserUpdate } from '../../../../../../shared' |
@@ -19,6 +19,7 @@ import { UserService } from '@app/shared' | |||
19 | export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | 19 | export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { |
20 | error: string | 20 | error: string |
21 | userId: number | 21 | userId: number |
22 | userEmail: string | ||
22 | username: string | 23 | username: string |
23 | 24 | ||
24 | private paramsSub: Subscription | 25 | private paramsSub: Subscription |
@@ -30,7 +31,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
30 | private userValidatorsService: UserValidatorsService, | 31 | private userValidatorsService: UserValidatorsService, |
31 | private route: ActivatedRoute, | 32 | private route: ActivatedRoute, |
32 | private router: Router, | 33 | private router: Router, |
33 | private notificationsService: NotificationsService, | 34 | private notifier: Notifier, |
34 | private userService: UserService, | 35 | private userService: UserService, |
35 | private i18n: I18n | 36 | private i18n: I18n |
36 | ) { | 37 | ) { |
@@ -73,10 +74,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
73 | 74 | ||
74 | this.userService.updateUser(this.userId, userUpdate).subscribe( | 75 | this.userService.updateUser(this.userId, userUpdate).subscribe( |
75 | () => { | 76 | () => { |
76 | this.notificationsService.success( | 77 | this.notifier.success(this.i18n('User {{username}} updated.', { username: this.username })) |
77 | this.i18n('Success'), | ||
78 | this.i18n('User {{username}} updated.', { username: this.username }) | ||
79 | ) | ||
80 | this.router.navigate([ '/admin/users/list' ]) | 78 | this.router.navigate([ '/admin/users/list' ]) |
81 | }, | 79 | }, |
82 | 80 | ||
@@ -92,9 +90,22 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
92 | return this.i18n('Update user') | 90 | return this.i18n('Update user') |
93 | } | 91 | } |
94 | 92 | ||
93 | resetPassword () { | ||
94 | this.userService.askResetPassword(this.userEmail).subscribe( | ||
95 | () => { | ||
96 | this.notifier.success( | ||
97 | this.i18n('An email asking for password reset has been sent to {{username}}.', { username: this.username }) | ||
98 | ) | ||
99 | }, | ||
100 | |||
101 | err => this.error = err.message | ||
102 | ) | ||
103 | } | ||
104 | |||
95 | private onUserFetched (userJson: User) { | 105 | private onUserFetched (userJson: User) { |
96 | this.userId = userJson.id | 106 | this.userId = userJson.id |
97 | this.username = userJson.username | 107 | this.username = userJson.username |
108 | this.userEmail = userJson.email | ||
98 | 109 | ||
99 | this.form.patchValue({ | 110 | this.form.patchValue({ |
100 | email: userJson.email, | 111 | email: userJson.email, |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html index cca057ba1..69a4616a3 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.html +++ b/client/src/app/+admin/users/user-list/user-list.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <div i18n class="form-sub-title">Users list</div> | 2 | <div i18n class="form-sub-title">Users list</div> |
3 | 3 | ||
4 | <a class="add-button" routerLink="/admin/users/create"> | 4 | <a class="add-button" routerLink="/admin/users/create"> |
5 | <span class="icon icon-add"></span> | 5 | <my-global-icon iconName="add"></my-global-icon> |
6 | <ng-container i18n>Create user</ng-container> | 6 | <ng-container i18n>Create user</ng-container> |
7 | </a> | 7 | </a> |
8 | </div> | 8 | </div> |
@@ -10,9 +10,32 @@ | |||
10 | <p-table | 10 | <p-table |
11 | [value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 11 | [value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
12 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" | 12 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" |
13 | [(selection)]="selectedUsers" | ||
13 | > | 14 | > |
15 | <ng-template pTemplate="caption"> | ||
16 | <div class="caption"> | ||
17 | <div> | ||
18 | <my-action-dropdown | ||
19 | *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange" | ||
20 | [actions]="bulkUserActions" [entry]="selectedUsers" | ||
21 | > | ||
22 | </my-action-dropdown> | ||
23 | </div> | ||
24 | |||
25 | <div> | ||
26 | <input | ||
27 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | ||
28 | (keyup)="onSearch($event.target.value)" | ||
29 | > | ||
30 | </div> | ||
31 | </div> | ||
32 | </ng-template> | ||
33 | |||
14 | <ng-template pTemplate="header"> | 34 | <ng-template pTemplate="header"> |
15 | <tr> | 35 | <tr> |
36 | <th style="width: 40px"> | ||
37 | <p-tableHeaderCheckbox></p-tableHeaderCheckbox> | ||
38 | </th> | ||
16 | <th style="width: 40px"></th> | 39 | <th style="width: 40px"></th> |
17 | <th i18n pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th> | 40 | <th i18n pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th> |
18 | <th i18n>Email</th> | 41 | <th i18n>Email</th> |
@@ -25,22 +48,42 @@ | |||
25 | 48 | ||
26 | <ng-template pTemplate="body" let-expanded="expanded" let-user> | 49 | <ng-template pTemplate="body" let-expanded="expanded" let-user> |
27 | 50 | ||
28 | <tr [ngClass]="{ banned: user.blocked }"> | 51 | <tr [pSelectableRow]="user" [ngClass]="{ banned: user.blocked }"> |
52 | <td> | ||
53 | <p-tableCheckbox [value]="user"></p-tableCheckbox> | ||
54 | </td> | ||
55 | |||
29 | <td> | 56 | <td> |
30 | <span *ngIf="user.blockedReason" class="expander" [pRowToggler]="user"> | 57 | <span *ngIf="user.blockedReason" class="expander" [pRowToggler]="user"> |
31 | <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i> | 58 | <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i> |
32 | </span> | 59 | </span> |
33 | </td> | 60 | </td> |
61 | |||
34 | <td> | 62 | <td> |
35 | {{ user.username }} | 63 | <a i18n-title title="Go to the account page" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]"> |
36 | <span *ngIf="user.blocked" class="banned-info">(banned)</span> | 64 | {{ user.username }} |
65 | <span i18n *ngIf="user.blocked" class="banned-info">(banned)</span> | ||
66 | </a> | ||
37 | </td> | 67 | </td> |
38 | <td>{{ user.email }}</td> | 68 | |
69 | <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">{{ user.email }}</td> | ||
70 | |||
71 | <ng-template #emailWithVerificationStatus> | ||
72 | <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login"> | ||
73 | <em>? {{ user.email }}</em> | ||
74 | </td> | ||
75 | <ng-template #emailVerifiedNotFalse> | ||
76 | <td i18n-title title="User's email is verified / User can login without email verification"> | ||
77 | ✓ {{ user.email }} | ||
78 | </td> | ||
79 | </ng-template> | ||
80 | </ng-template> | ||
81 | |||
39 | <td>{{ user.videoQuotaUsed }} / {{ user.videoQuota }}</td> | 82 | <td>{{ user.videoQuotaUsed }} / {{ user.videoQuota }}</td> |
40 | <td>{{ user.roleLabel }}</td> | 83 | <td>{{ user.roleLabel }}</td> |
41 | <td>{{ user.createdAt }}</td> | 84 | <td>{{ user.createdAt }}</td> |
42 | <td class="action-cell"> | 85 | <td class="action-cell"> |
43 | <my-user-moderation-dropdown [user]="user" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()"> | 86 | <my-user-moderation-dropdown *ngIf="!isInSelectionMode()" [user]="user" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()"> |
44 | </my-user-moderation-dropdown> | 87 | </my-user-moderation-dropdown> |
45 | </td> | 88 | </td> |
46 | </tr> | 89 | </tr> |
@@ -56,3 +99,4 @@ | |||
56 | </ng-template> | 99 | </ng-template> |
57 | </p-table> | 100 | </p-table> |
58 | 101 | ||
102 | <my-user-ban-modal #userBanModal (userBanned)="onUserChanged()"></my-user-ban-modal> | ||
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss index 47291918d..5274be01c 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/users/user-list/user-list.component.scss | |||
@@ -2,7 +2,7 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .add-button { | 4 | .add-button { |
5 | @include create-button('../../../../assets/images/global/add.svg'); | 5 | @include create-button; |
6 | } | 6 | } |
7 | 7 | ||
8 | tr.banned { | 8 | tr.banned { |
@@ -15,4 +15,12 @@ tr.banned { | |||
15 | 15 | ||
16 | .ban-reason-label { | 16 | .ban-reason-label { |
17 | font-weight: $font-semibold; | 17 | font-weight: $font-semibold; |
18 | } \ No newline at end of file | 18 | } |
19 | |||
20 | .caption { | ||
21 | justify-content: space-between; | ||
22 | |||
23 | input { | ||
24 | @include peertube-input-text(250px); | ||
25 | } | ||
26 | } | ||
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index dee3ed643..66ab796f9 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { SortMeta } from 'primeng/components/common/sortmeta' | 3 | import { SortMeta } from 'primeng/components/common/sortmeta' |
4 | import { ConfirmService } from '../../../core' | 4 | import { ConfirmService, ServerService } from '../../../core' |
5 | import { RestPagination, RestTable, UserService } from '../../../shared' | 5 | import { RestPagination, RestTable, UserService } from '../../../shared' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { User } from '../../../../../../shared' | 7 | import { User } from '../../../../../../shared' |
8 | import { UserBanModalComponent } from '@app/shared/moderation' | ||
9 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' | ||
8 | 10 | ||
9 | @Component({ | 11 | @Component({ |
10 | selector: 'my-user-list', | 12 | selector: 'my-user-list', |
@@ -12,38 +14,139 @@ import { User } from '../../../../../../shared' | |||
12 | styleUrls: [ './user-list.component.scss' ] | 14 | styleUrls: [ './user-list.component.scss' ] |
13 | }) | 15 | }) |
14 | export class UserListComponent extends RestTable implements OnInit { | 16 | export class UserListComponent extends RestTable implements OnInit { |
17 | @ViewChild('userBanModal') userBanModal: UserBanModalComponent | ||
18 | |||
15 | users: User[] = [] | 19 | users: User[] = [] |
16 | totalRecords = 0 | 20 | totalRecords = 0 |
17 | rowsPerPage = 10 | 21 | rowsPerPage = 10 |
18 | sort: SortMeta = { field: 'createdAt', order: 1 } | 22 | sort: SortMeta = { field: 'createdAt', order: 1 } |
19 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 23 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
20 | 24 | ||
25 | selectedUsers: User[] = [] | ||
26 | bulkUserActions: DropdownAction<User[]>[] = [] | ||
27 | |||
21 | constructor ( | 28 | constructor ( |
22 | private notificationsService: NotificationsService, | 29 | private notifier: Notifier, |
23 | private confirmService: ConfirmService, | 30 | private confirmService: ConfirmService, |
31 | private serverService: ServerService, | ||
24 | private userService: UserService, | 32 | private userService: UserService, |
25 | private i18n: I18n | 33 | private i18n: I18n |
26 | ) { | 34 | ) { |
27 | super() | 35 | super() |
28 | } | 36 | } |
29 | 37 | ||
38 | get requiresEmailVerification () { | ||
39 | return this.serverService.getConfig().signup.requiresEmailVerification | ||
40 | } | ||
41 | |||
30 | ngOnInit () { | 42 | ngOnInit () { |
31 | this.loadSort() | 43 | this.initialize() |
44 | |||
45 | this.bulkUserActions = [ | ||
46 | { | ||
47 | label: this.i18n('Delete'), | ||
48 | handler: users => this.removeUsers(users) | ||
49 | }, | ||
50 | { | ||
51 | label: this.i18n('Ban'), | ||
52 | handler: users => this.openBanUserModal(users), | ||
53 | isDisplayed: users => users.every(u => u.blocked === false) | ||
54 | }, | ||
55 | { | ||
56 | label: this.i18n('Unban'), | ||
57 | handler: users => this.unbanUsers(users), | ||
58 | isDisplayed: users => users.every(u => u.blocked === true) | ||
59 | }, | ||
60 | { | ||
61 | label: this.i18n('Set Email as Verified'), | ||
62 | handler: users => this.setEmailsAsVerified(users), | ||
63 | isDisplayed: users => this.requiresEmailVerification && users.every(u => !u.blocked && u.emailVerified === false) | ||
64 | } | ||
65 | ] | ||
66 | } | ||
67 | |||
68 | openBanUserModal (users: User[]) { | ||
69 | for (const user of users) { | ||
70 | if (user.username === 'root') { | ||
71 | this.notifier.error(this.i18n('You cannot ban root.')) | ||
72 | return | ||
73 | } | ||
74 | } | ||
75 | |||
76 | this.userBanModal.openModal(users) | ||
32 | } | 77 | } |
33 | 78 | ||
34 | onUserChanged () { | 79 | onUserChanged () { |
35 | this.loadData() | 80 | this.loadData() |
36 | } | 81 | } |
37 | 82 | ||
83 | async unbanUsers (users: User[]) { | ||
84 | const message = this.i18n('Do you really want to unban {{num}} users?', { num: users.length }) | ||
85 | |||
86 | const res = await this.confirmService.confirm(message, this.i18n('Unban')) | ||
87 | if (res === false) return | ||
88 | |||
89 | this.userService.unbanUsers(users) | ||
90 | .subscribe( | ||
91 | () => { | ||
92 | const message = this.i18n('{{num}} users unbanned.', { num: users.length }) | ||
93 | |||
94 | this.notifier.success(message) | ||
95 | this.loadData() | ||
96 | }, | ||
97 | |||
98 | err => this.notifier.error(err.message) | ||
99 | ) | ||
100 | } | ||
101 | |||
102 | async removeUsers (users: User[]) { | ||
103 | for (const user of users) { | ||
104 | if (user.username === 'root') { | ||
105 | this.notifier.error(this.i18n('You cannot delete root.')) | ||
106 | return | ||
107 | } | ||
108 | } | ||
109 | |||
110 | const message = this.i18n('If you remove these users, you will not be able to create others with the same username!') | ||
111 | const res = await this.confirmService.confirm(message, this.i18n('Delete')) | ||
112 | if (res === false) return | ||
113 | |||
114 | this.userService.removeUser(users).subscribe( | ||
115 | () => { | ||
116 | this.notifier.success(this.i18n('{{num}} users deleted.', { num: users.length })) | ||
117 | this.loadData() | ||
118 | }, | ||
119 | |||
120 | err => this.notifier.error(err.message) | ||
121 | ) | ||
122 | } | ||
123 | |||
124 | async setEmailsAsVerified (users: User[]) { | ||
125 | this.userService.updateUsers(users, { emailVerified: true }).subscribe( | ||
126 | () => { | ||
127 | this.notifier.success(this.i18n('{{num}} users email set as verified.', { num: users.length })) | ||
128 | this.loadData() | ||
129 | }, | ||
130 | |||
131 | err => this.notifier.error(err.message) | ||
132 | ) | ||
133 | } | ||
134 | |||
135 | isInSelectionMode () { | ||
136 | return this.selectedUsers.length !== 0 | ||
137 | } | ||
138 | |||
38 | protected loadData () { | 139 | protected loadData () { |
39 | this.userService.getUsers(this.pagination, this.sort) | 140 | this.selectedUsers = [] |
40 | .subscribe( | 141 | |
41 | resultList => { | 142 | this.userService.getUsers(this.pagination, this.sort, this.search) |
42 | this.users = resultList.data | 143 | .subscribe( |
43 | this.totalRecords = resultList.total | 144 | resultList => { |
44 | }, | 145 | this.users = resultList.data |
45 | 146 | this.totalRecords = resultList.total | |
46 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 147 | }, |
47 | ) | 148 | |
149 | err => this.notifier.error(err.message) | ||
150 | ) | ||
48 | } | 151 | } |
49 | } | 152 | } |