Add confirm when admin use custom js/css

This commit is contained in:
Chocobozzz 2018-02-22 15:29:32 +01:00
parent 78967fca4c
commit 1f30a1853e
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
17 changed files with 198 additions and 163 deletions

View File

@ -8,12 +8,7 @@
"root": "src", "root": "src",
"outDir": "dist", "outDir": "dist",
"assets": [ "assets": [
{ "./assets/images",
"glob": "**/*",
"input": "./assets/images",
"output": "./client/assets/images",
"allowOutsideOutDir": false
},
"./manifest.json" "./manifest.json"
], ],
"deployUrl": "client/", "deployUrl": "client/",

View File

@ -23,7 +23,7 @@ export const ConfigRoutes: Routes = [
component: EditCustomConfigComponent, component: EditCustomConfigComponent,
data: { data: {
meta: { meta: {
title: 'Following list' title: 'Edit custom configuration'
} }
} }
} }

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms' import { FormBuilder, FormGroup } from '@angular/forms'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { ConfigService } from '@app/+admin/config/shared/config.service' import { ConfigService } from '@app/+admin/config/shared/config.service'
import { ConfirmService } from '@app/core'
import { ServerService } from '@app/core/server/server.service' import { ServerService } from '@app/core/server/server.service'
import { FormReactive, USER_VIDEO_QUOTA } from '@app/shared' import { FormReactive, USER_VIDEO_QUOTA } from '@app/shared'
import { import {
@ -61,12 +62,16 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
userVideoQuota: USER_VIDEO_QUOTA.MESSAGES userVideoQuota: USER_VIDEO_QUOTA.MESSAGES
} }
private oldCustomJavascript: string
private oldCustomCSS: string
constructor ( constructor (
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router, private router: Router,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private configService: ConfigService, private configService: ConfigService,
private serverService: ServerService private serverService: ServerService,
private confirmService: ConfirmService
) { ) {
super() super()
} }
@ -109,6 +114,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
res => { res => {
this.customConfig = res this.customConfig = res
this.oldCustomCSS = this.customConfig.instance.customizations.css
this.oldCustomJavascript = this.customConfig.instance.customizations.javascript
this.updateForm() this.updateForm()
}, },
@ -124,7 +132,27 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
return this.form.value['signupEnabled'] === true return this.form.value['signupEnabled'] === true
} }
formValidated () { async formValidated () {
const newCustomizationJavascript = this.form.value['customizationJavascript']
const newCustomizationCSS = this.form.value['customizationCSS']
const customizations = []
if (newCustomizationJavascript && newCustomizationJavascript !== this.oldCustomJavascript) customizations.push('JavaScript')
if (newCustomizationCSS && newCustomizationCSS !== this.oldCustomCSS) customizations.push('CSS')
if (customizations.length !== 0) {
const customizationsText = customizations.join('/')
const message = `You set custom ${customizationsText}. ` +
'This could lead to security issues or bugs if you do not understand it. ' +
'Are you sure you want to update the configuration?'
const label = `Please type "I understand the ${customizationsText} I set" to confirm.`
const expectedInputValue = `I understand the ${customizationsText} I set`
const confirmRes = await this.confirmService.confirmWithInput(message, label, expectedInputValue)
if (confirmRes === false) return
}
const data = { const data = {
instance: { instance: {
name: this.form.value['instanceName'], name: this.form.value['instanceName'],

View File

@ -43,7 +43,7 @@ export class FollowingAddComponent {
} }
} }
addFollowing () { async addFollowing () {
this.error = '' this.error = ''
const hosts = this.getNotEmptyHosts() const hosts = this.getNotEmptyHosts()
@ -57,12 +57,11 @@ export class FollowingAddComponent {
} }
const confirmMessage = 'If you confirm, you will send a follow request to:<br /> - ' + hosts.join('<br /> - ') const confirmMessage = 'If you confirm, you will send a follow request to:<br /> - ' + hosts.join('<br /> - ')
this.confirmService.confirm(confirmMessage, 'Follow new server(s)').subscribe( const res = await this.confirmService.confirm(confirmMessage, 'Follow new server(s)')
res => {
if (res === false) return if (res === false) return
this.followService.follow(hosts).subscribe( this.followService.follow(hosts).subscribe(
status => { () => {
this.notificationsService.success('Success', 'Follow request(s) sent!') this.notificationsService.success('Success', 'Follow request(s) sent!')
setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500) setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500)
@ -71,8 +70,6 @@ export class FollowingAddComponent {
err => this.notificationsService.error('Error', err.message) err => this.notificationsService.error('Error', err.message)
) )
} }
)
}
private isHostsUnique (hosts: string[]) { private isHostsUnique (hosts: string[]) {
return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host)) return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host))

View File

@ -25,9 +25,8 @@ export class FollowingListComponent extends RestTable {
super() super()
} }
removeFollowing (follow: AccountFollow) { async removeFollowing (follow: AccountFollow) {
this.confirmService.confirm(`Do you really want to unfollow ${follow.following.host}?`, 'Unfollow').subscribe( const res = await this.confirmService.confirm(`Do you really want to unfollow ${follow.following.host}?`, 'Unfollow')
res => {
if (res === false) return if (res === false) return
this.followService.unfollow(follow).subscribe( this.followService.unfollow(follow).subscribe(
@ -39,8 +38,6 @@ export class FollowingListComponent extends RestTable {
err => this.notificationsService.error('Error', err.message) err => this.notificationsService.error('Error', err.message)
) )
} }
)
}
protected loadData () { protected loadData () {
this.followService.getFollowing(this.pagination, this.sort) this.followService.getFollowing(this.pagination, this.sort)

View File

@ -1,10 +1,10 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { SortMeta } from 'primeng/components/common/sortmeta'
import { NotificationsService } from 'angular2-notifications' import { NotificationsService } from 'angular2-notifications'
import { SortMeta } from 'primeng/components/common/sortmeta'
import { ConfirmService } from '../../../core' import { ConfirmService } from '../../../core'
import { RestTable, RestPagination, User } from '../../../shared' import { RestPagination, RestTable, User } from '../../../shared'
import { UserService } from '../shared' import { UserService } from '../shared'
@Component({ @Component({
@ -27,14 +27,13 @@ export class UserListComponent extends RestTable {
super() super()
} }
removeUser (user: User) { async removeUser (user: User) {
if (user.username === 'root') { if (user.username === 'root') {
this.notificationsService.error('Error', 'You cannot delete root.') this.notificationsService.error('Error', 'You cannot delete root.')
return return
} }
this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe( const res = await this.confirmService.confirm('Do you really want to delete this user?', 'Delete')
res => {
if (res === false) return if (res === false) return
this.userService.removeUser(user).subscribe( this.userService.removeUser(user).subscribe(
@ -46,8 +45,6 @@ export class UserListComponent extends RestTable {
err => this.notificationsService.error('Error', err.message) err => this.notificationsService.error('Error', err.message)
) )
} }
)
}
getRouterUserEditLink (user: User) { getRouterUserEditLink (user: User) {
return [ '/admin', 'users', user.id, 'update' ] return [ '/admin', 'users', user.id, 'update' ]

View File

@ -31,15 +31,14 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
this.loadData() this.loadData()
} }
removeVideoFromBlacklist (entry: BlacklistedVideo) { async removeVideoFromBlacklist (entry: BlacklistedVideo) {
const confirmMessage = 'Do you really want to remove this video from the blacklist ? It will be available again in the video list.' const confirmMessage = 'Do you really want to remove this video from the blacklist ? It will be available again in the video list.'
this.confirmService.confirm(confirmMessage, 'Remove').subscribe( const res = await this.confirmService.confirm(confirmMessage, 'Remove')
res => {
if (res === false) return if (res === false) return
this.videoBlacklistService.removeVideoFromBlacklist(entry.videoId).subscribe( this.videoBlacklistService.removeVideoFromBlacklist(entry.videoId).subscribe(
status => { () => {
this.notificationsService.success('Success', `Video ${entry.name} removed from the blacklist.`) this.notificationsService.success('Success', `Video ${entry.name} removed from the blacklist.`)
this.loadData() this.loadData()
}, },
@ -47,8 +46,6 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
err => this.notificationsService.error('Error', err.message) err => this.notificationsService.error('Error', err.message)
) )
} }
)
}
protected loadData () { protected loadData () {
this.videoBlacklistService.listBlacklist(this.pagination, this.sort) this.videoBlacklistService.listBlacklist(this.pagination, this.sort)

View File

@ -56,13 +56,12 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
return this.videoService.getMyVideos(newPagination, this.sort) return this.videoService.getMyVideos(newPagination, this.sort)
} }
deleteSelectedVideos () { async deleteSelectedVideos () {
const toDeleteVideosIds = Object.keys(this.checkedVideos) const toDeleteVideosIds = Object.keys(this.checkedVideos)
.filter(k => this.checkedVideos[k] === true) .filter(k => this.checkedVideos[k] === true)
.map(k => parseInt(k, 10)) .map(k => parseInt(k, 10))
this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete').subscribe( const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete')
res => {
if (res === false) return if (res === false) return
const observables: Observable<any>[] = [] const observables: Observable<any>[] = []
@ -85,12 +84,9 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
err => this.notificationsService.error('Error', err.message) err => this.notificationsService.error('Error', err.message)
) )
} }
)
}
deleteVideo (video: Video) { async deleteVideo (video: Video) {
this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete').subscribe( const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete')
res => {
if (res === false) return if (res === false) return
this.videoService.removeVideo(video.id) this.videoService.removeVideo(video.id)
@ -104,8 +100,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
error => this.notificationsService.error('Error', error.message) error => this.notificationsService.error('Error', error.message)
) )
} }
)
}
private spliceVideosById (id: number) { private spliceVideosById (id: number) {
for (const key of Object.keys(this.loadedPages)) { for (const key of Object.keys(this.loadedPages)) {

View File

@ -10,13 +10,18 @@
<div class="modal-body" > <div class="modal-body" >
<div [innerHtml]="message"></div> <div [innerHtml]="message"></div>
<div *ngIf="inputLabel && expectedInputValue" class="form-group">
<label for="confirmInput">{{ inputLabel }}</label>
<input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" />
</div>
<div class="form-group inputs"> <div class="form-group inputs">
<span class="action-button action-button-cancel" (click)="cancel()"> <span class="action-button action-button-cancel" (click)="cancel()">
Cancel Cancel
</span> </span>
<input <input
type="submit" value="Confirm" class="action-button-submit" type="submit" value="Confirm" class="action-button-submit" [disabled]="isConfirmationDisabled()"
(click)="confirm()" (click)="confirm()"
> >
</div> </div>

View File

@ -0,0 +1,17 @@
@import '_variables';
@import '_mixins';
.button {
padding: 0 13px;
}
input[type=text] {
@include peertube-input-text(100%);
display: block;
}
.form-group {
margin: 20px 0;
}

View File

@ -4,21 +4,20 @@ import { ModalDirective } from 'ngx-bootstrap/modal'
import { ConfirmService } from './confirm.service' import { ConfirmService } from './confirm.service'
export interface ConfigChangedEvent {
columns: { [id: string]: { isDisplayed: boolean } }
config: { resultsPerPage: number }
}
@Component({ @Component({
selector: 'my-confirm', selector: 'my-confirm',
templateUrl: './confirm.component.html', templateUrl: './confirm.component.html',
styles: [ '.button { padding: 0 13px; }' ] styleUrls: [ './confirm.component.scss' ]
}) })
export class ConfirmComponent implements OnInit { export class ConfirmComponent implements OnInit {
@ViewChild('confirmModal') confirmModal: ModalDirective @ViewChild('confirmModal') confirmModal: ModalDirective
title = '' title = ''
message = '' message = ''
expectedInputValue = ''
inputLabel = ''
inputValue = ''
constructor (private confirmService: ConfirmService) { constructor (private confirmService: ConfirmService) {
// Empty // Empty
@ -31,10 +30,13 @@ export class ConfirmComponent implements OnInit {
} }
this.confirmService.showConfirm.subscribe( this.confirmService.showConfirm.subscribe(
({ title, message }) => { ({ title, message, expectedInputValue, inputLabel }) => {
this.title = title this.title = title
this.message = message this.message = message
this.inputLabel = inputLabel
this.expectedInputValue = expectedInputValue
this.showModal() this.showModal()
} }
) )
@ -52,6 +54,13 @@ export class ConfirmComponent implements OnInit {
this.hideModal() this.hideModal()
} }
isConfirmationDisabled () {
// No input validation
if (!this.inputLabel || !this.expectedInputValue) return false
return this.expectedInputValue !== this.inputValue
}
showModal () { showModal () {
this.confirmModal.show() this.confirmModal.show()
} }

View File

@ -1,15 +1,22 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Subject } from 'rxjs/Subject' import { Subject } from 'rxjs/Subject'
import 'rxjs/add/operator/first' import 'rxjs/add/operator/first'
import 'rxjs/add/operator/toPromise'
@Injectable() @Injectable()
export class ConfirmService { export class ConfirmService {
showConfirm = new Subject<{ title, message }>() showConfirm = new Subject<{ title: string, message: string, inputLabel?: string, expectedInputValue?: string }>()
confirmResponse = new Subject<boolean>() confirmResponse = new Subject<boolean>()
confirm (message = '', title = '') { confirm (message: string, title = '') {
this.showConfirm.next({ title, message }) this.showConfirm.next({ title, message })
return this.confirmResponse.asObservable().first() return this.confirmResponse.asObservable().first().toPromise()
}
confirmWithInput (message: string, inputLabel: string, expectedInputValue: string, title = '') {
this.showConfirm.next({ title, message, inputLabel, expectedInputValue })
return this.confirmResponse.asObservable().first().toPromise()
} }
} }

View File

@ -1,5 +1,6 @@
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { NgModule, Optional, SkipSelf } from '@angular/core' import { NgModule, Optional, SkipSelf } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { LoadingBarModule } from '@ngx-loading-bar/core' import { LoadingBarModule } from '@ngx-loading-bar/core'
@ -18,6 +19,7 @@ import { ServerService } from './server'
imports: [ imports: [
CommonModule, CommonModule,
RouterModule, RouterModule,
FormsModule,
BrowserAnimationsModule, BrowserAnimationsModule,
ModalModule, ModalModule,

View File

@ -15,7 +15,7 @@ export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate>
currentRoute: ActivatedRouteSnapshot, currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot, currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot nextState: RouterStateSnapshot
): Observable<boolean> | boolean { ) {
const result = component.canDeactivate() const result = component.canDeactivate()
const text = result.text || 'All unsaved data will be lost, are you sure you want to leave this page?' const text = result.text || 'All unsaved data will be lost, are you sure you want to leave this page?'

View File

@ -109,12 +109,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
this.viewReplies(commentTree.comment.id) this.viewReplies(commentTree.comment.id)
} }
onWantedToDelete (commentToDelete: VideoComment) { async onWantedToDelete (commentToDelete: VideoComment) {
let message = 'Do you really want to delete this comment?' let message = 'Do you really want to delete this comment?'
if (commentToDelete.totalReplies !== 0) message += `${commentToDelete.totalReplies} would be deleted too.` if (commentToDelete.totalReplies !== 0) message += `${commentToDelete.totalReplies} would be deleted too.`
this.confirmService.confirm(message, 'Delete').subscribe( const res = await this.confirmService.confirm(message, 'Delete')
res => {
if (res === false) return if (res === false) return
this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id) this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id)
@ -140,8 +139,6 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
err => this.notificationsService.error('Error', err.message) err => this.notificationsService.error('Error', err.message)
) )
} }
)
}
isUserLoggedIn () { isUserLoggedIn () {
return this.authService.isLoggedIn() return this.authService.isLoggedIn()

View File

@ -130,11 +130,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
} }
} }
blacklistVideo (event: Event) { async blacklistVideo (event: Event) {
event.preventDefault() event.preventDefault()
this.confirmService.confirm('Do you really want to blacklist this video?', 'Blacklist').subscribe( const res = await this.confirmService.confirm('Do you really want to blacklist this video?', 'Blacklist')
res => {
if (res === false) return if (res === false) return
this.videoBlacklistService.blacklistVideo(this.video.id) this.videoBlacklistService.blacklistVideo(this.video.id)
@ -147,8 +146,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
error => this.notificationsService.error('Error', error.message) error => this.notificationsService.error('Error', error.message)
) )
} }
)
}
showMoreDescription () { showMoreDescription () {
if (this.completeVideoDescription === undefined) { if (this.completeVideoDescription === undefined) {
@ -236,12 +233,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
return this.video.isRemovableBy(this.authService.getUser()) return this.video.isRemovableBy(this.authService.getUser())
} }
removeVideo (event: Event) { async removeVideo (event: Event) {
event.preventDefault() event.preventDefault()
this.confirmService.confirm('Do you really want to delete this video?', 'Delete') const res = await this.confirmService.confirm('Do you really want to delete this video?', 'Delete')
.subscribe(
res => {
if (res === false) return if (res === false) return
this.videoService.removeVideo(this.video.id) this.videoService.removeVideo(this.video.id)
@ -256,8 +251,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
error => this.notificationsService.error('Error', error.message) error => this.notificationsService.error('Error', error.message)
) )
} }
)
}
private updateVideoDescription (description: string) { private updateVideoDescription (description: string) {
this.video.description = description this.video.description = description

View File

@ -10,7 +10,7 @@ import { VideoModel } from '../models/video/video'
const clientsRouter = express.Router() const clientsRouter = express.Router()
const distPath = join(root(), 'client', 'dist') const distPath = join(root(), 'client', 'dist')
const assetsImagesPath = join(root(), 'client', 'dist', 'client', 'assets', 'images') const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images')
const embedPath = join(distPath, 'standalone', 'videos', 'embed.html') const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
const indexPath = join(distPath, 'index.html') const indexPath = join(distPath, 'index.html')