import { UsersRoutes } from './users'
import { ModerationRoutes } from '@app/+admin/moderation/moderation.routes'
import { SystemRoutes } from '@app/+admin/system'
+import { PluginsRoutes } from '@app/+admin/plugins/plugins.routes'
const adminRoutes: Routes = [
{
...UsersRoutes,
...ModerationRoutes,
...SystemRoutes,
- ...ConfigRoutes
+ ...ConfigRoutes,
+ ...PluginsRoutes
]
}
]
Configuration
</a>
+ <a i18n *ngIf="hasPluginsRight()" routerLink="/admin/plugins" routerLinkActive="active" class="title-page">
+ Plugins/Themes
+ </a>
+
<a i18n *ngIf="hasJobsRight() || hasLogsRight() || hasDebugRight()" routerLink="/admin/system" routerLinkActive="active" class="title-page">
System
</a>
return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION)
}
+ hasPluginsRight () {
+ return this.auth.getUser().hasRight(UserRight.MANAGE_PLUGINS)
+ }
+
hasLogsRight () {
return this.auth.getUser().hasRight(UserRight.MANAGE_LOGS)
}
import { JobsComponent } from '@app/+admin/system/jobs/jobs.component'
import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+admin/system'
import { DebugComponent, DebugService } from '@app/+admin/system/debug'
+import { PluginsComponent } from '@app/+admin/plugins/plugins.component'
+import { PluginListInstalledComponent } from '@app/+admin/plugins/plugin-list-installed/plugin-list-installed.component'
+import { PluginSearchComponent } from '@app/+admin/plugins/plugin-search/plugin-search.component'
+import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component'
+import { SelectButtonModule } from 'primeng/primeng'
+import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
@NgModule({
imports: [
AdminRoutingModule,
TableModule,
+ SelectButtonModule,
SharedModule
],
InstanceServerBlocklistComponent,
InstanceAccountBlocklistComponent,
+ PluginsComponent,
+ PluginListInstalledComponent,
+ PluginSearchComponent,
+ PluginShowInstalledComponent,
+
SystemComponent,
JobsComponent,
LogsComponent,
JobService,
LogsService,
DebugService,
- ConfigService
+ ConfigService,
+ PluginApiService
]
})
export class AdminModule { }
--- /dev/null
+export * from './plugins.component'
--- /dev/null
+<div class="toggle-plugin-type">
+ <p-selectButton [options]="pluginTypeOptions" [(ngModel)]="pluginType" (ngModelChange)="reloadPlugins()"></p-selectButton>
+</div>
+
+<div class="no-results" i18n *ngIf="pagination.totalItems === 0">
+ {{ getNoResultMessage() }}
+</div>
+
+<div class="plugins" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true">
+ <div class="section plugin" *ngFor="let plugin of plugins">
+ {{ plugin.name }}
+ </div>
+</div>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.toggle-plugin-type {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 30px;
+}
--- /dev/null
+import { Component, OnInit } from '@angular/core'
+import { PluginType } from '@shared/models/plugins/plugin.type'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
+import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
+import { Notifier } from '@app/core'
+import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
+
+@Component({
+ selector: 'my-plugin-list-installed',
+ templateUrl: './plugin-list-installed.component.html',
+ styleUrls: [ './plugin-list-installed.component.scss' ]
+})
+export class PluginListInstalledComponent implements OnInit {
+ pluginTypeOptions: { label: string, value: PluginType }[] = []
+ pluginType: PluginType = PluginType.PLUGIN
+
+ pagination: ComponentPagination = {
+ currentPage: 1,
+ itemsPerPage: 10
+ }
+ sort = 'name'
+
+ plugins: PeerTubePlugin[] = []
+
+ constructor (
+ private i18n: I18n,
+ private pluginService: PluginApiService,
+ private notifier: Notifier
+ ) {
+ this.pluginTypeOptions = this.pluginService.getPluginTypeOptions()
+ }
+
+ ngOnInit () {
+ this.reloadPlugins()
+ }
+
+ reloadPlugins () {
+ this.pagination.currentPage = 1
+ this.plugins = []
+
+ this.loadMorePlugins()
+ }
+
+ loadMorePlugins () {
+ this.pluginService.getPlugins(this.pluginType, this.pagination, this.sort)
+ .subscribe(
+ res => {
+ this.plugins = this.plugins.concat(res.data)
+ this.pagination.totalItems = res.total
+ },
+
+ err => this.notifier.error(err.message)
+ )
+ }
+
+ onNearOfBottom () {
+ if (!hasMoreItems(this.pagination)) return
+
+ this.pagination.currentPage += 1
+
+ this.loadMorePlugins()
+ }
+
+ getNoResultMessage () {
+ if (this.pluginType === PluginType.PLUGIN) {
+ return this.i18n('You don\'t have plugins installed yet.')
+ }
+
+ return this.i18n('You don\'t have themes installed yet.')
+ }
+}
--- /dev/null
+@import '_variables';
+@import '_mixins';
--- /dev/null
+import { Component, OnInit, ViewChild } from '@angular/core'
+import { Notifier } from '@app/core'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+import { ConfirmService, ServerService } from '../../../core'
+import { RestPagination, RestTable, UserService } from '../../../shared'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { User } from '../../../../../../shared'
+import { UserBanModalComponent } from '@app/shared/moderation'
+import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
+import { PluginType } from '@shared/models/plugins/plugin.type'
+import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
+
+@Component({
+ selector: 'my-plugin-search',
+ templateUrl: './plugin-search.component.html',
+ styleUrls: [ './plugin-search.component.scss' ]
+})
+export class PluginSearchComponent implements OnInit {
+ pluginTypeOptions: { label: string, value: PluginType }[] = []
+
+ constructor (
+ private i18n: I18n,
+ private pluginService: PluginApiService
+ ) {
+ this.pluginTypeOptions = this.pluginService.getPluginTypeOptions()
+ }
+
+ ngOnInit () {
+ }
+}
--- /dev/null
+@import '_variables';
+@import '_mixins';
--- /dev/null
+import { Component, OnInit } from '@angular/core'
+
+@Component({
+ selector: 'my-plugin-show-installed',
+ templateUrl: './plugin-show-installed.component.html',
+ styleUrls: [ './plugin-show-installed.component.scss' ]
+})
+export class PluginShowInstalledComponent implements OnInit {
+
+ ngOnInit () {
+
+ }
+
+}
--- /dev/null
+<div class="admin-sub-header">
+ <div i18n class="form-sub-title">Plugins/Themes</div>
+
+ <div class="admin-sub-nav">
+ <a i18n routerLink="list-installed" routerLinkActive="active">Installed</a>
+
+ <a i18n routerLink="search" routerLinkActive="active">Search</a>
+ </div>
+</div>
+
+<router-outlet></router-outlet>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.form-sub-title {
+ flex-grow: 0;
+ margin-right: 30px;
+}
--- /dev/null
+import { Component } from '@angular/core'
+
+@Component({
+ templateUrl: './plugins.component.html',
+ styleUrls: [ './plugins.component.scss' ]
+})
+export class PluginsComponent {
+}
--- /dev/null
+import { Routes } from '@angular/router'
+
+import { UserRightGuard } from '../../core'
+import { UserRight } from '../../../../../shared'
+import { PluginListInstalledComponent } from '@app/+admin/plugins/plugin-list-installed/plugin-list-installed.component'
+import { PluginSearchComponent } from '@app/+admin/plugins/plugin-search/plugin-search.component'
+import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component'
+import { PluginsComponent } from '@app/+admin/plugins/plugins.component'
+
+export const PluginsRoutes: Routes = [
+ {
+ path: 'plugins',
+ component: PluginsComponent,
+ canActivate: [ UserRightGuard ],
+ data: {
+ userRight: UserRight.MANAGE_PLUGINS
+ },
+ children: [
+ {
+ path: '',
+ redirectTo: 'list-installed',
+ pathMatch: 'full'
+ },
+ {
+ path: 'list-installed',
+ component: PluginListInstalledComponent,
+ data: {
+ meta: {
+ title: 'List installed plugins'
+ }
+ }
+ },
+ {
+ path: 'search',
+ component: PluginSearchComponent,
+ data: {
+ meta: {
+ title: 'Search plugins'
+ }
+ }
+ },
+ {
+ path: 'show/:name',
+ component: PluginShowInstalledComponent,
+ data: {
+ meta: {
+ title: 'Show plugin'
+ }
+ }
+ }
+ ]
+ }
+]
--- /dev/null
+import { catchError } from 'rxjs/operators'
+import { HttpClient, HttpParams } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import { environment } from '../../../../environments/environment'
+import { RestExtractor, RestService } from '../../../shared'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { PluginType } from '@shared/models/plugins/plugin.type'
+import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
+import { ResultList } from '@shared/models'
+import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
+
+@Injectable()
+export class PluginApiService {
+ private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/plugins'
+
+ constructor (
+ private authHttp: HttpClient,
+ private restExtractor: RestExtractor,
+ private restService: RestService,
+ private i18n: I18n
+ ) { }
+
+ getPluginTypeOptions () {
+ return [
+ {
+ label: this.i18n('Plugin'),
+ value: PluginType.PLUGIN
+ },
+ {
+ label: this.i18n('Theme'),
+ value: PluginType.THEME
+ }
+ ]
+ }
+
+ getPlugins (
+ type: PluginType,
+ componentPagination: ComponentPagination,
+ sort: string
+ ) {
+ const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
+
+ let params = new HttpParams()
+ params = this.restService.addRestGetParams(params, pagination, sort)
+ params = params.append('type', type.toString())
+
+ return this.authHttp.get<ResultList<PeerTubePlugin>>(PluginApiService.BASE_APPLICATION_URL, { params })
+ .pipe(catchError(res => this.restExtractor.handleError(res)))
+ }
+}
import { ClientScript } from '@shared/models/plugins/plugin-package-json.model'
import { PluginScope } from '@shared/models/plugins/plugin-scope.type'
import { environment } from '../../../environments/environment'
-import { RegisterHookOptions } from '@shared/models/plugins/register.model'
+import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model'
import { ReplaySubject } from 'rxjs'
import { first, shareReplay } from 'rxjs/operators'
console.log('Enabling %s theme.', currentTheme)
this.loadTheme(currentTheme)
+
const theme = this.getTheme(currentTheme)
if (theme) {
console.log('Adding scripts of theme %s.', currentTheme)
}
private listenUserTheme () {
+ if (!this.auth.isLoggedIn()) {
+ this.updateCurrentTheme()
+ }
+
this.auth.userInformationLoaded
.subscribe(() => this.updateCurrentTheme())
}
import { overviewsRouter } from './overviews'
import { videoPlaylistRouter } from './video-playlist'
import { CONFIG } from '../../initializers/config'
-import { pluginsRouter } from '../plugins'
+import { pluginRouter } from './plugins'
const apiRouter = express.Router()
apiRouter.use('/jobs', jobsRouter)
apiRouter.use('/search', searchRouter)
apiRouter.use('/overviews', overviewsRouter)
-apiRouter.use('/plugins', pluginsRouter)
+apiRouter.use('/plugins', pluginRouter)
apiRouter.use('/ping', pong)
apiRouter.use('/*', badRequest)