diff options
20 files changed, 282 insertions, 20 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index 5703d5a2e..4ee573696 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 | |||
@@ -276,6 +276,58 @@ | |||
276 | </div> | 276 | </div> |
277 | </div> | 277 | </div> |
278 | 278 | ||
279 | <div class="form-row mt-4"> <!-- broadcast grid --> | ||
280 | <div class="form-group col-12 col-lg-4 col-xl-3"> | ||
281 | <div i18n class="inner-form-title">BROADCAST MESSAGE</div> | ||
282 | <div i18n class="inner-for-description"> | ||
283 | Display a message on your instance | ||
284 | </div> | ||
285 | </div> | ||
286 | |||
287 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | ||
288 | |||
289 | <ng-container formGroupName="broadcastMessage"> | ||
290 | |||
291 | <div class="form-group"> | ||
292 | <my-peertube-checkbox | ||
293 | inputName="broadcastMessageEnabled" formControlName="enabled" | ||
294 | i18n-labelText labelText="Enable broadcast message" | ||
295 | ></my-peertube-checkbox> | ||
296 | </div> | ||
297 | |||
298 | <div class="form-group"> | ||
299 | <my-peertube-checkbox | ||
300 | inputName="broadcastMessageDismissable" formControlName="dismissable" | ||
301 | i18n-labelText labelText="Allow users to dismiss the broadcast message " | ||
302 | ></my-peertube-checkbox> | ||
303 | </div> | ||
304 | |||
305 | <div class="form-group"> | ||
306 | <label i18n for="broadcastMessageLevel">Broadcast message level</label> | ||
307 | <div class="peertube-select-container"> | ||
308 | <select id="broadcastMessageLevel" formControlName="level" class="form-control"> | ||
309 | <option value="info">info</option> | ||
310 | <option value="warning">warning</option> | ||
311 | <option value="error">error</option> | ||
312 | </select> | ||
313 | </div> | ||
314 | <div *ngIf="formErrors.broadcastMessage.level" class="form-error">{{ formErrors.broadcastMessage.level }}</div> | ||
315 | </div> | ||
316 | |||
317 | <div class="form-group"> | ||
318 | <label i18n for="broadcastMessageMessage">Message</label><my-help helpType="markdownText"></my-help> | ||
319 | <my-markdown-textarea | ||
320 | name="broadcastMessageMessage" formControlName="message" textareaMaxWidth="500px" | ||
321 | [classes]="{ 'input-error': formErrors['broadcastMessage.message'] }" | ||
322 | ></my-markdown-textarea> | ||
323 | <div *ngIf="formErrors.broadcastMessage.message" class="form-error">{{ formErrors.broadcastMessage.message }}</div> | ||
324 | </div> | ||
325 | |||
326 | </ng-container> | ||
327 | |||
328 | </div> | ||
329 | </div> | ||
330 | |||
279 | <div class="form-row mt-4"> <!-- new users grid --> | 331 | <div class="form-row mt-4"> <!-- new users grid --> |
280 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 332 | <div class="form-group col-12 col-lg-4 col-xl-3"> |
281 | <div i18n class="inner-form-title">NEW USERS</div> | 333 | <div i18n class="inner-form-title">NEW USERS</div> |
@@ -801,8 +853,9 @@ | |||
801 | <div class="form-row mt-4"> <!-- submit placement block --> | 853 | <div class="form-row mt-4"> <!-- submit placement block --> |
802 | <div class="col-md-7 col-xl-5"></div> | 854 | <div class="col-md-7 col-xl-5"></div> |
803 | <div class="col-md-5 col-xl-5"> | 855 | <div class="col-md-5 col-xl-5"> |
856 | <span class="form-error submit-error" i18n *ngIf="!form.valid">It seems like the configuration is invalid. Please search for potential errors in the different tabs.</span> | ||
857 | |||
804 | <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid"> | 858 | <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid"> |
805 | <span class="form-error" i18n *ngIf="!form.valid">It seems like the configuration is invalid. Please search for potential errors in the different tabs.</span> | ||
806 | </div> | 859 | </div> |
807 | </div> | 860 | </div> |
808 | </form> | 861 | </form> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss index 9ee960ad6..2bfa92da4 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | |||
@@ -76,4 +76,8 @@ ngb-tabset:not(.previews) ::ng-deep { | |||
76 | .nav-link { | 76 | .nav-link { |
77 | font-size: 105%; | 77 | font-size: 105%; |
78 | } | 78 | } |
79 | } \ No newline at end of file | 79 | } |
80 | |||
81 | .submit-error { | ||
82 | margin-bottom: 20px; | ||
83 | } | ||
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 cea314cea..6d59494c8 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 | |||
@@ -215,6 +215,12 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
215 | indexUrl: this.customConfigValidatorsService.INDEX_URL | 215 | indexUrl: this.customConfigValidatorsService.INDEX_URL |
216 | } | 216 | } |
217 | } | 217 | } |
218 | }, | ||
219 | broadcastMessage: { | ||
220 | enabled: null, | ||
221 | level: null, | ||
222 | dismissable: null, | ||
223 | message: null | ||
218 | } | 224 | } |
219 | } | 225 | } |
220 | 226 | ||
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index b0d2e5050..b243c129b 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -25,6 +25,16 @@ | |||
25 | <div id="content" tabindex="-1" class="main-col" [ngClass]="{ expanded: menu.isMenuDisplayed === false }"> | 25 | <div id="content" tabindex="-1" class="main-col" [ngClass]="{ expanded: menu.isMenuDisplayed === false }"> |
26 | 26 | ||
27 | <div class="main-row"> | 27 | <div class="main-row"> |
28 | |||
29 | <div *ngIf="broadcastMessage" class="broadcast-message alert" [ngClass]="broadcastMessage.class"> | ||
30 | <div [innerHTML]="broadcastMessage.message"></div> | ||
31 | |||
32 | <my-global-icon | ||
33 | *ngIf="broadcastMessage.dismissable" (click)="hideBroadcastMessage()" | ||
34 | iconName="cross" role="button" title="Close this message" i18n-title | ||
35 | ></my-global-icon> | ||
36 | </div> | ||
37 | |||
28 | <router-outlet></router-outlet> | 38 | <router-outlet></router-outlet> |
29 | </div> | 39 | </div> |
30 | </div> | 40 | </div> |
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 0c33dc4a1..27fd69c8d 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss | |||
@@ -1,5 +1,7 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import '~bootstrap/scss/functions'; | ||
4 | @import '~bootstrap/scss/variables'; | ||
3 | 5 | ||
4 | .peertube-container { | 6 | .peertube-container { |
5 | padding-bottom: 20px; | 7 | padding-bottom: 20px; |
@@ -88,3 +90,39 @@ | |||
88 | flex: 1; | 90 | flex: 1; |
89 | } | 91 | } |
90 | } | 92 | } |
93 | |||
94 | .broadcast-message { | ||
95 | min-height: 50px; | ||
96 | text-align: center; | ||
97 | margin-bottom: 0; | ||
98 | border-radius: 0; | ||
99 | display: grid; | ||
100 | grid-template-columns: 1fr 30px; | ||
101 | column-gap: 10px; | ||
102 | |||
103 | my-global-icon { | ||
104 | justify-self: center; | ||
105 | align-self: center; | ||
106 | cursor: pointer; | ||
107 | |||
108 | width: 20px; | ||
109 | } | ||
110 | |||
111 | @each $color, $value in $theme-colors { | ||
112 | &.alert-#{$color} { | ||
113 | my-global-icon { | ||
114 | @include apply-svg-color(theme-color-level($color, $alert-color-level)); | ||
115 | } | ||
116 | } | ||
117 | } | ||
118 | |||
119 | ::ng-deep { | ||
120 | p { | ||
121 | font-size: 16px; | ||
122 | } | ||
123 | |||
124 | p:last-child { | ||
125 | margin-bottom: 0; | ||
126 | } | ||
127 | } | ||
128 | } | ||
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 12c0efd8a..a464e90fa 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -4,7 +4,7 @@ import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular | |||
4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' | 4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' |
5 | import { is18nPath } from '../../../shared/models/i18n' | 5 | import { is18nPath } from '../../../shared/models/i18n' |
6 | import { ScreenService } from '@app/shared/misc/screen.service' | 6 | import { ScreenService } from '@app/shared/misc/screen.service' |
7 | import { filter, map, pairwise } from 'rxjs/operators' | 7 | import { filter, map, pairwise, first } from 'rxjs/operators' |
8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { PlatformLocation, ViewportScroller } from '@angular/common' | 10 | import { PlatformLocation, ViewportScroller } from '@angular/common' |
@@ -19,6 +19,10 @@ import { ServerConfig, UserRole } from '@shared/models' | |||
19 | import { User } from '@app/shared' | 19 | import { User } from '@app/shared' |
20 | import { InstanceService } from '@app/shared/instance/instance.service' | 20 | import { InstanceService } from '@app/shared/instance/instance.service' |
21 | import { MenuService } from './core/menu/menu.service' | 21 | import { MenuService } from './core/menu/menu.service' |
22 | import { BroadcastMessageLevel } from '@shared/models/server' | ||
23 | import { MarkdownService } from './shared/renderer' | ||
24 | import { concat } from 'rxjs' | ||
25 | import { peertubeLocalStorage } from './shared/misc/peertube-web-storage' | ||
22 | 26 | ||
23 | @Component({ | 27 | @Component({ |
24 | selector: 'my-app', | 28 | selector: 'my-app', |
@@ -26,11 +30,14 @@ import { MenuService } from './core/menu/menu.service' | |||
26 | styleUrls: [ './app.component.scss' ] | 30 | styleUrls: [ './app.component.scss' ] |
27 | }) | 31 | }) |
28 | export class AppComponent implements OnInit, AfterViewInit { | 32 | export class AppComponent implements OnInit, AfterViewInit { |
33 | private static BROADCAST_MESSAGE_KEY = 'app-broadcast-message-dismissed' | ||
34 | |||
29 | @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent | 35 | @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent |
30 | @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent | 36 | @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent |
31 | @ViewChild('customModal') customModal: CustomModalComponent | 37 | @ViewChild('customModal') customModal: CustomModalComponent |
32 | 38 | ||
33 | customCSS: SafeHtml | 39 | customCSS: SafeHtml |
40 | broadcastMessage: { message: string, dismissable: boolean, class: string } | null = null | ||
34 | 41 | ||
35 | private serverConfig: ServerConfig | 42 | private serverConfig: ServerConfig |
36 | 43 | ||
@@ -50,6 +57,7 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
50 | private hooks: HooksService, | 57 | private hooks: HooksService, |
51 | private location: PlatformLocation, | 58 | private location: PlatformLocation, |
52 | private modalService: NgbModal, | 59 | private modalService: NgbModal, |
60 | private markdownService: MarkdownService, | ||
53 | public menu: MenuService | 61 | public menu: MenuService |
54 | ) { } | 62 | ) { } |
55 | 63 | ||
@@ -81,6 +89,7 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
81 | this.initRouteEvents() | 89 | this.initRouteEvents() |
82 | this.injectJS() | 90 | this.injectJS() |
83 | this.injectCSS() | 91 | this.injectCSS() |
92 | this.injectBroadcastMessage() | ||
84 | 93 | ||
85 | this.initHotkeys() | 94 | this.initHotkeys() |
86 | 95 | ||
@@ -97,6 +106,12 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
97 | return this.authService.isLoggedIn() | 106 | return this.authService.isLoggedIn() |
98 | } | 107 | } |
99 | 108 | ||
109 | hideBroadcastMessage () { | ||
110 | peertubeLocalStorage.setItem(AppComponent.BROADCAST_MESSAGE_KEY, this.serverConfig.broadcastMessage.message) | ||
111 | |||
112 | this.broadcastMessage = null | ||
113 | } | ||
114 | |||
100 | private initRouteEvents () { | 115 | private initRouteEvents () { |
101 | let resetScroll = true | 116 | let resetScroll = true |
102 | const eventsObs = this.router.events | 117 | const eventsObs = this.router.events |
@@ -165,6 +180,36 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
165 | ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page | 180 | ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page |
166 | } | 181 | } |
167 | 182 | ||
183 | private injectBroadcastMessage () { | ||
184 | concat( | ||
185 | this.serverService.getConfig().pipe(first()), | ||
186 | this.serverService.configReloaded | ||
187 | ).subscribe(async config => { | ||
188 | this.broadcastMessage = null | ||
189 | |||
190 | const messageConfig = config.broadcastMessage | ||
191 | |||
192 | if (messageConfig.enabled) { | ||
193 | // Already dismissed this message? | ||
194 | if (messageConfig.dismissable && localStorage.getItem(AppComponent.BROADCAST_MESSAGE_KEY) === messageConfig.message) { | ||
195 | return | ||
196 | } | ||
197 | |||
198 | const classes: { [id in BroadcastMessageLevel]: string } = { | ||
199 | info: 'alert-info', | ||
200 | warning: 'alert-warning', | ||
201 | error: 'alert-danger' | ||
202 | } | ||
203 | |||
204 | this.broadcastMessage = { | ||
205 | message: await this.markdownService.completeMarkdownToHTML(messageConfig.message), | ||
206 | dismissable: messageConfig.dismissable, | ||
207 | class: classes[messageConfig.level] | ||
208 | } | ||
209 | } | ||
210 | }) | ||
211 | } | ||
212 | |||
168 | private injectJS () { | 213 | private injectJS () { |
169 | // Inject JS | 214 | // Inject JS |
170 | this.serverService.getConfig() | 215 | this.serverService.getConfig() |
@@ -182,17 +227,19 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
182 | 227 | ||
183 | private injectCSS () { | 228 | private injectCSS () { |
184 | // Inject CSS if modified (admin config settings) | 229 | // Inject CSS if modified (admin config settings) |
185 | this.serverService.configReloaded | 230 | concat( |
186 | .subscribe(() => { | 231 | this.serverService.getConfig().pipe(first()), |
187 | const headStyle = document.querySelector('style.custom-css-style') | 232 | this.serverService.configReloaded |
188 | if (headStyle) headStyle.parentNode.removeChild(headStyle) | 233 | ).subscribe(config => { |
189 | 234 | const headStyle = document.querySelector('style.custom-css-style') | |
190 | // We test customCSS if the admin removed the css | 235 | if (headStyle) headStyle.parentNode.removeChild(headStyle) |
191 | if (this.customCSS || this.serverConfig.instance.customizations.css) { | 236 | |
192 | const styleTag = '<style>' + this.serverConfig.instance.customizations.css + '</style>' | 237 | // We test customCSS if the admin removed the css |
193 | this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag) | 238 | if (this.customCSS || config.instance.customizations.css) { |
194 | } | 239 | const styleTag = '<style>' + config.instance.customizations.css + '</style>' |
195 | }) | 240 | this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag) |
241 | } | ||
242 | }) | ||
196 | } | 243 | } |
197 | 244 | ||
198 | private async loadPlugins () { | 245 | private async loadPlugins () { |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index eac8f85e4..fdfbe4c02 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -21,7 +21,7 @@ export class ServerService { | |||
21 | 21 | ||
22 | private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' | 22 | private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' |
23 | 23 | ||
24 | configReloaded = new Subject<void>() | 24 | configReloaded = new Subject<ServerConfig>() |
25 | 25 | ||
26 | private localeObservable: Observable<any> | 26 | private localeObservable: Observable<any> |
27 | private videoLicensesObservable: Observable<VideoConstant<number>[]> | 27 | private videoLicensesObservable: Observable<VideoConstant<number>[]> |
@@ -139,6 +139,12 @@ export class ServerService { | |||
139 | indexUrl: 'https://instances.joinpeertube.org' | 139 | indexUrl: 'https://instances.joinpeertube.org' |
140 | } | 140 | } |
141 | } | 141 | } |
142 | }, | ||
143 | broadcastMessage: { | ||
144 | enabled: false, | ||
145 | message: '', | ||
146 | level: 'info', | ||
147 | dismissable: false | ||
142 | } | 148 | } |
143 | } | 149 | } |
144 | 150 | ||
@@ -162,6 +168,11 @@ export class ServerService { | |||
162 | resetConfig () { | 168 | resetConfig () { |
163 | this.configLoaded = false | 169 | this.configLoaded = false |
164 | this.configReset = true | 170 | this.configReset = true |
171 | |||
172 | // Notify config update | ||
173 | this.getConfig().subscribe(() => { | ||
174 | // empty, to fire a reset config event | ||
175 | }) | ||
165 | } | 176 | } |
166 | 177 | ||
167 | getConfig () { | 178 | getConfig () { |
@@ -175,9 +186,9 @@ export class ServerService { | |||
175 | this.config = config | 186 | this.config = config |
176 | this.configLoaded = true | 187 | this.configLoaded = true |
177 | }), | 188 | }), |
178 | tap(() => { | 189 | tap(config => { |
179 | if (this.configReset) { | 190 | if (this.configReset) { |
180 | this.configReloaded.next() | 191 | this.configReloaded.next(config) |
181 | this.configReset = false | 192 | this.configReset = false |
182 | } | 193 | } |
183 | }), | 194 | }), |
diff --git a/config/default.yaml b/config/default.yaml index a0f2eb3a1..34a0a146f 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -372,3 +372,9 @@ followings: | |||
372 | 372 | ||
373 | theme: | 373 | theme: |
374 | default: 'default' | 374 | default: 'default' |
375 | |||
376 | broadcast_message: | ||
377 | enabled: false | ||
378 | message: '' # Support markdown | ||
379 | level: 'info' # 'info' | 'warning' | 'error' | ||
380 | dismissable: false | ||
diff --git a/config/production.yaml.example b/config/production.yaml.example index 8b8c98f8c..0ac05c515 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -386,3 +386,9 @@ followings: | |||
386 | 386 | ||
387 | theme: | 387 | theme: |
388 | default: 'default' | 388 | default: 'default' |
389 | |||
390 | broadcast_message: | ||
391 | enabled: false | ||
392 | message: '' # Support markdown | ||
393 | level: 'info' # 'info' | 'warning' | 'error' | ||
394 | dismissable: false | ||
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index edcb0b99e..41e5027b9 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -172,6 +172,13 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
172 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | 172 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL |
173 | } | 173 | } |
174 | } | 174 | } |
175 | }, | ||
176 | |||
177 | broadcastMessage: { | ||
178 | enabled: CONFIG.BROADCAST_MESSAGE.ENABLED, | ||
179 | message: CONFIG.BROADCAST_MESSAGE.MESSAGE, | ||
180 | level: CONFIG.BROADCAST_MESSAGE.LEVEL, | ||
181 | dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE | ||
175 | } | 182 | } |
176 | } | 183 | } |
177 | 184 | ||
@@ -432,6 +439,12 @@ function customConfig (): CustomConfig { | |||
432 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | 439 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL |
433 | } | 440 | } |
434 | } | 441 | } |
442 | }, | ||
443 | broadcastMessage: { | ||
444 | enabled: CONFIG.BROADCAST_MESSAGE.ENABLED, | ||
445 | message: CONFIG.BROADCAST_MESSAGE.MESSAGE, | ||
446 | level: CONFIG.BROADCAST_MESSAGE.LEVEL, | ||
447 | dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE | ||
435 | } | 448 | } |
436 | } | 449 | } |
437 | } | 450 | } |
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts index f111be2ae..b5b854137 100644 --- a/server/initializers/checker-after-init.ts +++ b/server/initializers/checker-after-init.ts | |||
@@ -107,6 +107,10 @@ function checkConfig () { | |||
107 | } | 107 | } |
108 | } | 108 | } |
109 | 109 | ||
110 | if (CONFIG.STORAGE.VIDEOS_DIR === CONFIG.STORAGE.REDUNDANCY_DIR) { | ||
111 | logger.warn('Redundancy directory should be different than the videos folder.') | ||
112 | } | ||
113 | |||
110 | // Transcoding | 114 | // Transcoding |
111 | if (CONFIG.TRANSCODING.ENABLED) { | 115 | if (CONFIG.TRANSCODING.ENABLED) { |
112 | if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) { | 116 | if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) { |
@@ -114,8 +118,14 @@ function checkConfig () { | |||
114 | } | 118 | } |
115 | } | 119 | } |
116 | 120 | ||
117 | if (CONFIG.STORAGE.VIDEOS_DIR === CONFIG.STORAGE.REDUNDANCY_DIR) { | 121 | // Broadcast message |
118 | logger.warn('Redundancy directory should be different than the videos folder.') | 122 | if (CONFIG.BROADCAST_MESSAGE.ENABLED) { |
123 | const currentLevel = CONFIG.BROADCAST_MESSAGE.LEVEL | ||
124 | const available = [ 'info', 'warning', 'error' ] | ||
125 | |||
126 | if (available.includes(currentLevel) === false) { | ||
127 | return 'Broadcast message level should be ' + available.join(' or ') + ' instead of ' + currentLevel | ||
128 | } | ||
119 | } | 129 | } |
120 | 130 | ||
121 | return null | 131 | return null |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 6932b41e1..e2920ce9e 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -6,6 +6,7 @@ import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core- | |||
6 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 6 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
7 | import * as bytes from 'bytes' | 7 | import * as bytes from 'bytes' |
8 | import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type' | 8 | import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type' |
9 | import { BroadcastMessageLevel } from '@shared/models/server' | ||
9 | 10 | ||
10 | // Use a variable to reload the configuration if we need | 11 | // Use a variable to reload the configuration if we need |
11 | let config: IConfig = require('config') | 12 | let config: IConfig = require('config') |
@@ -285,6 +286,12 @@ const CONFIG = { | |||
285 | }, | 286 | }, |
286 | THEME: { | 287 | THEME: { |
287 | get DEFAULT () { return config.get<string>('theme.default') } | 288 | get DEFAULT () { return config.get<string>('theme.default') } |
289 | }, | ||
290 | BROADCAST_MESSAGE: { | ||
291 | get ENABLED () { return config.get<boolean>('broadcast_message.enabled') }, | ||
292 | get MESSAGE () { return config.get<string>('broadcast_message.message') }, | ||
293 | get LEVEL () { return config.get<BroadcastMessageLevel>('broadcast_message.level') }, | ||
294 | get DISMISSABLE () { return config.get<boolean>('broadcast_message.dismissable') } | ||
288 | } | 295 | } |
289 | } | 296 | } |
290 | 297 | ||
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index dfa549e76..6905ac762 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -55,6 +55,11 @@ const customConfigUpdateValidator = [ | |||
55 | 55 | ||
56 | body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), | 56 | body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), |
57 | 57 | ||
58 | body('broadcastMessage.enabled').isBoolean().withMessage('Should have a valid broadcast message enabled boolean'), | ||
59 | body('broadcastMessage.message').exists().withMessage('Should have a valid broadcast message'), | ||
60 | body('broadcastMessage.level').exists().withMessage('Should have a valid broadcast level'), | ||
61 | body('broadcastMessage.dismissable').exists().withMessage('Should have a valid broadcast dismissable boolean'), | ||
62 | |||
58 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 63 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
59 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) | 64 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) |
60 | 65 | ||
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index f1a79806b..7c96fa762 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -133,6 +133,12 @@ describe('Test config API validators', function () { | |||
133 | indexUrl: 'https://index.example.com' | 133 | indexUrl: 'https://index.example.com' |
134 | } | 134 | } |
135 | } | 135 | } |
136 | }, | ||
137 | broadcastMessage: { | ||
138 | enabled: true, | ||
139 | dismissable: true, | ||
140 | message: 'super message', | ||
141 | level: 'warning' | ||
136 | } | 142 | } |
137 | } | 143 | } |
138 | 144 | ||
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index 8580835d6..d18a93082 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -87,6 +87,11 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) { | |||
87 | expect(data.followings.instance.autoFollowBack.enabled).to.be.false | 87 | expect(data.followings.instance.autoFollowBack.enabled).to.be.false |
88 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.false | 88 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.false |
89 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('') | 89 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('') |
90 | |||
91 | expect(data.broadcastMessage.enabled).to.be.false | ||
92 | expect(data.broadcastMessage.level).to.equal('info') | ||
93 | expect(data.broadcastMessage.message).to.equal('') | ||
94 | expect(data.broadcastMessage.dismissable).to.be.false | ||
90 | } | 95 | } |
91 | 96 | ||
92 | function checkUpdatedConfig (data: CustomConfig) { | 97 | function checkUpdatedConfig (data: CustomConfig) { |
@@ -155,6 +160,11 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
155 | expect(data.followings.instance.autoFollowBack.enabled).to.be.true | 160 | expect(data.followings.instance.autoFollowBack.enabled).to.be.true |
156 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.true | 161 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.true |
157 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://updated.example.com') | 162 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://updated.example.com') |
163 | |||
164 | expect(data.broadcastMessage.enabled).to.be.true | ||
165 | expect(data.broadcastMessage.level).to.equal('error') | ||
166 | expect(data.broadcastMessage.message).to.equal('super bad message') | ||
167 | expect(data.broadcastMessage.dismissable).to.be.true | ||
158 | } | 168 | } |
159 | 169 | ||
160 | describe('Test config', function () { | 170 | describe('Test config', function () { |
@@ -324,6 +334,12 @@ describe('Test config', function () { | |||
324 | indexUrl: 'https://updated.example.com' | 334 | indexUrl: 'https://updated.example.com' |
325 | } | 335 | } |
326 | } | 336 | } |
337 | }, | ||
338 | broadcastMessage: { | ||
339 | enabled: true, | ||
340 | level: 'error', | ||
341 | message: 'super bad message', | ||
342 | dismissable: true | ||
327 | } | 343 | } |
328 | } | 344 | } |
329 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) | 345 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) |
diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts index 743d10316..98cd435f6 100644 --- a/shared/extra-utils/server/config.ts +++ b/shared/extra-utils/server/config.ts | |||
@@ -159,6 +159,12 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti | |||
159 | enabled: false | 159 | enabled: false |
160 | } | 160 | } |
161 | } | 161 | } |
162 | }, | ||
163 | broadcastMessage: { | ||
164 | enabled: true, | ||
165 | level: 'warning', | ||
166 | message: 'hello', | ||
167 | dismissable: true | ||
162 | } | 168 | } |
163 | } | 169 | } |
164 | 170 | ||
diff --git a/shared/models/server/broadcast-message-level.type.ts b/shared/models/server/broadcast-message-level.type.ts new file mode 100644 index 000000000..bf43e18b5 --- /dev/null +++ b/shared/models/server/broadcast-message-level.type.ts | |||
@@ -0,0 +1 @@ | |||
export type BroadcastMessageLevel = 'info' | 'warning' | 'error' | |||
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index 07e17bda2..851bf1854 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' | 1 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' |
2 | import { BroadcastMessageLevel } from './broadcast-message-level.type' | ||
2 | 3 | ||
3 | export interface CustomConfig { | 4 | export interface CustomConfig { |
4 | instance: { | 5 | instance: { |
@@ -131,4 +132,11 @@ export interface CustomConfig { | |||
131 | } | 132 | } |
132 | } | 133 | } |
133 | } | 134 | } |
135 | |||
136 | broadcastMessage: { | ||
137 | enabled: boolean | ||
138 | message: string | ||
139 | level: BroadcastMessageLevel | ||
140 | dismissable: boolean | ||
141 | } | ||
134 | } | 142 | } |
diff --git a/shared/models/server/index.ts b/shared/models/server/index.ts index b0afb2c66..2bb443d46 100644 --- a/shared/models/server/index.ts +++ b/shared/models/server/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export * from './about.model' | 1 | export * from './about.model' |
2 | export * from './broadcast-message-level.type' | ||
2 | export * from './contact-form.model' | 3 | export * from './contact-form.model' |
3 | export * from './custom-config.model' | 4 | export * from './custom-config.model' |
4 | export * from './debug.model' | 5 | export * from './debug.model' |
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts index a1f9b3b5d..9c903b7ee 100644 --- a/shared/models/server/server-config.model.ts +++ b/shared/models/server/server-config.model.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' | ||
2 | import { ClientScript } from '../plugins/plugin-package-json.model' | 1 | import { ClientScript } from '../plugins/plugin-package-json.model' |
2 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' | ||
3 | import { BroadcastMessageLevel } from './broadcast-message-level.type' | ||
3 | 4 | ||
4 | export interface ServerConfigPlugin { | 5 | export interface ServerConfigPlugin { |
5 | name: string | 6 | name: string |
@@ -161,4 +162,11 @@ export interface ServerConfig { | |||
161 | } | 162 | } |
162 | } | 163 | } |
163 | } | 164 | } |
165 | |||
166 | broadcastMessage: { | ||
167 | enabled: boolean | ||
168 | message: string | ||
169 | level: BroadcastMessageLevel | ||
170 | dismissable: boolean | ||
171 | } | ||
164 | } | 172 | } |