aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html5
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss4
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts43
-rw-r--r--client/src/app/+admin/plugins/shared/plugin-api.service.ts13
-rw-r--r--client/src/app/core/plugins/plugin.service.ts6
-rw-r--r--client/src/app/shared/buttons/button.component.html4
-rw-r--r--client/src/app/shared/buttons/button.component.scss6
-rw-r--r--client/src/app/shared/buttons/button.component.ts1
-rw-r--r--client/src/app/shared/misc/small-loader.component.html2
-rw-r--r--client/src/app/shared/misc/utils.ts18
10 files changed, 94 insertions, 8 deletions
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
index d4501490f..f10b4eb8d 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
@@ -26,8 +26,11 @@
26 <span i18n class="button-label">Homepage</span> 26 <span i18n class="button-label">Homepage</span>
27 </a> 27 </a>
28 28
29 <my-edit-button *ngIf="pluginType !== PluginType.THEME" [routerLink]="getShowRouterLink(plugin)" label="Settings" i18n-label></my-edit-button>
29 30
30 <my-edit-button [routerLink]="getShowRouterLink(plugin)" label="Settings" i18n-label></my-edit-button> 31 <my-button class="update-button" *ngIf="!isUpdateAvailable(plugin)" (click)="update(plugin)" [loading]="isUpdating(plugin)"
32 [label]="getUpdateLabel(plugin)" icon="refresh" [attr.disabled]="isUpdating(plugin)"
33 ></my-button>
31 34
32 <my-delete-button (click)="uninstall(plugin)" label="Uninstall" i18n-label></my-delete-button> 35 <my-delete-button (click)="uninstall(plugin)" label="Uninstall" i18n-label></my-delete-button>
33 </div> 36 </div>
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss
index f250404ed..7641c507b 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss
@@ -35,3 +35,7 @@
35 @include peertube-button-link; 35 @include peertube-button-link;
36 @include button-with-icon(21px, 0, -2px); 36 @include button-with-icon(21px, 0, -2px);
37} 37}
38
39.update-button[disabled="true"] /deep/ .action-button {
40 cursor: default !important;
41}
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
index 26a9a616e..67a11c3a8 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts
@@ -6,6 +6,7 @@ import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pa
6import { ConfirmService, Notifier } from '@app/core' 6import { ConfirmService, Notifier } from '@app/core'
7import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' 7import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
8import { ActivatedRoute, Router } from '@angular/router' 8import { ActivatedRoute, Router } from '@angular/router'
9import { compareSemVer } from '@app/shared/misc/utils'
9 10
10@Component({ 11@Component({
11 selector: 'my-plugin-list-installed', 12 selector: 'my-plugin-list-installed',
@@ -26,6 +27,9 @@ export class PluginListInstalledComponent implements OnInit {
26 sort = 'name' 27 sort = 'name'
27 28
28 plugins: PeerTubePlugin[] = [] 29 plugins: PeerTubePlugin[] = []
30 updating: { [name: string]: boolean } = {}
31
32 PluginType = PluginType
29 33
30 constructor ( 34 constructor (
31 private i18n: I18n, 35 private i18n: I18n,
@@ -49,7 +53,7 @@ export class PluginListInstalledComponent implements OnInit {
49 this.pagination.currentPage = 1 53 this.pagination.currentPage = 1
50 this.plugins = [] 54 this.plugins = []
51 55
52 this.router.navigate([], { queryParams: { pluginType: this.pluginType }}) 56 this.router.navigate([], { queryParams: { pluginType: this.pluginType } })
53 57
54 this.loadMorePlugins() 58 this.loadMorePlugins()
55 } 59 }
@@ -82,6 +86,18 @@ export class PluginListInstalledComponent implements OnInit {
82 return this.i18n('You don\'t have themes installed yet.') 86 return this.i18n('You don\'t have themes installed yet.')
83 } 87 }
84 88
89 isUpdateAvailable (plugin: PeerTubePlugin) {
90 return plugin.latestVersion && compareSemVer(plugin.latestVersion, plugin.version) > 0
91 }
92
93 getUpdateLabel (plugin: PeerTubePlugin) {
94 return this.i18n('Update to {{version}}', { version: plugin.latestVersion })
95 }
96
97 isUpdating (plugin: PeerTubePlugin) {
98 return !!this.updating[this.getUpdatingKey(plugin)]
99 }
100
85 async uninstall (plugin: PeerTubePlugin) { 101 async uninstall (plugin: PeerTubePlugin) {
86 const res = await this.confirmService.confirm( 102 const res = await this.confirmService.confirm(
87 this.i18n('Do you really want to uninstall {{pluginName}}?', { pluginName: plugin.name }), 103 this.i18n('Do you really want to uninstall {{pluginName}}?', { pluginName: plugin.name }),
@@ -102,7 +118,32 @@ export class PluginListInstalledComponent implements OnInit {
102 ) 118 )
103 } 119 }
104 120
121 async update (plugin: PeerTubePlugin) {
122 const updatingKey = this.getUpdatingKey(plugin)
123 if (this.updating[updatingKey]) return
124
125 this.updating[updatingKey] = true
126
127 this.pluginService.update(plugin.name, plugin.type)
128 .pipe()
129 .subscribe(
130 res => {
131 this.updating[updatingKey] = false
132
133 this.notifier.success(this.i18n('{{pluginName}} updated.', { pluginName: plugin.name }))
134
135 Object.assign(plugin, res)
136 },
137
138 err => this.notifier.error(err.message)
139 )
140 }
141
105 getShowRouterLink (plugin: PeerTubePlugin) { 142 getShowRouterLink (plugin: PeerTubePlugin) {
106 return [ '/admin', 'plugins', 'show', this.pluginService.nameToNpmName(plugin.name, plugin.type) ] 143 return [ '/admin', 'plugins', 'show', this.pluginService.nameToNpmName(plugin.name, plugin.type) ]
107 } 144 }
145
146 private getUpdatingKey (plugin: PeerTubePlugin) {
147 return plugin.name + plugin.type
148 }
108} 149}
diff --git a/client/src/app/+admin/plugins/shared/plugin-api.service.ts b/client/src/app/+admin/plugins/shared/plugin-api.service.ts
index 1d33cd179..89f190675 100644
--- a/client/src/app/+admin/plugins/shared/plugin-api.service.ts
+++ b/client/src/app/+admin/plugins/shared/plugin-api.service.ts
@@ -9,7 +9,7 @@ import { ComponentPagination } from '@app/shared/rest/component-pagination.model
9import { ResultList } from '@shared/models' 9import { ResultList } from '@shared/models'
10import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' 10import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
11import { ManagePlugin } from '@shared/models/plugins/manage-plugin.model' 11import { ManagePlugin } from '@shared/models/plugins/manage-plugin.model'
12import { InstallPlugin } from '@shared/models/plugins/install-plugin.model' 12import { InstallOrUpdatePlugin } from '@shared/models/plugins/install-plugin.model'
13import { RegisterSettingOptions } from '@shared/models/plugins/register-setting.model' 13import { RegisterSettingOptions } from '@shared/models/plugins/register-setting.model'
14 14
15@Injectable() 15@Injectable()
@@ -89,8 +89,17 @@ export class PluginApiService {
89 .pipe(catchError(res => this.restExtractor.handleError(res))) 89 .pipe(catchError(res => this.restExtractor.handleError(res)))
90 } 90 }
91 91
92 update (pluginName: string, pluginType: PluginType) {
93 const body: ManagePlugin = {
94 npmName: this.nameToNpmName(pluginName, pluginType)
95 }
96
97 return this.authHttp.post(PluginApiService.BASE_APPLICATION_URL + '/update', body)
98 .pipe(catchError(res => this.restExtractor.handleError(res)))
99 }
100
92 install (npmName: string) { 101 install (npmName: string) {
93 const body: InstallPlugin = { 102 const body: InstallOrUpdatePlugin = {
94 npmName 103 npmName
95 } 104 }
96 105
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts
index 86bde2d02..c6ba3dd17 100644
--- a/client/src/app/core/plugins/plugin.service.ts
+++ b/client/src/app/core/plugins/plugin.service.ts
@@ -48,7 +48,9 @@ export class PluginService {
48 .toPromise() 48 .toPromise()
49 } 49 }
50 50
51 addPlugin (plugin: ServerConfigPlugin) { 51 addPlugin (plugin: ServerConfigPlugin, isTheme = false) {
52 const pathPrefix = isTheme ? '/themes' : '/plugins'
53
52 for (const key of Object.keys(plugin.clientScripts)) { 54 for (const key of Object.keys(plugin.clientScripts)) {
53 const clientScript = plugin.clientScripts[key] 55 const clientScript = plugin.clientScripts[key]
54 56
@@ -58,7 +60,7 @@ export class PluginService {
58 this.scopes[scope].push({ 60 this.scopes[scope].push({
59 plugin, 61 plugin,
60 clientScript: { 62 clientScript: {
61 script: environment.apiUrl + `/plugins/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`, 63 script: environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/client-scripts/${clientScript.script}`,
62 scopes: clientScript.scopes 64 scopes: clientScript.scopes
63 } 65 }
64 }) 66 })
diff --git a/client/src/app/shared/buttons/button.component.html b/client/src/app/shared/buttons/button.component.html
index b6df67102..d2b0eb81a 100644
--- a/client/src/app/shared/buttons/button.component.html
+++ b/client/src/app/shared/buttons/button.component.html
@@ -1,4 +1,6 @@
1<span class="action-button" [ngClass]="className" [title]="getTitle()"> 1<span class="action-button" [ngClass]="className" [title]="getTitle()">
2 <my-global-icon [iconName]="icon"></my-global-icon> 2 <my-global-icon *ngIf="!loading" [iconName]="icon"></my-global-icon>
3 <my-small-loader [loading]="loading"></my-small-loader>
4
3 <span class="button-label">{{ label }}</span> 5 <span class="button-label">{{ label }}</span>
4</span> 6</span>
diff --git a/client/src/app/shared/buttons/button.component.scss b/client/src/app/shared/buttons/button.component.scss
index 99d7f51c1..4cc2b0573 100644
--- a/client/src/app/shared/buttons/button.component.scss
+++ b/client/src/app/shared/buttons/button.component.scss
@@ -1,6 +1,12 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4my-small-loader /deep/ .root {
5 display: inline-block;
6 margin: 0 3px 0 0;
7 width: 20px;
8}
9
4.action-button { 10.action-button {
5 @include peertube-button-link; 11 @include peertube-button-link;
6 @include button-with-icon(21px, 0, -2px); 12 @include button-with-icon(21px, 0, -2px);
diff --git a/client/src/app/shared/buttons/button.component.ts b/client/src/app/shared/buttons/button.component.ts
index cf334e8d5..cac5ad210 100644
--- a/client/src/app/shared/buttons/button.component.ts
+++ b/client/src/app/shared/buttons/button.component.ts
@@ -12,6 +12,7 @@ export class ButtonComponent {
12 @Input() className = 'grey-button' 12 @Input() className = 'grey-button'
13 @Input() icon: GlobalIconName = undefined 13 @Input() icon: GlobalIconName = undefined
14 @Input() title: string = undefined 14 @Input() title: string = undefined
15 @Input() loading = false
15 16
16 getTitle () { 17 getTitle () {
17 return this.title || this.label 18 return this.title || this.label
diff --git a/client/src/app/shared/misc/small-loader.component.html b/client/src/app/shared/misc/small-loader.component.html
index 5a7cea738..7886f8918 100644
--- a/client/src/app/shared/misc/small-loader.component.html
+++ b/client/src/app/shared/misc/small-loader.component.html
@@ -1,3 +1,3 @@
1<div *ngIf="loading"> 1<div class="root" *ngIf="loading">
2 <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div> 2 <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div>
3</div> 3</div>
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
index 85fc1c3a0..098496d45 100644
--- a/client/src/app/shared/misc/utils.ts
+++ b/client/src/app/shared/misc/utils.ts
@@ -134,6 +134,23 @@ function scrollToTop () {
134 window.scroll(0, 0) 134 window.scroll(0, 0)
135} 135}
136 136
137// Thanks https://stackoverflow.com/a/16187766
138function compareSemVer (a: string, b: string) {
139 const regExStrip0 = /(\.0+)+$/
140 const segmentsA = a.replace(regExStrip0, '').split('.')
141 const segmentsB = b.replace(regExStrip0, '').split('.')
142
143 const l = Math.min(segmentsA.length, segmentsB.length)
144
145 for (let i = 0; i < l; i++) {
146 const diff = parseInt(segmentsA[ i ], 10) - parseInt(segmentsB[ i ], 10)
147
148 if (diff) return diff
149 }
150
151 return segmentsA.length - segmentsB.length
152}
153
137export { 154export {
138 sortBy, 155 sortBy,
139 durationToString, 156 durationToString,
@@ -144,6 +161,7 @@ export {
144 getAbsoluteAPIUrl, 161 getAbsoluteAPIUrl,
145 dateToHuman, 162 dateToHuman,
146 immutableAssign, 163 immutableAssign,
164 compareSemVer,
147 objectToFormData, 165 objectToFormData,
148 objectLineFeedToHtml, 166 objectLineFeedToHtml,
149 removeElementFromArray, 167 removeElementFromArray,