diff options
author | Chocobozzz <me@florianbigard.com> | 2019-04-11 10:56:29 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-04-11 10:58:09 +0200 |
commit | 5d79474cc66383ecbfcef6366f63a34c3af21cbf (patch) | |
tree | 6b2cd85c25294ca04c94cbbcc90353d25e9cee06 | |
parent | 2b3f1919fda81c2781ceeb9071d426c184e1b21c (diff) | |
download | PeerTube-5d79474cc66383ecbfcef6366f63a34c3af21cbf.tar.gz PeerTube-5d79474cc66383ecbfcef6366f63a34c3af21cbf.tar.zst PeerTube-5d79474cc66383ecbfcef6366f63a34c3af21cbf.zip |
Add debug component to help admins to fix IP issues
19 files changed, 242 insertions, 10 deletions
diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html index 065d92509..98f45a7d1 100644 --- a/client/src/app/+admin/admin.component.html +++ b/client/src/app/+admin/admin.component.html | |||
@@ -16,7 +16,7 @@ | |||
16 | Configuration | 16 | Configuration |
17 | </a> | 17 | </a> |
18 | 18 | ||
19 | <a i18n *ngIf="hasJobsRight() || hasLogsRight()" routerLink="/admin/system" routerLinkActive="active" class="title-page"> | 19 | <a i18n *ngIf="hasJobsRight() || hasLogsRight() || hasDebugRight()" routerLink="/admin/system" routerLinkActive="active" class="title-page"> |
20 | System | 20 | System |
21 | </a> | 21 | </a> |
22 | </div> | 22 | </div> |
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index fc775a5a4..408de4837 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts | |||
@@ -24,15 +24,19 @@ export class AdminComponent { | |||
24 | return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) | 24 | return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) |
25 | } | 25 | } |
26 | 26 | ||
27 | hasJobsRight () { | 27 | hasConfigRight () { |
28 | return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS) | 28 | return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION) |
29 | } | 29 | } |
30 | 30 | ||
31 | hasLogsRight () { | 31 | hasLogsRight () { |
32 | return this.auth.getUser().hasRight(UserRight.MANAGE_LOGS) | 32 | return this.auth.getUser().hasRight(UserRight.MANAGE_LOGS) |
33 | } | 33 | } |
34 | 34 | ||
35 | hasConfigRight () { | 35 | hasJobsRight () { |
36 | return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION) | 36 | return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS) |
37 | } | ||
38 | |||
39 | hasDebugRight () { | ||
40 | return this.auth.getUser().hasRight(UserRight.MANAGE_DEBUG) | ||
37 | } | 41 | } |
38 | } | 42 | } |
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index ae0af686b..71a4dfc4a 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts | |||
@@ -20,6 +20,7 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service | |||
20 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' | 20 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' |
21 | import { JobsComponent } from '@app/+admin/system/jobs/jobs.component' | 21 | import { JobsComponent } from '@app/+admin/system/jobs/jobs.component' |
22 | import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+admin/system' | 22 | import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+admin/system' |
23 | import { DebugComponent, DebugService } from '@app/+admin/system/debug' | ||
23 | 24 | ||
24 | @NgModule({ | 25 | @NgModule({ |
25 | imports: [ | 26 | imports: [ |
@@ -54,6 +55,7 @@ import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+a | |||
54 | SystemComponent, | 55 | SystemComponent, |
55 | JobsComponent, | 56 | JobsComponent, |
56 | LogsComponent, | 57 | LogsComponent, |
58 | DebugComponent, | ||
57 | 59 | ||
58 | ConfigComponent, | 60 | ConfigComponent, |
59 | EditCustomConfigComponent | 61 | EditCustomConfigComponent |
@@ -68,6 +70,7 @@ import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+a | |||
68 | RedundancyService, | 70 | RedundancyService, |
69 | JobService, | 71 | JobService, |
70 | LogsService, | 72 | LogsService, |
73 | DebugService, | ||
71 | ConfigService | 74 | ConfigService |
72 | ] | 75 | ] |
73 | }) | 76 | }) |
diff --git a/client/src/app/+admin/system/debug/debug.component.html b/client/src/app/+admin/system/debug/debug.component.html new file mode 100644 index 000000000..f35414b37 --- /dev/null +++ b/client/src/app/+admin/system/debug/debug.component.html | |||
@@ -0,0 +1,19 @@ | |||
1 | <div class="root"> | ||
2 | <h4>IP</h4> | ||
3 | |||
4 | <p>PeerTube thinks your public IP is <strong>{{ debug?.ip }}</strong>.</p> | ||
5 | |||
6 | <p>If this is not your correct public IP, please consider fixing it because:</p> | ||
7 | <ul> | ||
8 | <li>Views may not be counted correctly (reduced compared to what they should be)</li> | ||
9 | <li>Anti brute force system could be overzealous</li> | ||
10 | <li>P2P system could not work correctly</li> | ||
11 | </ul> | ||
12 | |||
13 | <p>To fix it:<p> | ||
14 | <ul> | ||
15 | <li>Check the <code>trust_proxy</code> configuration key</li> | ||
16 | <li>If you run PeerTube using Docker, check you run the <code>reverse-proxy</code> with <code>network_mode: "host"</code> | ||
17 | (see <a href="https://github.com/Chocobozzz/PeerTube/issues/1643#issuecomment-464789666">issue 1643</a>)</li> | ||
18 | </ul> | ||
19 | </div> | ||
diff --git a/client/src/app/+admin/system/debug/debug.component.scss b/client/src/app/+admin/system/debug/debug.component.scss new file mode 100644 index 000000000..90addd284 --- /dev/null +++ b/client/src/app/+admin/system/debug/debug.component.scss | |||
@@ -0,0 +1,6 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .root { | ||
5 | font-size: 14px; | ||
6 | } | ||
diff --git a/client/src/app/+admin/system/debug/debug.component.ts b/client/src/app/+admin/system/debug/debug.component.ts new file mode 100644 index 000000000..8a77f79f7 --- /dev/null +++ b/client/src/app/+admin/system/debug/debug.component.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { Debug } from '@shared/models/server' | ||
4 | import { DebugService } from '@app/+admin/system/debug/debug.service' | ||
5 | |||
6 | @Component({ | ||
7 | templateUrl: './debug.component.html', | ||
8 | styleUrls: [ './debug.component.scss' ] | ||
9 | }) | ||
10 | export class DebugComponent implements OnInit { | ||
11 | debug: Debug | ||
12 | |||
13 | constructor ( | ||
14 | private debugService: DebugService, | ||
15 | private notifier: Notifier | ||
16 | ) { | ||
17 | } | ||
18 | |||
19 | ngOnInit (): void { | ||
20 | this.load() | ||
21 | } | ||
22 | |||
23 | load () { | ||
24 | this.debugService.getDebug() | ||
25 | .subscribe( | ||
26 | debug => this.debug = debug, | ||
27 | |||
28 | err => this.notifier.error(err.message) | ||
29 | ) | ||
30 | } | ||
31 | } | ||
diff --git a/client/src/app/+admin/system/debug/debug.service.ts b/client/src/app/+admin/system/debug/debug.service.ts new file mode 100644 index 000000000..6c722d177 --- /dev/null +++ b/client/src/app/+admin/system/debug/debug.service.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { catchError } from 'rxjs/operators' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { Observable } from 'rxjs' | ||
5 | import { environment } from '../../../../environments/environment' | ||
6 | import { RestExtractor, RestService } from '../../../shared' | ||
7 | import { Debug } from '@shared/models/server' | ||
8 | |||
9 | @Injectable() | ||
10 | export class DebugService { | ||
11 | private static BASE_DEBUG_URL = environment.apiUrl + '/api/v1/server/debug' | ||
12 | |||
13 | constructor ( | ||
14 | private authHttp: HttpClient, | ||
15 | private restService: RestService, | ||
16 | private restExtractor: RestExtractor | ||
17 | ) {} | ||
18 | |||
19 | getDebug (): Observable<Debug> { | ||
20 | return this.authHttp.get<Debug>(DebugService.BASE_DEBUG_URL) | ||
21 | .pipe( | ||
22 | catchError(err => this.restExtractor.handleError(err)) | ||
23 | ) | ||
24 | } | ||
25 | } | ||
diff --git a/client/src/app/+admin/system/debug/index.ts b/client/src/app/+admin/system/debug/index.ts new file mode 100644 index 000000000..7fc7a0721 --- /dev/null +++ b/client/src/app/+admin/system/debug/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './debug.component' | ||
2 | export * from './debug.service' | ||
diff --git a/client/src/app/+admin/system/logs/logs.service.ts b/client/src/app/+admin/system/logs/logs.service.ts index 4db79a1fa..24b9cb6d1 100644 --- a/client/src/app/+admin/system/logs/logs.service.ts +++ b/client/src/app/+admin/system/logs/logs.service.ts | |||
@@ -9,7 +9,7 @@ import { LogLevel } from '@shared/models/server/log-level.type' | |||
9 | 9 | ||
10 | @Injectable() | 10 | @Injectable() |
11 | export class LogsService { | 11 | export class LogsService { |
12 | private static BASE_JOB_URL = environment.apiUrl + '/api/v1/server/logs' | 12 | private static BASE_LOG_URL = environment.apiUrl + '/api/v1/server/logs' |
13 | 13 | ||
14 | constructor ( | 14 | constructor ( |
15 | private authHttp: HttpClient, | 15 | private authHttp: HttpClient, |
@@ -17,14 +17,14 @@ export class LogsService { | |||
17 | private restExtractor: RestExtractor | 17 | private restExtractor: RestExtractor |
18 | ) {} | 18 | ) {} |
19 | 19 | ||
20 | getLogs (level: LogLevel, startDate: string, endDate?: string): Observable<any> { | 20 | getLogs (level: LogLevel, startDate: string, endDate?: string): Observable<any[]> { |
21 | let params = new HttpParams() | 21 | let params = new HttpParams() |
22 | params = params.append('startDate', startDate) | 22 | params = params.append('startDate', startDate) |
23 | params = params.append('level', level) | 23 | params = params.append('level', level) |
24 | 24 | ||
25 | if (endDate) params.append('endDate', endDate) | 25 | if (endDate) params.append('endDate', endDate) |
26 | 26 | ||
27 | return this.authHttp.get<any[]>(LogsService.BASE_JOB_URL, { params }) | 27 | return this.authHttp.get<any[]>(LogsService.BASE_LOG_URL, { params }) |
28 | .pipe( | 28 | .pipe( |
29 | map(rows => rows.map(r => new LogRow(r))), | 29 | map(rows => rows.map(r => new LogRow(r))), |
30 | catchError(err => this.restExtractor.handleError(err)) | 30 | catchError(err => this.restExtractor.handleError(err)) |
diff --git a/client/src/app/+admin/system/system.component.html b/client/src/app/+admin/system/system.component.html index 345a101e6..7c4278d35 100644 --- a/client/src/app/+admin/system/system.component.html +++ b/client/src/app/+admin/system/system.component.html | |||
@@ -2,9 +2,11 @@ | |||
2 | <div i18n class="form-sub-title">System</div> | 2 | <div i18n class="form-sub-title">System</div> |
3 | 3 | ||
4 | <div class="admin-sub-nav"> | 4 | <div class="admin-sub-nav"> |
5 | <a i18n routerLink="jobs" routerLinkActive="active">Jobs</a> | 5 | <a *ngIf="hasJobsRight()" i18n routerLink="jobs" routerLinkActive="active">Jobs</a> |
6 | 6 | ||
7 | <a i18n routerLink="logs" routerLinkActive="active">Logs</a> | 7 | <a *ngIf="hasLogsRight()" i18n routerLink="logs" routerLinkActive="active">Logs</a> |
8 | |||
9 | <a *ngIf="hasDebugRight()" i18n routerLink="debug" routerLinkActive="active">Debug</a> | ||
8 | </div> | 10 | </div> |
9 | </div> | 11 | </div> |
10 | 12 | ||
diff --git a/client/src/app/+admin/system/system.component.ts b/client/src/app/+admin/system/system.component.ts index 992d9c8af..b544c2a97 100644 --- a/client/src/app/+admin/system/system.component.ts +++ b/client/src/app/+admin/system/system.component.ts | |||
@@ -1,8 +1,24 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component } from '@angular/core' |
2 | import { UserRight } from '@shared/models' | ||
3 | import { AuthService } from '@app/core' | ||
2 | 4 | ||
3 | @Component({ | 5 | @Component({ |
4 | templateUrl: './system.component.html', | 6 | templateUrl: './system.component.html', |
5 | styleUrls: [ './system.component.scss' ] | 7 | styleUrls: [ './system.component.scss' ] |
6 | }) | 8 | }) |
7 | export class SystemComponent { | 9 | export class SystemComponent { |
10 | |||
11 | constructor (private auth: AuthService) {} | ||
12 | |||
13 | hasLogsRight () { | ||
14 | return this.auth.getUser().hasRight(UserRight.MANAGE_LOGS) | ||
15 | } | ||
16 | |||
17 | hasJobsRight () { | ||
18 | return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS) | ||
19 | } | ||
20 | |||
21 | hasDebugRight () { | ||
22 | return this.auth.getUser().hasRight(UserRight.MANAGE_DEBUG) | ||
23 | } | ||
8 | } | 24 | } |
diff --git a/client/src/app/+admin/system/system.routes.ts b/client/src/app/+admin/system/system.routes.ts index e6d45b760..2d851794d 100644 --- a/client/src/app/+admin/system/system.routes.ts +++ b/client/src/app/+admin/system/system.routes.ts | |||
@@ -4,6 +4,7 @@ import { UserRight } from '../../../../../shared' | |||
4 | import { JobsComponent } from '@app/+admin/system/jobs/jobs.component' | 4 | import { JobsComponent } from '@app/+admin/system/jobs/jobs.component' |
5 | import { LogsComponent } from '@app/+admin/system/logs' | 5 | import { LogsComponent } from '@app/+admin/system/logs' |
6 | import { SystemComponent } from '@app/+admin/system/system.component' | 6 | import { SystemComponent } from '@app/+admin/system/system.component' |
7 | import { DebugComponent } from '@app/+admin/system/debug' | ||
7 | 8 | ||
8 | export const SystemRoutes: Routes = [ | 9 | export const SystemRoutes: Routes = [ |
9 | { | 10 | { |
@@ -38,6 +39,17 @@ export const SystemRoutes: Routes = [ | |||
38 | title: 'Logs' | 39 | title: 'Logs' |
39 | } | 40 | } |
40 | } | 41 | } |
42 | }, | ||
43 | { | ||
44 | path: 'debug', | ||
45 | canActivate: [ UserRightGuard ], | ||
46 | component: DebugComponent, | ||
47 | data: { | ||
48 | meta: { | ||
49 | userRight: UserRight.MANAGE_DEBUG, | ||
50 | title: 'Debug' | ||
51 | } | ||
52 | } | ||
41 | } | 53 | } |
42 | ] | 54 | ] |
43 | } | 55 | } |
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts new file mode 100644 index 000000000..4450038f6 --- /dev/null +++ b/server/controllers/api/server/debug.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import * as express from 'express' | ||
2 | import { UserRight } from '../../../../shared/models/users' | ||
3 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' | ||
4 | |||
5 | const debugRouter = express.Router() | ||
6 | |||
7 | debugRouter.get('/debug', | ||
8 | authenticate, | ||
9 | ensureUserHasRight(UserRight.MANAGE_DEBUG), | ||
10 | asyncMiddleware(getDebug) | ||
11 | ) | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | export { | ||
16 | debugRouter | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | async function getDebug (req: express.Request, res: express.Response) { | ||
22 | return res.json({ | ||
23 | ip: req.ip | ||
24 | }).end() | ||
25 | } | ||
diff --git a/server/controllers/api/server/index.ts b/server/controllers/api/server/index.ts index de09588df..6b8793a19 100644 --- a/server/controllers/api/server/index.ts +++ b/server/controllers/api/server/index.ts | |||
@@ -5,6 +5,7 @@ import { serverRedundancyRouter } from './redundancy' | |||
5 | import { serverBlocklistRouter } from './server-blocklist' | 5 | import { serverBlocklistRouter } from './server-blocklist' |
6 | import { contactRouter } from './contact' | 6 | import { contactRouter } from './contact' |
7 | import { logsRouter } from './logs' | 7 | import { logsRouter } from './logs' |
8 | import { debugRouter } from './debug' | ||
8 | 9 | ||
9 | const serverRouter = express.Router() | 10 | const serverRouter = express.Router() |
10 | 11 | ||
@@ -14,6 +15,7 @@ serverRouter.use('/', statsRouter) | |||
14 | serverRouter.use('/', serverBlocklistRouter) | 15 | serverRouter.use('/', serverBlocklistRouter) |
15 | serverRouter.use('/', contactRouter) | 16 | serverRouter.use('/', contactRouter) |
16 | serverRouter.use('/', logsRouter) | 17 | serverRouter.use('/', logsRouter) |
18 | serverRouter.use('/', debugRouter) | ||
17 | 19 | ||
18 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
19 | 21 | ||
diff --git a/server/tests/api/check-params/debug.ts b/server/tests/api/check-params/debug.ts new file mode 100644 index 000000000..9bf664657 --- /dev/null +++ b/server/tests/api/check-params/debug.ts | |||
@@ -0,0 +1,78 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import 'mocha' | ||
4 | |||
5 | import { | ||
6 | createUser, | ||
7 | flushTests, | ||
8 | killallServers, | ||
9 | runServer, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | userLogin | ||
13 | } from '../../../../shared/utils' | ||
14 | import { makeGetRequest } from '../../../../shared/utils/requests/requests' | ||
15 | |||
16 | describe('Test debug API validators', function () { | ||
17 | const path = '/api/v1/server/debug' | ||
18 | let server: ServerInfo | ||
19 | let userAccessToken = '' | ||
20 | |||
21 | // --------------------------------------------------------------- | ||
22 | |||
23 | before(async function () { | ||
24 | this.timeout(120000) | ||
25 | |||
26 | await flushTests() | ||
27 | |||
28 | server = await runServer(1) | ||
29 | |||
30 | await setAccessTokensToServers([ server ]) | ||
31 | |||
32 | const user = { | ||
33 | username: 'user1', | ||
34 | password: 'my super password' | ||
35 | } | ||
36 | await createUser(server.url, server.accessToken, user.username, user.password) | ||
37 | userAccessToken = await userLogin(server, user) | ||
38 | }) | ||
39 | |||
40 | describe('When getting debug endpoint', function () { | ||
41 | |||
42 | it('Should fail with a non authenticated user', async function () { | ||
43 | await makeGetRequest({ | ||
44 | url: server.url, | ||
45 | path, | ||
46 | statusCodeExpected: 401 | ||
47 | }) | ||
48 | }) | ||
49 | |||
50 | it('Should fail with a non admin user', async function () { | ||
51 | await makeGetRequest({ | ||
52 | url: server.url, | ||
53 | path, | ||
54 | token: userAccessToken, | ||
55 | statusCodeExpected: 403 | ||
56 | }) | ||
57 | }) | ||
58 | |||
59 | it('Should succeed with the correct params', async function () { | ||
60 | await makeGetRequest({ | ||
61 | url: server.url, | ||
62 | path, | ||
63 | token: server.accessToken, | ||
64 | query: { startDate: new Date().toISOString() }, | ||
65 | statusCodeExpected: 200 | ||
66 | }) | ||
67 | }) | ||
68 | }) | ||
69 | |||
70 | after(async function () { | ||
71 | killallServers([ server ]) | ||
72 | |||
73 | // Keep the logs if the test failed | ||
74 | if (this['ok']) { | ||
75 | await flushTests() | ||
76 | } | ||
77 | }) | ||
78 | }) | ||
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index bdac95025..844fa31c5 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -2,6 +2,7 @@ import './accounts' | |||
2 | import './blocklist' | 2 | import './blocklist' |
3 | import './config' | 3 | import './config' |
4 | import './contact-form' | 4 | import './contact-form' |
5 | import './debug' | ||
5 | import './follows' | 6 | import './follows' |
6 | import './jobs' | 7 | import './jobs' |
7 | import './logs' | 8 | import './logs' |
diff --git a/shared/models/server/debug.model.ts b/shared/models/server/debug.model.ts new file mode 100644 index 000000000..61cba6518 --- /dev/null +++ b/shared/models/server/debug.model.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export interface Debug { | ||
2 | ip: string | ||
3 | } | ||
diff --git a/shared/models/server/index.ts b/shared/models/server/index.ts index c42f6f67f..bf61ab270 100644 --- a/shared/models/server/index.ts +++ b/shared/models/server/index.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | export * from './about.model' | 1 | export * from './about.model' |
2 | export * from './contact-form.model' | 2 | export * from './contact-form.model' |
3 | export * from './custom-config.model' | 3 | export * from './custom-config.model' |
4 | export * from './debug.model' | ||
4 | export * from './job.model' | 5 | export * from './job.model' |
5 | export * from './server-config.model' | 6 | export * from './server-config.model' |
6 | export * from './server-stats.model' | 7 | export * from './server-stats.model' |
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts index 5ec255ea5..71701bdb4 100644 --- a/shared/models/users/user-right.enum.ts +++ b/shared/models/users/user-right.enum.ts | |||
@@ -7,6 +7,8 @@ export enum UserRight { | |||
7 | 7 | ||
8 | MANAGE_LOGS, | 8 | MANAGE_LOGS, |
9 | 9 | ||
10 | MANAGE_DEBUG, | ||
11 | |||
10 | MANAGE_SERVER_REDUNDANCY, | 12 | MANAGE_SERVER_REDUNDANCY, |
11 | 13 | ||
12 | MANAGE_VIDEO_ABUSES, | 14 | MANAGE_VIDEO_ABUSES, |