WIP plugins: list installed plugins in client

pull/1987/head
Chocobozzz 2019-07-10 18:30:27 +02:00 committed by Chocobozzz
parent ad91e7006e
commit d00dc28dd7
22 changed files with 304 additions and 5 deletions

View File

@ -9,6 +9,7 @@ import { FollowsRoutes } from './follows'
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 = [
{
@ -26,7 +27,8 @@ const adminRoutes: Routes = [
...UsersRoutes,
...ModerationRoutes,
...SystemRoutes,
...ConfigRoutes
...ConfigRoutes,
...PluginsRoutes
]
}
]

View File

@ -16,6 +16,10 @@
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>

View File

@ -28,6 +28,10 @@ export class AdminComponent {
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)
}

View File

@ -21,11 +21,18 @@ import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } f
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
],
@ -52,6 +59,11 @@ import { DebugComponent, DebugService } from '@app/+admin/system/debug'
InstanceServerBlocklistComponent,
InstanceAccountBlocklistComponent,
PluginsComponent,
PluginListInstalledComponent,
PluginSearchComponent,
PluginShowInstalledComponent,
SystemComponent,
JobsComponent,
LogsComponent,
@ -70,7 +82,8 @@ import { DebugComponent, DebugService } from '@app/+admin/system/debug'
JobService,
LogsService,
DebugService,
ConfigService
ConfigService,
PluginApiService
]
})
export class AdminModule { }

View File

@ -0,0 +1 @@
export * from './plugins.component'

View File

@ -0,0 +1,13 @@
<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>

View File

@ -0,0 +1,8 @@
@import '_variables';
@import '_mixins';
.toggle-plugin-type {
display: flex;
justify-content: center;
margin-bottom: 30px;
}

View File

@ -0,0 +1,72 @@
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.')
}
}

View File

@ -0,0 +1,2 @@
@import '_variables';
@import '_mixins';

View File

@ -0,0 +1,30 @@
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 () {
}
}

View File

@ -0,0 +1,2 @@
@import '_variables';
@import '_mixins';

View File

@ -0,0 +1,14 @@
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 () {
}
}

View File

@ -0,0 +1,11 @@
<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>

View File

@ -0,0 +1,7 @@
@import '_variables';
@import '_mixins';
.form-sub-title {
flex-grow: 0;
margin-right: 30px;
}

View File

@ -0,0 +1,8 @@
import { Component } from '@angular/core'
@Component({
templateUrl: './plugins.component.html',
styleUrls: [ './plugins.component.scss' ]
})
export class PluginsComponent {
}

View File

@ -0,0 +1,53 @@
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'
}
}
}
]
}
]

View File

@ -0,0 +1,50 @@
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)))
}
}

View File

@ -5,7 +5,7 @@ import { ServerService } from '@app/core/server/server.service'
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'

View File

@ -83,6 +83,7 @@ export class ThemeService {
console.log('Enabling %s theme.', currentTheme)
this.loadTheme(currentTheme)
const theme = this.getTheme(currentTheme)
if (theme) {
console.log('Adding scripts of theme %s.', currentTheme)
@ -95,6 +96,10 @@ export class ThemeService {
}
private listenUserTheme () {
if (!this.auth.isLoggedIn()) {
this.updateCurrentTheme()
}
this.auth.userInformationLoaded
.subscribe(() => this.updateCurrentTheme())
}

View File

@ -14,7 +14,7 @@ import { searchRouter } from './search'
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()
@ -43,7 +43,7 @@ apiRouter.use('/videos', videosRouter)
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)