Global client redesign
* Split "my library" into "video space (channels, videos...)" and "my library (playlists, history...)" * Split "admin" into "overview (users, videos...)", "moderation (abuses, blocks, registrations...)" and "settings (configuration, runners...)" * Reorganize the header and the left menu: account settings/notifications are now in the header * Add instance information context in the left menu * Merge dedicated videos pages for "recently added", "trending", "local videos" into a "browse videos" page that includes quick filters * Clean up entire CSS * Clean CSS variables so it's easier to theme PeerTube (some new variables fallback to old variables to limit currnet themes breakages) * Replace the current light theme into a new one (beige) * Add a dark (brown) theme (included in PeerTube core) * Fix accessibility issues with old light theme colors (white on orange button for example) * Redesign the left menu, the horizontal menu, form controls and buttons, "Discover videos" page and common video filters panel * Replace/remove/add some global icon
This commit is contained in:
parent
064a44ec4d
commit
f83674c143
|
@ -8,7 +8,7 @@ export class AdminConfigPage {
|
|||
'basic-configuration': 'APPEARANCE',
|
||||
'instance-information': 'INSTANCE'
|
||||
}
|
||||
await go('/admin/config/edit-custom#' + tab)
|
||||
await go('/admin/settings/config/edit-custom#' + tab)
|
||||
|
||||
await $('h2=' + waitTitles[tab]).waitForDisplayed()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { browserSleep, go } from '../utils'
|
|||
export class AdminPluginPage {
|
||||
|
||||
async navigateToPluginSearch () {
|
||||
await go('/admin/plugins/search')
|
||||
await go('/admin/settings/plugins/search')
|
||||
|
||||
await $('my-plugin-search').waitForDisplayed()
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ export class MyAccountPage {
|
|||
|
||||
async countVideos (names: string[]) {
|
||||
const elements = await $$('.video').filter(async e => {
|
||||
const t = await e.$('.video-miniature-name').getText()
|
||||
const t = await e.$('.video-name').getText()
|
||||
|
||||
return names.some(n => t.includes(n))
|
||||
})
|
||||
|
@ -140,7 +140,7 @@ export class MyAccountPage {
|
|||
private async getVideoElement (name: string) {
|
||||
const video = async () => {
|
||||
const videos = await $$('.video').filter(async e => {
|
||||
const t = await e.$('.video-miniature-name').getText()
|
||||
const t = await e.$('.video-name').getText()
|
||||
|
||||
return t.includes(name)
|
||||
})
|
||||
|
|
|
@ -66,25 +66,25 @@ export class VideoListPage {
|
|||
}
|
||||
|
||||
async getVideosListName () {
|
||||
const elems = await $$('.videos .video-miniature .video-miniature-name')
|
||||
const elems = await $$('.videos .video-miniature .video-name')
|
||||
const texts = await elems.map(e => e.getText())
|
||||
|
||||
return texts.map(t => t.trim())
|
||||
}
|
||||
|
||||
videoExists (name: string) {
|
||||
return $('.video-miniature-name=' + name).isDisplayed()
|
||||
return $('.video-name=' + name).isDisplayed()
|
||||
}
|
||||
|
||||
async videoIsBlurred (name: string) {
|
||||
const filter = await $('.video-miniature-name=' + name).getCSSProperty('filter')
|
||||
const filter = await $('.video-name=' + name).getCSSProperty('filter')
|
||||
|
||||
return filter.value !== 'none'
|
||||
}
|
||||
|
||||
async clickOnVideo (videoName: string) {
|
||||
const video = async () => {
|
||||
const videos = await $$('.videos .video-miniature .video-miniature-name').filter(async e => {
|
||||
const videos = await $$('.videos .video-miniature .video-name').filter(async e => {
|
||||
const t = await e.getText()
|
||||
|
||||
return t === videoName
|
||||
|
@ -106,7 +106,7 @@ export class VideoListPage {
|
|||
|
||||
async clickOnFirstVideo () {
|
||||
const video = () => $('.videos .video-miniature .video-thumbnail')
|
||||
const videoName = () => $('.videos .video-miniature .video-miniature-name')
|
||||
const videoName = () => $('.videos .video-miniature .video-name')
|
||||
|
||||
await video().waitForClickable()
|
||||
|
||||
|
@ -119,7 +119,7 @@ export class VideoListPage {
|
|||
}
|
||||
|
||||
private waitForList () {
|
||||
return $('.videos .video-miniature .video-miniature-name').waitForDisplayed()
|
||||
return $('.videos .video-miniature .video-name').waitForDisplayed()
|
||||
}
|
||||
|
||||
private waitForTitle (title: string) {
|
||||
|
|
|
@ -199,7 +199,7 @@ export class VideoWatchPage {
|
|||
|
||||
await textarea.setValue(comment)
|
||||
|
||||
const confirmButton = await $('.comment-buttons .orange-button')
|
||||
const confirmButton = await $('.comment-buttons .primary-button')
|
||||
await confirmButton.waitForClickable()
|
||||
await confirmButton.click()
|
||||
|
||||
|
@ -218,7 +218,7 @@ export class VideoWatchPage {
|
|||
await textarea.waitForClickable()
|
||||
await textarea.setValue(comment)
|
||||
|
||||
const confirmButton = await $('my-video-comment .comment-buttons .orange-button')
|
||||
const confirmButton = await $('my-video-comment .comment-buttons .primary-button')
|
||||
await confirmButton.waitForClickable()
|
||||
await confirmButton.click()
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
"buffer": "^6.0.3",
|
||||
"chart.js": "^4.3.0",
|
||||
"chartjs-plugin-zoom": "~2.0.1",
|
||||
"color-bits": "^1.0.4",
|
||||
"core-js": "^3.22.8",
|
||||
"debug": "^4.3.1",
|
||||
"dompurify": "^3.1.6",
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{{ follower.name }}
|
||||
</a>
|
||||
|
||||
<button i18n class="peertube-button-link grey-button mt-1" *ngIf="!loadedAllFollowers && canLoadMoreFollowers()" (click)="loadAllFollowers()">Show full list</button>
|
||||
<button i18n class="peertube-button-link secondary-button mt-1" *ngIf="!loadedAllFollowers && canLoadMoreFollowers()" (click)="loadAllFollowers()">Show full list</button>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-6 col-md-12">
|
||||
|
@ -23,7 +23,7 @@
|
|||
{{ following.name }}
|
||||
</a>
|
||||
|
||||
<button i18n class="peertube-button-link grey-button mt-1" *ngIf="!loadedAllFollowings && canLoadMoreFollowings()" (click)="loadAllFollowings()">Show full list</button>
|
||||
<button i18n class="peertube-button-link secondary-button mt-1" *ngIf="!loadedAllFollowings && canLoadMoreFollowings()" (click)="loadAllFollowings()">Show full list</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<div class="banner" *ngIf="instanceBannerUrl">
|
||||
<img [src]="instanceBannerUrl" alt="Instance banner">
|
||||
</div>
|
||||
|
||||
<div class="margin-content mt-4">
|
||||
<div class="banner mb-4" *ngIf="instanceBannerUrl">
|
||||
<img class="rounded" [src]="instanceBannerUrl" alt="Instance banner">
|
||||
</div>
|
||||
|
||||
<div class="row ">
|
||||
<div class="col-md-12 col-xl-6">
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<h1 i18n class="fw-semibold fs-5">About {{ instanceName }}</h1>
|
||||
|
||||
<a routerLink="/about/contact" i18n *ngIf="isContactFormEnabled" class="peertube-button-link orange-button h-100 d-flex align-items-center">Contact us</a>
|
||||
<a routerLink="/about/contact" i18n *ngIf="isContactFormEnabled" class="peertube-button-link primary-button h-100 d-flex align-items-center">Contact us</a>
|
||||
</div>
|
||||
|
||||
<div class="mb-4" *ngIf="categories.length !== 0 || languages.length !== 0">
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
|
||||
.middle-title {
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
color: pvar(--fg);
|
||||
font-weight: $font-bold;
|
||||
|
||||
@include in-content-small-title;
|
||||
@include font-size(22px);
|
||||
@include margin-bottom(1.5rem);
|
||||
}
|
||||
|
||||
|
@ -42,9 +45,6 @@
|
|||
.middle-title,
|
||||
.section-title {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: var(--mainForegroundColor);
|
||||
color: pvar(--fg-400);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,11 +50,11 @@
|
|||
|
||||
<div class="form-group inputs">
|
||||
<input
|
||||
type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button"
|
||||
type="button" role="button" i18n-value value="Cancel" class="peertube-button secondary-button"
|
||||
(click)="hide()" (key.enter)="hide()"
|
||||
>
|
||||
|
||||
<input type="submit" i18n-value value="Submit" class="peertube-button orange-button" [disabled]="!form.valid" />
|
||||
<input type="submit" i18n-value value="Submit" class="peertube-button primary-button" [disabled]="!form.valid" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use '_form-mixins' as *;
|
||||
|
||||
.modal-subtitle {
|
||||
line-height: 1rem;
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
</p>
|
||||
|
||||
<p i18n>
|
||||
It is free and open-source software, under <a class="link-orange" href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE">AGPLv3
|
||||
It is free and open-source software, under <a class="link-primary" href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE">AGPLv3
|
||||
licence</a>.
|
||||
</p>
|
||||
|
||||
<p i18n>
|
||||
For more information, please visit <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">joinpeertube.org</a>.
|
||||
For more information, please visit <a class="link-primary" target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">joinpeertube.org</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
<a i18n class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use/setup-account">Use PeerTube documentation</a>
|
||||
<a i18n class="link-primary" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use/setup-account">Use PeerTube documentation</a>
|
||||
</div>
|
||||
|
||||
<div i18n class="card-text">
|
||||
|
@ -37,7 +37,7 @@
|
|||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
<a i18n class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use/third-party-application">PeerTube Applications</a>
|
||||
<a i18n class="link-primary" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/use/third-party-application">PeerTube Applications</a>
|
||||
</div>
|
||||
|
||||
<div i18n class="card-text">
|
||||
|
@ -49,7 +49,7 @@
|
|||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
<a i18n class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/contribute/getting-started">Contribute on PeerTube</a>
|
||||
<a i18n class="link-primary" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/contribute/getting-started">Contribute on PeerTube</a>
|
||||
</div>
|
||||
|
||||
<div i18n class="card-text">
|
||||
|
@ -112,7 +112,7 @@
|
|||
Web peers are not publicly accessible: because we use the websocket transport, the protocol is different from classic BitTorrent tracker.
|
||||
When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers
|
||||
to forward the information to.
|
||||
See <a class="link-orange" href="https://github.com/yciabaud/webtorrent/blob/beps/bep_webrtc.rst">this document</a> for more information
|
||||
See <a class="link-primary" href="https://github.com/yciabaud/webtorrent/blob/beps/bep_webrtc.rst">this document</a> for more information
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
<div>
|
||||
<div class="sub-menu mb-0" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed }">
|
||||
<a myPluginSelector pluginSelectorId="about-menu-instance" i18n routerLink="instance" routerLinkActive="active" class="sub-menu-entry">Instance</a>
|
||||
|
||||
<a myPluginSelector pluginSelectorId="about-menu-peertube" i18n routerLink="peertube" routerLinkActive="active" class="sub-menu-entry">PeerTube</a>
|
||||
|
||||
<a myPluginSelector pluginSelectorId="about-menu-network" i18n routerLink="follows" routerLinkActive="active" class="sub-menu-entry">Network</a>
|
||||
<div class="margin-content">
|
||||
<my-horizontal-menu [menuEntries]="menuEntries"></my-horizontal-menu>
|
||||
</div>
|
||||
|
||||
<div [ngClass]="{ 'sub-menu-offset-content': !isBroadcastMessageDisplayed }">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
import { Component } from '@angular/core'
|
||||
import { ScreenService } from '@app/core'
|
||||
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'
|
||||
import { PluginSelectorDirective } from '../shared/shared-main/plugins/plugin-selector.directive'
|
||||
import { NgClass } from '@angular/common'
|
||||
import { RouterOutlet } from '@angular/router'
|
||||
import { HorizontalMenuComponent, HorizontalMenuEntry } from '@app/shared/shared-main/menu/horizontal-menu.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-about',
|
||||
templateUrl: './about.component.html',
|
||||
standalone: true,
|
||||
imports: [ NgClass, PluginSelectorDirective, RouterLink, RouterLinkActive, RouterOutlet ]
|
||||
imports: [ RouterOutlet, HorizontalMenuComponent ]
|
||||
})
|
||||
|
||||
export class AboutComponent {
|
||||
constructor (
|
||||
private screenService: ScreenService
|
||||
) { }
|
||||
|
||||
get isBroadcastMessageDisplayed () {
|
||||
return this.screenService.isBroadcastMessageDisplayed
|
||||
menuEntries: HorizontalMenuEntry[] = [
|
||||
{
|
||||
label: $localize`Platform`,
|
||||
routerLink: '/about/instance',
|
||||
pluginSelectorId: 'about-menu-instance'
|
||||
},
|
||||
{
|
||||
label: $localize`PeerTube`,
|
||||
routerLink: '/about/peertube',
|
||||
pluginSelectorId: 'about-menu-peertube'
|
||||
},
|
||||
{
|
||||
label: $localize`Network`,
|
||||
routerLink: '/about/follows',
|
||||
pluginSelectorId: 'about-menu-network'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
></my-actor-avatar>
|
||||
|
||||
<h2 class="fs-5 lh-1 fw-bold m-0">
|
||||
<a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel">
|
||||
<a class="text-decoration-none" [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel">
|
||||
{{ videoChannel.displayName }}
|
||||
</a>
|
||||
</h2>
|
||||
|
@ -35,7 +35,7 @@
|
|||
|
||||
<my-subscribe-button [videoChannels]="[videoChannel]"></my-subscribe-button>
|
||||
|
||||
<a i18n class="button-show-channel peertube-button-link orange-button-inverted" [routerLink]="getVideoChannelLink(videoChannel)">Show this channel</a>
|
||||
<a i18n class="button-show-channel peertube-button-link primary-button" [routerLink]="getVideoChannelLink(videoChannel)">Show this channel</a>
|
||||
|
||||
<div class="videos-overflow-workaround">
|
||||
<div class="videos">
|
||||
|
@ -47,7 +47,7 @@
|
|||
></my-video-miniature>
|
||||
|
||||
<div *ngIf="getTotalVideosOf(videoChannel)" class="miniature-show-channel">
|
||||
<a i18n [routerLink]="getVideoChannelLink(videoChannel)">SHOW THIS CHANNEL ></a>
|
||||
<a class="link-primary" i18n [routerLink]="getVideoChannelLink(videoChannel)">SHOW THIS CHANNEL ></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
.channel {
|
||||
max-width: $max-channels-width;
|
||||
background-color: pvar(--channelBackgroundColor);
|
||||
background-color: pvar(--bg-secondary-350);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
|
||||
a {
|
||||
color: pvar(--mainForegroundColor);
|
||||
color: pvar(--fg);
|
||||
|
||||
@include peertube-word-wrap;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@
|
|||
|
||||
max-height: 80px;
|
||||
|
||||
@include fade-text(50px, pvar(--channelBackgroundColor));
|
||||
@include fade-text(50px, pvar(--bg-secondary-350));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,14 +95,9 @@ my-subscribe-button {
|
|||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: linear-gradient(90deg, transparent 0, pvar(--channelBackgroundColor) 45px);
|
||||
background: linear-gradient(90deg, transparent 0, pvar(--bg-secondary-350) 45px);
|
||||
padding: (math.div($video-thumbnail-medium-height, 2) - 10px) 15px 0 60px;
|
||||
z-index: z(miniature) + 1;
|
||||
|
||||
a {
|
||||
color: pvar(--mainColor);
|
||||
font-weight: $font-semibold;
|
||||
}
|
||||
}
|
||||
|
||||
.button-show-channel {
|
||||
|
@ -130,11 +125,6 @@ my-subscribe-button {
|
|||
}
|
||||
}
|
||||
|
||||
.show-channel a {
|
||||
@include peertube-button-link;
|
||||
@include orange-button-inverted;
|
||||
}
|
||||
|
||||
.videos {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<h1 class="visually-hidden" i18n>Videos</h1>
|
||||
|
||||
<my-videos-list
|
||||
#videosList
|
||||
|
||||
*ngIf="account"
|
||||
|
||||
[title]="title"
|
||||
displayTitle="false"
|
||||
|
||||
[getVideosObservableFunction]="getVideosObservableFunction"
|
||||
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ export class AccountVideosComponent implements OnInit, OnDestroy, DisableForReus
|
|||
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
||||
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
|
||||
|
||||
title = $localize`Videos`
|
||||
defaultSort = '-publishedAt' as VideoSortField
|
||||
|
||||
account: Account
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div *ngIf="account" class="root">
|
||||
<div class="account-info d-md-grid d-block">
|
||||
<div class="margin-content account-info d-md-grid d-block">
|
||||
|
||||
<div class="account-avatar-row">
|
||||
<my-actor-avatar [size]="getAccountAvatarSize()" actorType="account" [actor]="account"></my-actor-avatar>
|
||||
|
@ -48,7 +48,7 @@
|
|||
<div class="description-html" [innerHTML]="accountDescriptionHTML"></div>
|
||||
</div>
|
||||
|
||||
<button *ngIf="hasShowMoreDescription()" class="show-more d-md-none d-block button-unstyle"
|
||||
<button *ngIf="hasShowMoreDescription()" class="show-more peertube-button-like-link d-md-none d-block"
|
||||
(click)="accountDescriptionExpanded = !accountDescriptionExpanded"
|
||||
title="Show the complete description" i18n-title i18n
|
||||
>
|
||||
|
@ -56,7 +56,7 @@
|
|||
</button>
|
||||
|
||||
<div class="buttons">
|
||||
<a *ngIf="isManageable()" routerLink="/my-account" class="peertube-button-link orange-button" i18n>
|
||||
<a *ngIf="isManageable()" routerLink="/my-account" class="peertube-button-link primary-button" i18n>
|
||||
Manage account
|
||||
</a>
|
||||
|
||||
|
@ -64,14 +64,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="links" [ngClass]="{ 'on-channel-page': isOnChannelPage() }">
|
||||
<div class="margin-content horizontal-menu mb-3">
|
||||
<ng-template #linkTemplate let-item="item">
|
||||
<a [routerLink]="item.routerLink" routerLinkActive="active" class="sub-menu-entry">{{ item.label }}</a>
|
||||
</ng-template>
|
||||
|
||||
<my-list-overflow [hidden]="hideMenu" [items]="links" [itemTemplate]="linkTemplate"></my-list-overflow>
|
||||
<my-horizontal-menu [hidden]="hideMenu" [menuEntries]="links"></my-horizontal-menu>
|
||||
|
||||
<my-simple-search-input
|
||||
class="ms-auto"
|
||||
[alwaysShow]="!isInSmallView()" (searchChanged)="searchChanged($event)"
|
||||
(inputDisplayChanged)="onSearchInputDisplayChanged($event)" name="search-videos"
|
||||
i18n-iconTitle icon-title="Search account videos"
|
||||
|
|
|
@ -4,28 +4,21 @@
|
|||
@use '_miniature' as *;
|
||||
|
||||
.root {
|
||||
--myFontSize: 1rem;
|
||||
--myGreyFontSize: 1rem;
|
||||
--co-font-size: 1rem;
|
||||
--co-secondary-font-size: 1rem;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
@include section-label-responsive;
|
||||
my-horizontal-menu {
|
||||
flex-grow: 1;
|
||||
|
||||
@include margin-right(3rem);
|
||||
}
|
||||
|
||||
.links {
|
||||
.horizontal-menu {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@include grid-videos-miniature-margins;
|
||||
|
||||
&.on-channel-page {
|
||||
max-width: $max-channels-width;
|
||||
}
|
||||
|
||||
simple-search-input {
|
||||
@include margin-left(auto);
|
||||
}
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
my-copy-button {
|
||||
|
@ -36,17 +29,12 @@ my-copy-button {
|
|||
grid-template-columns: 1fr min-content;
|
||||
grid-template-rows: auto auto;
|
||||
|
||||
background-color: pvar(--submenuBackgroundColor);
|
||||
|
||||
@include grid-videos-miniature-margins(false, 15px);
|
||||
@include padding-top(3.75rem);
|
||||
@include padding-bottom(3.75rem);
|
||||
@include margin-bottom(3rem);
|
||||
@include font-size(1rem);
|
||||
}
|
||||
|
||||
.account-avatar-row {
|
||||
@include avatar-row-responsive(2rem, var(--myGreyFontSize));
|
||||
@include avatar-row-responsive(2rem, var(--co-secondary-font-size));
|
||||
}
|
||||
|
||||
.actor-display-name {
|
||||
|
@ -93,7 +81,7 @@ my-copy-button {
|
|||
.description:not(.expanded) {
|
||||
max-height: 70px;
|
||||
|
||||
@include fade-text(30px, pvar(--submenuBackgroundColor));
|
||||
@include fade-text(30px, pvar(--bg));
|
||||
}
|
||||
|
||||
.buttons {
|
||||
|
@ -103,8 +91,8 @@ my-copy-button {
|
|||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
.root {
|
||||
--myFontSize: 14px;
|
||||
--myGreyFontSize: 13px;
|
||||
--co-font-size: 14px;
|
||||
--co-secondary-font-size: 13px;
|
||||
}
|
||||
|
||||
.links {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { AccountService } from '@app/shared/shared-main/account/account.service'
|
|||
import { DropdownAction } from '@app/shared/shared-main/buttons/action-dropdown.component'
|
||||
import { VideoChannel } from '@app/shared/shared-main/channel/video-channel.model'
|
||||
import { VideoChannelService } from '@app/shared/shared-main/channel/video-channel.service'
|
||||
import { HorizontalMenuComponent, HorizontalMenuEntry } from '@app/shared/shared-main/menu/horizontal-menu.component'
|
||||
import { VideoService } from '@app/shared/shared-main/video/video.service'
|
||||
import { BlocklistService } from '@app/shared/shared-moderation/blocklist.service'
|
||||
import { AccountReportComponent } from '@app/shared/shared-moderation/report-modals'
|
||||
|
@ -16,7 +17,6 @@ import { Subscription } from 'rxjs'
|
|||
import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'
|
||||
import { ActorAvatarComponent } from '../shared/shared-actor-image/actor-avatar.component'
|
||||
import { CopyButtonComponent } from '../shared/shared-main/buttons/copy-button.component'
|
||||
import { ListOverflowComponent, ListOverflowItem } from '../shared/shared-main/menu/list-overflow.component'
|
||||
import { SimpleSearchInputComponent } from '../shared/shared-main/search/simple-search-input.component'
|
||||
import { AccountBlockBadgesComponent } from '../shared/shared-moderation/account-block-badges.component'
|
||||
import { UserModerationDropdownComponent } from '../shared/shared-moderation/user-moderation-dropdown.component'
|
||||
|
@ -37,11 +37,12 @@ import { SubscribeButtonComponent } from '../shared/shared-user-subscription/sub
|
|||
RouterLink,
|
||||
SubscribeButtonComponent,
|
||||
RouterLinkActive,
|
||||
ListOverflowComponent,
|
||||
HorizontalMenuComponent,
|
||||
SimpleSearchInputComponent,
|
||||
RouterOutlet,
|
||||
AccountReportComponent,
|
||||
DatePipe
|
||||
DatePipe,
|
||||
HorizontalMenuComponent
|
||||
]
|
||||
})
|
||||
export class AccountsComponent implements OnInit, OnDestroy {
|
||||
|
@ -52,7 +53,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
|
|||
|
||||
videoChannels: VideoChannel[] = []
|
||||
|
||||
links: ListOverflowItem[] = []
|
||||
links: HorizontalMenuEntry[] = []
|
||||
hideMenu = false
|
||||
|
||||
accountVideosCount: number
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<div class="root">
|
||||
<a i18n class="visually-hidden-focusable skip-to-content-sub-menu" href="#admin-moderation-content" (click)="$event.preventDefault(); adminContent.focus()">Skip the sub menu</a>
|
||||
|
||||
<div class="margin-content">
|
||||
<my-horizontal-menu i18n-h1 h1="Moderation" h1Icon="moderation" [menuEntries]="menuEntries"></my-horizontal-menu>
|
||||
</div>
|
||||
|
||||
<div #adminContent tabindex="-1" id="admin-moderation-content" class="margin-content outline-0">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,87 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
import { RouterOutlet } from '@angular/router'
|
||||
import { AuthService, ServerService } from '@app/core'
|
||||
import { HorizontalMenuComponent, HorizontalMenuEntry } from '@app/shared/shared-main/menu/horizontal-menu.component'
|
||||
import { UserRight, UserRightType } from '@peertube/peertube-models'
|
||||
|
||||
@Component({
|
||||
templateUrl: './admin-moderation.component.html',
|
||||
standalone: true,
|
||||
imports: [ HorizontalMenuComponent, RouterOutlet ]
|
||||
})
|
||||
export class AdminModerationComponent implements OnInit {
|
||||
menuEntries: HorizontalMenuEntry[] = []
|
||||
|
||||
constructor (
|
||||
private auth: AuthService,
|
||||
private server: ServerService
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.server.configReloaded.subscribe(() => this.buildMenu())
|
||||
|
||||
this.buildMenu()
|
||||
}
|
||||
|
||||
private buildMenu () {
|
||||
this.menuEntries = []
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_ABUSES)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Reports`,
|
||||
routerLink: '/admin/moderation/abuses/list'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_REGISTRATIONS)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Registrations`,
|
||||
routerLink: '/admin/moderation/registrations/list'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Video blocks`,
|
||||
routerLink: '/admin/moderation/video-blocks/list'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST) || this.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) {
|
||||
const item: HorizontalMenuEntry = {
|
||||
label: $localize`Mutes`,
|
||||
routerLink: '',
|
||||
children: []
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) {
|
||||
item.children.push({
|
||||
label: $localize`Muted accounts`,
|
||||
routerLink: '/admin/moderation/blocklist/accounts'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) {
|
||||
item.children.push({
|
||||
label: $localize`Muted servers`,
|
||||
routerLink: '/admin/moderation/blocklist/servers'
|
||||
})
|
||||
}
|
||||
|
||||
item.routerLink = item.children[0].routerLink
|
||||
|
||||
this.menuEntries.push(item)
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_INSTANCE_WATCHED_WORDS)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Watched words`,
|
||||
routerLink: '/admin/moderation/watched-words/list'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private hasRight (right: UserRightType) {
|
||||
return this.auth.getUser().hasRight(right)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<div class="root">
|
||||
<a i18n class="visually-hidden-focusable skip-to-content-sub-menu" href="#admin-overview-content" (click)="$event.preventDefault(); adminContent.focus()">Skip the sub menu</a>
|
||||
|
||||
<div class="margin-content">
|
||||
<my-horizontal-menu i18n-h1 h1="Overview" h1Icon="overview" [menuEntries]="menuEntries"></my-horizontal-menu>
|
||||
</div>
|
||||
|
||||
<div #adminContent tabindex="-1" id="admin-overview-content" class="margin-content outline-0">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,54 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
import { RouterOutlet } from '@angular/router'
|
||||
import { AuthService } from '@app/core'
|
||||
import { HorizontalMenuComponent, HorizontalMenuEntry } from '@app/shared/shared-main/menu/horizontal-menu.component'
|
||||
import { UserRight, UserRightType } from '@peertube/peertube-models'
|
||||
|
||||
@Component({
|
||||
templateUrl: './admin-overview.component.html',
|
||||
standalone: true,
|
||||
imports: [ HorizontalMenuComponent, RouterOutlet ]
|
||||
})
|
||||
export class AdminOverviewComponent implements OnInit {
|
||||
menuEntries: HorizontalMenuEntry[] = []
|
||||
|
||||
constructor (
|
||||
private auth: AuthService
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.buildMenu()
|
||||
}
|
||||
|
||||
private buildMenu () {
|
||||
this.menuEntries = []
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_USERS)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Users`,
|
||||
routerLink: '/admin/overview/users'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Videos`,
|
||||
routerLink: '/admin/overview/videos',
|
||||
queryParams: {
|
||||
search: 'isLocal:true'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.SEE_ALL_COMMENTS)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Comments`,
|
||||
routerLink: '/admin/overview/comments'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private hasRight (right: UserRightType) {
|
||||
return this.auth.getUser().hasRight(right)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<div class="root">
|
||||
<a i18n class="visually-hidden-focusable skip-to-content-sub-menu" href="#admin-settings-content" (click)="$event.preventDefault(); adminContent.focus()">Skip the sub menu</a>
|
||||
|
||||
<div class="margin-content">
|
||||
<my-horizontal-menu i18n-h1 h1="Settings" h1Icon="config" [menuEntries]="menuEntries"></my-horizontal-menu>
|
||||
</div>
|
||||
|
||||
<div #adminContent tabindex="-1" id="admin-settings-content" class="margin-content outline-0">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,179 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
import { RouterOutlet } from '@angular/router'
|
||||
import { AuthService, ServerService } from '@app/core'
|
||||
import { HorizontalMenuComponent, HorizontalMenuEntry } from '@app/shared/shared-main/menu/horizontal-menu.component'
|
||||
import { PluginType, UserRight, UserRightType } from '@peertube/peertube-models'
|
||||
|
||||
@Component({
|
||||
templateUrl: './admin-settings.component.html',
|
||||
standalone: true,
|
||||
imports: [ HorizontalMenuComponent, RouterOutlet ]
|
||||
})
|
||||
export class AdminSettingsComponent implements OnInit {
|
||||
menuEntries: HorizontalMenuEntry[] = []
|
||||
|
||||
constructor (
|
||||
private auth: AuthService,
|
||||
private server: ServerService
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.server.configReloaded.subscribe(() => this.buildMenu())
|
||||
|
||||
this.buildMenu()
|
||||
}
|
||||
|
||||
private buildMenu () {
|
||||
this.menuEntries = []
|
||||
|
||||
this.buildConfigurationItems()
|
||||
this.buildFederationItems()
|
||||
this.buildPluginItems()
|
||||
this.buildRunnerItems()
|
||||
this.buildSystemItems()
|
||||
}
|
||||
|
||||
private buildFederationItems () {
|
||||
if (!this.hasRight(UserRight.MANAGE_SERVER_FOLLOW)) return
|
||||
|
||||
this.menuEntries.push({
|
||||
label: $localize`Federation`,
|
||||
routerLink: '/admin/settings/follows/following-list',
|
||||
children: [
|
||||
{
|
||||
label: $localize`Following`,
|
||||
routerLink: '/admin/settings/follows/following-list'
|
||||
},
|
||||
{
|
||||
label: $localize`Followers`,
|
||||
routerLink: '/admin/settings/follows/followers-list'
|
||||
},
|
||||
{
|
||||
label: $localize`Video redundancies`,
|
||||
routerLink: '/admin/settings/follows/video-redundancies-list'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
private buildConfigurationItems () {
|
||||
if (this.hasRight(UserRight.MANAGE_CONFIGURATION)) {
|
||||
this.menuEntries.push({ label: $localize`Configuration`, routerLink: '/admin/settings/config' })
|
||||
}
|
||||
}
|
||||
|
||||
private buildPluginItems () {
|
||||
if (this.hasRight(UserRight.MANAGE_PLUGINS)) {
|
||||
this.menuEntries.push({
|
||||
label: $localize`Plugins/Themes`,
|
||||
routerLink: '/admin/settings/plugins',
|
||||
queryParams: {
|
||||
pluginType: PluginType.PLUGIN
|
||||
},
|
||||
children: [
|
||||
{
|
||||
label: 'Installed plugins',
|
||||
routerLink: '/admin/settings/plugins/list-installed',
|
||||
queryParams: {
|
||||
pluginType: PluginType.PLUGIN
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search plugins',
|
||||
routerLink: '/admin/settings/plugins/search',
|
||||
queryParams: {
|
||||
pluginType: PluginType.PLUGIN
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Installed themes',
|
||||
routerLink: '/admin/settings/plugins/list-installed',
|
||||
queryParams: {
|
||||
pluginType: PluginType.THEME
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search themes',
|
||||
routerLink: '/admin/settings/plugins/search',
|
||||
queryParams: {
|
||||
pluginType: PluginType.THEME
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private buildRunnerItems () {
|
||||
if (!this.isRemoteRunnersEnabled() || !this.hasRight(UserRight.MANAGE_RUNNERS)) return
|
||||
|
||||
this.menuEntries.push({
|
||||
label: $localize`Runners`,
|
||||
routerLink: '/admin/settings/system/runners/runners-list',
|
||||
children: [
|
||||
{
|
||||
label: $localize`Remote runners`,
|
||||
routerLink: '/admin/settings/system/runners/runners-list'
|
||||
},
|
||||
|
||||
{
|
||||
label: $localize`Runner jobs`,
|
||||
routerLink: '/admin/settings/system/runners/jobs-list'
|
||||
},
|
||||
|
||||
{
|
||||
label: $localize`Registration tokens`,
|
||||
routerLink: '/admin/settings/system/runners/registration-tokens-list'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
private buildSystemItems () {
|
||||
const systemItems: HorizontalMenuEntry = {
|
||||
label: $localize`System`,
|
||||
routerLink: '',
|
||||
children: []
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_JOBS)) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Local jobs`,
|
||||
routerLink: '/admin/settings/system/jobs'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_LOGS)) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Logs`,
|
||||
routerLink: '/admin/settings/system/logs'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_DEBUG)) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Debug`,
|
||||
routerLink: '/admin/settings/system/debug'
|
||||
})
|
||||
}
|
||||
|
||||
if (systemItems.children.length === 0) return
|
||||
|
||||
systemItems.routerLink = systemItems.children[0].routerLink
|
||||
|
||||
this.menuEntries.push(systemItems)
|
||||
}
|
||||
|
||||
private hasRight (right: UserRightType) {
|
||||
return this.auth.getUser().hasRight(right)
|
||||
}
|
||||
|
||||
private isRemoteRunnersEnabled () {
|
||||
const config = this.server.getHTMLConfig()
|
||||
|
||||
return config.transcoding.remoteRunners.enabled ||
|
||||
config.live.transcoding.remoteRunners.enabled ||
|
||||
config.videoStudio.remoteRunners.enabled ||
|
||||
config.videoTranscription.remoteRunners.enabled
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<div class="root">
|
||||
<a i18n class="visually-hidden-focusable skip-to-content-sub-menu" href="#admin-content" (click)="$event.preventDefault(); adminContent.focus()">Skip the sub menu</a>
|
||||
|
||||
<my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown>
|
||||
|
||||
<div #adminContent tabindex="-1" id="admin-content" class="margin-content outline-0" [ngClass]="{ 'sub-menu-offset-content': !isBroadcastMessageDisplayed }">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
my-top-menu-dropdown {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.root {
|
||||
@include sub-menu-h1;
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
import { NgClass } from '@angular/common'
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { RouterOutlet } from '@angular/router'
|
||||
import { AuthService, ScreenService, ServerService } from '@app/core'
|
||||
import { ListOverflowItem } from '@app/shared/shared-main/menu/list-overflow.component'
|
||||
import { TopMenuDropdownParam } from '@app/shared/shared-main/menu/top-menu-dropdown.component'
|
||||
import { UserRight, UserRightType } from '@peertube/peertube-models'
|
||||
import { TopMenuDropdownComponent } from '../shared/shared-main/menu/top-menu-dropdown.component'
|
||||
|
||||
@Component({
|
||||
templateUrl: './admin.component.html',
|
||||
styleUrls: [ './admin.component.scss' ],
|
||||
standalone: true,
|
||||
imports: [ TopMenuDropdownComponent, NgClass, RouterOutlet ]
|
||||
})
|
||||
export class AdminComponent implements OnInit {
|
||||
items: ListOverflowItem[] = []
|
||||
menuEntries: TopMenuDropdownParam[] = []
|
||||
|
||||
constructor (
|
||||
private auth: AuthService,
|
||||
private screen: ScreenService,
|
||||
private server: ServerService
|
||||
) { }
|
||||
|
||||
get isBroadcastMessageDisplayed () {
|
||||
return this.screen.isBroadcastMessageDisplayed
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.server.configReloaded.subscribe(() => this.buildMenu())
|
||||
|
||||
this.buildMenu()
|
||||
}
|
||||
|
||||
private buildMenu () {
|
||||
this.menuEntries = []
|
||||
|
||||
this.buildOverviewItems()
|
||||
this.buildFederationItems()
|
||||
this.buildModerationItems()
|
||||
this.buildConfigurationItems()
|
||||
this.buildPluginItems()
|
||||
this.buildSystemItems()
|
||||
}
|
||||
|
||||
private buildOverviewItems () {
|
||||
const overviewItems: TopMenuDropdownParam = {
|
||||
label: $localize`Overview`,
|
||||
children: []
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_USERS)) {
|
||||
overviewItems.children.push({
|
||||
label: $localize`Users`,
|
||||
routerLink: '/admin/users',
|
||||
iconName: 'user'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
||||
overviewItems.children.push({
|
||||
label: $localize`Videos`,
|
||||
routerLink: '/admin/videos',
|
||||
queryParams: {
|
||||
search: 'isLocal:true'
|
||||
},
|
||||
iconName: 'videos'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.SEE_ALL_COMMENTS)) {
|
||||
overviewItems.children.push({
|
||||
label: $localize`Comments`,
|
||||
routerLink: '/admin/comments',
|
||||
iconName: 'message-circle'
|
||||
})
|
||||
}
|
||||
|
||||
if (overviewItems.children.length !== 0) {
|
||||
this.menuEntries.push(overviewItems)
|
||||
}
|
||||
}
|
||||
|
||||
private buildFederationItems () {
|
||||
if (!this.hasRight(UserRight.MANAGE_SERVER_FOLLOW)) return
|
||||
|
||||
this.menuEntries.push({
|
||||
label: $localize`Federation`,
|
||||
children: [
|
||||
{
|
||||
label: $localize`Following`,
|
||||
routerLink: '/admin/follows/following-list',
|
||||
iconName: 'following'
|
||||
},
|
||||
{
|
||||
label: $localize`Followers`,
|
||||
routerLink: '/admin/follows/followers-list',
|
||||
iconName: 'follower'
|
||||
},
|
||||
{
|
||||
label: $localize`Video redundancies`,
|
||||
routerLink: '/admin/follows/video-redundancies-list',
|
||||
iconName: 'videos'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
private buildModerationItems () {
|
||||
const moderationItems: TopMenuDropdownParam = {
|
||||
label: $localize`Moderation`,
|
||||
children: []
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_REGISTRATIONS)) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Registrations`,
|
||||
routerLink: '/admin/moderation/registrations/list',
|
||||
iconName: 'user'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_ABUSES)) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Reports`,
|
||||
routerLink: '/admin/moderation/abuses/list',
|
||||
iconName: 'flag'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Video blocks`,
|
||||
routerLink: '/admin/moderation/video-blocks/list',
|
||||
iconName: 'cross'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Muted accounts`,
|
||||
routerLink: '/admin/moderation/blocklist/accounts',
|
||||
iconName: 'user-x'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Muted servers`,
|
||||
routerLink: '/admin/moderation/blocklist/servers',
|
||||
iconName: 'peertube-x'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_INSTANCE_WATCHED_WORDS)) {
|
||||
moderationItems.children.push({
|
||||
label: $localize`Watched words`,
|
||||
routerLink: '/admin/moderation/watched-words/list',
|
||||
iconName: 'eye-open'
|
||||
})
|
||||
}
|
||||
|
||||
if (moderationItems.children.length !== 0) this.menuEntries.push(moderationItems)
|
||||
}
|
||||
|
||||
private buildConfigurationItems () {
|
||||
if (this.hasRight(UserRight.MANAGE_CONFIGURATION)) {
|
||||
this.menuEntries.push({ label: $localize`Configuration`, routerLink: '/admin/config' })
|
||||
}
|
||||
}
|
||||
|
||||
private buildPluginItems () {
|
||||
if (this.hasRight(UserRight.MANAGE_PLUGINS)) {
|
||||
this.menuEntries.push({ label: $localize`Plugins/Themes`, routerLink: '/admin/plugins' })
|
||||
}
|
||||
}
|
||||
|
||||
private buildSystemItems () {
|
||||
const systemItems: TopMenuDropdownParam = {
|
||||
label: $localize`System`,
|
||||
children: []
|
||||
}
|
||||
|
||||
if (this.isRemoteRunnersEnabled() && this.hasRight(UserRight.MANAGE_RUNNERS)) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Remote runners`,
|
||||
iconName: 'codesandbox',
|
||||
routerLink: '/admin/system/runners/runners-list'
|
||||
})
|
||||
|
||||
systemItems.children.push({
|
||||
label: $localize`Runner jobs`,
|
||||
iconName: 'globe',
|
||||
routerLink: '/admin/system/runners/jobs-list'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_JOBS)) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Local jobs`,
|
||||
iconName: 'circle-tick',
|
||||
routerLink: '/admin/system/jobs'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_LOGS)) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Logs`,
|
||||
iconName: 'playlists',
|
||||
routerLink: '/admin/system/logs'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasRight(UserRight.MANAGE_DEBUG)) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Debug`,
|
||||
iconName: 'cog',
|
||||
routerLink: '/admin/system/debug'
|
||||
})
|
||||
}
|
||||
|
||||
if (systemItems.children.length !== 0) {
|
||||
this.menuEntries.push(systemItems)
|
||||
}
|
||||
}
|
||||
|
||||
private hasRight (right: UserRightType) {
|
||||
return this.auth.getUser().hasRight(right)
|
||||
}
|
||||
|
||||
private isRemoteRunnersEnabled () {
|
||||
const config = this.server.getHTMLConfig()
|
||||
|
||||
return config.transcoding.remoteRunners.enabled ||
|
||||
config.live.transcoding.remoteRunners.enabled ||
|
||||
config.videoStudio.remoteRunners.enabled ||
|
||||
config.videoTranscription.remoteRunners.enabled
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import { EditCustomConfigComponent } from '@app/+admin/config/edit-custom-config
|
|||
import { UserRightGuard } from '@app/core'
|
||||
import { UserRight } from '@peertube/peertube-models'
|
||||
|
||||
export const ConfigRoutes: Routes = [
|
||||
export const configRoutes: Routes = [
|
||||
{
|
||||
path: 'config',
|
||||
canActivate: [ UserRightGuard ],
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<h2 i18n>APPEARANCE</h2>
|
||||
|
||||
<div i18n class="inner-form-description">
|
||||
Use <a class="link-orange" routerLink="/admin/plugins">plugins & themes</a> for more involved changes, or add slight <a class="link-orange" routerLink="/admin/config/edit-custom" fragment="advanced-configuration">customizations</a>.
|
||||
Use <a class="link-primary" routerLink="/admin/settings/plugins">plugins & themes</a> for more involved changes, or add slight <a class="link-primary" routerLink="/admin/settings/config/edit-custom" fragment="advanced-configuration">customizations</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -14,13 +14,7 @@
|
|||
<div class="form-group">
|
||||
<label i18n for="themeDefault">Theme</label>
|
||||
|
||||
<div class="peertube-select-container">
|
||||
<select formControlName="default" id="themeDefault" class="form-control">
|
||||
<option i18n value="default">{{ getDefaultThemeLabel() }}</option>
|
||||
|
||||
<option *ngFor="let theme of availableThemes" [value]="theme.id">{{ theme.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<my-select-options formControlName="default" inputId="themeDefault" [items]="availableThemes"></my-select-options>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
@ -42,7 +36,7 @@
|
|||
<div class="form-group" formGroupName="trending">
|
||||
<ng-container formGroupName="videos">
|
||||
<ng-container formGroupName="algorithms">
|
||||
<label i18n for="trendingVideosAlgorithmsDefault">Default trending page</label>
|
||||
<label i18n for="trendingVideosAlgorithmsDefault">Default trending algorithm</label>
|
||||
|
||||
<div class="peertube-select-container">
|
||||
<select id="trendingVideosAlgorithmsDefault" formControlName="default" class="form-control">
|
||||
|
@ -151,7 +145,7 @@
|
|||
<div class="title-col">
|
||||
<h2 i18n>NEW USERS</h2>
|
||||
<div i18n class="inner-form-description">
|
||||
Manage <a class="link-orange" routerLink="/admin/users">users</a> to set their quota individually.
|
||||
Manage <a class="link-primary" routerLink="/admin/overview/users">users</a> to set their quota individually.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -296,7 +290,7 @@
|
|||
i18n-labelText labelText="Allow import with HTTP URL (e.g. YouTube)"
|
||||
>
|
||||
<ng-container ngProjectAs="description">
|
||||
<span i18n>⚠️ If enabled, we recommend to use <a class="link-orange" href="https://docs.joinpeertube.org/maintain/configuration#security">a HTTP proxy</a> to prevent private URL access from your PeerTube server</span>
|
||||
<span i18n>⚠️ If enabled, we recommend to use <a class="link-primary" href="https://docs.joinpeertube.org/maintain/configuration#security">a HTTP proxy</a> to prevent private URL access from your PeerTube server</span>
|
||||
</ng-container>
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
@ -393,7 +387,7 @@
|
|||
>
|
||||
<ng-container ngProjectAs="description">
|
||||
<span i18n>
|
||||
Use <a routerLink="/admin/system/runners/runners-list">remote runners</a> to process transcription tasks.
|
||||
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process transcription tasks.
|
||||
Remote runners has to register on your instance first.
|
||||
</span>
|
||||
</ng-container>
|
||||
|
@ -472,7 +466,7 @@
|
|||
<div i18n>⚠️ This functionality depends heavily on the moderation of instances followed by the search index you select.</div>
|
||||
|
||||
<div i18n>
|
||||
You should only use moderated search indexes in production, or <a class="link-orange" href="https://framagit.org/framasoft/peertube/search-index">host your own</a>.
|
||||
You should only use moderated search indexes in production, or <a class="link-primary" href="https://framagit.org/framasoft/peertube/search-index">host your own</a>.
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
@ -595,7 +589,7 @@
|
|||
<div class="title-col">
|
||||
<h2 i18n>FEDERATION</h2>
|
||||
<div i18n class="inner-form-description">
|
||||
Manage <a class="link-orange" routerLink="/admin/follows">relations</a> with other instances.
|
||||
Manage <a class="link-primary" routerLink="/admin/settings/follows">relations</a> with other instances.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -646,7 +640,7 @@
|
|||
<div i18n>⚠️ This functionality requires a lot of attention and extra moderation.</div>
|
||||
|
||||
<span i18n>
|
||||
See <a class="link-orange" href="https://docs.joinpeertube.org/admin/following-instances#automatically-follow-other-instances" rel="noopener noreferrer" target="_blank">the documentation</a> for more information about the expected URL
|
||||
See <a class="link-primary" href="https://docs.joinpeertube.org/admin/following-instances#automatically-follow-other-instances" rel="noopener noreferrer" target="_blank">the documentation</a> for more information about the expected URL
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { NgClass, NgFor, NgIf } from '@angular/common'
|
|||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'
|
||||
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { MenuService, ThemeService } from '@app/core'
|
||||
import { ThemeService } from '@app/core'
|
||||
import { AlertComponent } from '@app/shared/shared-main/common/alert.component'
|
||||
import { HTMLServerConfig } from '@peertube/peertube-models'
|
||||
import { pairwise } from 'rxjs/operators'
|
||||
|
@ -12,7 +12,6 @@ import { PeertubeCheckboxComponent } from '../../../shared/shared-forms/peertube
|
|||
import { SelectCustomValueComponent } from '../../../shared/shared-forms/select/select-custom-value.component'
|
||||
import { SelectOptionsComponent } from '../../../shared/shared-forms/select/select-options.component'
|
||||
import { HelpComponent } from '../../../shared/shared-main/buttons/help.component'
|
||||
import { PeerTubeTemplateDirective } from '../../../shared/shared-main/common/peertube-template.directive'
|
||||
import { UserRealQuotaInfoComponent } from '../../shared/user-real-quota-info.component'
|
||||
import { ConfigService } from '../shared/config.service'
|
||||
|
||||
|
@ -34,7 +33,6 @@ import { ConfigService } from '../shared/config.service'
|
|||
NgClass,
|
||||
UserRealQuotaInfoComponent,
|
||||
SelectOptionsComponent,
|
||||
PeerTubeTemplateDirective,
|
||||
AlertComponent
|
||||
]
|
||||
})
|
||||
|
@ -53,7 +51,6 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
|
|||
|
||||
constructor (
|
||||
private configService: ConfigService,
|
||||
private menuService: MenuService,
|
||||
private themeService: ThemeService
|
||||
) {}
|
||||
|
||||
|
@ -62,7 +59,11 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
|
|||
this.checkSignupField()
|
||||
this.checkImportSyncField()
|
||||
|
||||
this.availableThemes = this.themeService.buildAvailableThemes()
|
||||
this.availableThemes = [
|
||||
this.themeService.getDefaultThemeItem(),
|
||||
|
||||
...this.themeService.buildAvailableThemes()
|
||||
]
|
||||
|
||||
this.exportExpirationOptions = [
|
||||
{ id: 1000 * 3600 * 24, label: $localize`1 day` },
|
||||
|
@ -156,19 +157,24 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
|
|||
}
|
||||
|
||||
buildLandingPageOptions () {
|
||||
this.defaultLandingPageOptions = this.menuService.buildCommonLinks(this.serverConfig)
|
||||
.links
|
||||
.map(o => ({
|
||||
let links: { label: string, path: string }[] = []
|
||||
|
||||
if (this.serverConfig.homepage.enabled) {
|
||||
links.push({ label: $localize`Home`, path: '/home' })
|
||||
}
|
||||
|
||||
links = links.concat([
|
||||
{ label: $localize`Discover`, path: '/videos/overview' },
|
||||
{ label: $localize`Browse videos`, path: '/videos/browse' }
|
||||
])
|
||||
|
||||
this.defaultLandingPageOptions = links.map(o => ({
|
||||
id: o.path,
|
||||
label: o.label,
|
||||
description: o.path
|
||||
}))
|
||||
}
|
||||
|
||||
getDefaultThemeLabel () {
|
||||
return this.themeService.getDefaultThemeLabel()
|
||||
}
|
||||
|
||||
private checkImportSyncField () {
|
||||
const importSyncControl = this.form.get('import.videoChannelSynchronization.enabled')
|
||||
const importVideosHttpControl = this.form.get('import.videos.http.enabled')
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
</span>
|
||||
|
||||
<input
|
||||
class="peertube-button primary-button"
|
||||
(click)="formValidated()" type="submit" i18n-value value="Update configuration"
|
||||
[disabled]="!form.valid || !hasConsistentOptions() || !isUpdateAllowed()"
|
||||
>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use '_form-mixins' as *;
|
||||
|
||||
$form-base-input-width: 340px;
|
||||
$form-max-width: 500px;
|
||||
|
@ -33,8 +34,8 @@ input[type=number] {
|
|||
|
||||
input[type=number] + span {
|
||||
position: absolute;
|
||||
top: 0.2em;
|
||||
right: 2.5rem;
|
||||
top: 0.4em;
|
||||
right: 3em;
|
||||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
display: none;
|
||||
|
@ -42,13 +43,13 @@ input[type=number] {
|
|||
}
|
||||
|
||||
input[disabled] {
|
||||
background-color: #f9f9f9;
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
@include peertube-checkbox(1px);
|
||||
@include peertube-checkbox;
|
||||
}
|
||||
|
||||
.peertube-select-container {
|
||||
|
@ -65,8 +66,6 @@ my-select-custom-value {
|
|||
input[type=submit] {
|
||||
display: flex;
|
||||
|
||||
@include peertube-button;
|
||||
@include orange-button;
|
||||
@include margin-left(auto);
|
||||
|
||||
+ .form-error {
|
||||
|
@ -131,18 +130,6 @@ ngb-tabset:not(.previews) ::ng-deep {
|
|||
height: 0;
|
||||
width: 100%;
|
||||
justify-content: right;
|
||||
|
||||
.callout-link {
|
||||
position: relative;
|
||||
right: 3.3em;
|
||||
top: .3em;
|
||||
font-size: 90%;
|
||||
color: pvar(--mainColor);
|
||||
background-color: pvar(--mainBackgroundColor);
|
||||
padding: 0 .3em;
|
||||
|
||||
@include peertube-button-link;
|
||||
}
|
||||
}
|
||||
|
||||
my-actor-banner-edit {
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
<div class="title-col">
|
||||
<h2 i18n>MODERATION & NSFW</h2>
|
||||
<div i18n class="inner-form-description">
|
||||
Manage <a class="link-orange" routerLink="/admin/users">users</a> to build a moderation team.
|
||||
Manage <a class="link-primary" routerLink="/admin/overview/users">users</a> to build a moderation team.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@
|
|||
>
|
||||
<ng-container ngProjectAs="description">
|
||||
<span i18n>
|
||||
Use <a routerLink="/admin/system/runners/runners-list">remote runners</a> to process live transcoding.
|
||||
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process live transcoding.
|
||||
Remote runners has to register on your instance first.
|
||||
</span>
|
||||
</ng-container>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="title-col"></div>
|
||||
<div class="content-col">
|
||||
|
||||
<div class="callout callout-orange">
|
||||
<div class="callout callout-primary">
|
||||
<span i18n>
|
||||
Estimating a server's capacity to transcode and stream videos isn't easy and we can't tune PeerTube automatically.
|
||||
</span>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<br />
|
||||
|
||||
<span i18n>
|
||||
However, you may want to read <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/admin/configuration#vod-transcoding">our guidelines</a> before tweaking the following values.
|
||||
However, you may want to read <a class="link-primary" target="_blank" rel="noopener noreferrer" href="https://docs.joinpeertube.org/admin/configuration#vod-transcoding">our guidelines</a> before tweaking the following values.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -192,7 +192,7 @@
|
|||
>
|
||||
<ng-container ngProjectAs="description">
|
||||
<span i18n>
|
||||
Use <a routerLink="/admin/system/runners/runners-list">remote runners</a> to process VOD transcoding.
|
||||
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process VOD transcoding.
|
||||
Remote runners has to register on your instance first.
|
||||
</span>
|
||||
</ng-container>
|
||||
|
@ -278,7 +278,7 @@
|
|||
>
|
||||
<ng-container ngProjectAs="description">
|
||||
<span i18n>
|
||||
Use <a routerLink="/admin/system/runners/runners-list">remote runners</a> to process studio transcoding tasks.
|
||||
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process studio transcoding tasks.
|
||||
Remote runners has to register on your instance first.
|
||||
</span>
|
||||
</ng-container>
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="follower" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Followers of your instance</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="followers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
|
@ -16,7 +9,7 @@
|
|||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-action-dropdown
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="primary"
|
||||
[actions]="bulkActions" [entry]="selectedRows"
|
||||
>
|
||||
</my-action-dropdown>
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
|
||||
@include disable-default-a-behaviour;
|
||||
|
||||
&,
|
||||
&:hover {
|
||||
color: pvar(--mainForegroundColor);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 80%;
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
}
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
my-button:first-child {
|
||||
@include margin-right(10px);
|
||||
|
|
|
@ -32,11 +32,11 @@
|
|||
|
||||
<div class="form-group inputs">
|
||||
<input
|
||||
type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button"
|
||||
type="button" role="button" i18n-value value="Cancel" class="peertube-button secondary-button"
|
||||
(click)="hide()" (key.enter)="hide()"
|
||||
>
|
||||
|
||||
<input type="submit" i18n-value value="Follow" class="peertube-button orange-button" [disabled]="!form.valid" />
|
||||
<input type="submit" i18n-value value="Follow" class="peertube-button primary-button" [disabled]="!form.valid" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="following" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Subscriptions of your instance</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="following" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
|
@ -16,7 +9,7 @@
|
|||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-action-dropdown
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="primary"
|
||||
[actions]="bulkActions" [entry]="selectedRows"
|
||||
>
|
||||
</my-action-dropdown>
|
||||
|
@ -27,7 +20,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div class="ms-auto d-flex gap-1">
|
||||
<div class="ms-auto d-flex gap-1 flex-wrap">
|
||||
<my-advanced-input-filter [filters]="searchFilters" (search)="onSearch($event)"></my-advanced-input-filter>
|
||||
|
||||
<my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button>
|
||||
|
|
|
@ -2,19 +2,7 @@
|
|||
@use '_mixins' as *;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
|
||||
@include disable-default-a-behaviour;
|
||||
|
||||
&,
|
||||
&:hover {
|
||||
color: pvar(--mainForegroundColor);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 80%;
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
}
|
||||
color: pvar(--fg);
|
||||
}
|
||||
|
||||
my-delete-button {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { UserRight } from '@peertube/peertube-models'
|
|||
import { FollowersListComponent } from './followers-list'
|
||||
import { FollowingListComponent } from './following-list/following-list.component'
|
||||
|
||||
export const FollowsRoutes: Routes = [
|
||||
export const followsRoutes: Routes = [
|
||||
{
|
||||
path: 'follows',
|
||||
canActivate: [ UserRightGuard ],
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="videos" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Videos redundancies</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-sub-header">
|
||||
<div class="select-filter-block">
|
||||
<label for="displayType" i18n>Display</label>
|
||||
|
|
|
@ -1,20 +1,9 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use '_form-mixins' as *;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
|
||||
@include disable-default-a-behaviour;
|
||||
|
||||
&,
|
||||
&:hover {
|
||||
color: pvar(--mainForegroundColor);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 80%;
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
}
|
||||
color: pvar(--fg);
|
||||
}
|
||||
|
||||
.expansion-block {
|
||||
|
|
|
@ -39,7 +39,7 @@ import { VideoRedundancyInformationComponent } from './video-redundancy-informat
|
|||
]
|
||||
})
|
||||
export class VideoRedundanciesListComponent extends RestTable implements OnInit {
|
||||
private static LOCAL_STORAGE_DISPLAY_TYPE = 'video-redundancies-list-display-type'
|
||||
private static LS_DISPLAY_TYPE = 'video-redundancies-list-display-type'
|
||||
|
||||
videoRedundancies: VideoRedundancy[] = []
|
||||
totalRecords = 0
|
||||
|
@ -211,12 +211,12 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
|
|||
}
|
||||
|
||||
private loadSelectLocalStorage () {
|
||||
const displayType = peertubeLocalStorage.getItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE)
|
||||
const displayType = peertubeLocalStorage.getItem(VideoRedundanciesListComponent.LS_DISPLAY_TYPE)
|
||||
if (displayType) this.displayType = displayType as VideoRedundanciesTarget
|
||||
}
|
||||
|
||||
private saveSelectLocalStorage () {
|
||||
peertubeLocalStorage.setItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE, this.displayType)
|
||||
peertubeLocalStorage.setItem(VideoRedundanciesListComponent.LS_DISPLAY_TYPE, this.displayType)
|
||||
}
|
||||
|
||||
private bytesToHuman (bytes: number) {
|
||||
|
|
|
@ -1,8 +1 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="flag" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Reports</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<my-abuse-list-table viewType="admin"></my-abuse-list-table>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { UserRight } from '@peertube/peertube-models'
|
|||
import { RegistrationListComponent } from './registration-list'
|
||||
import { WatchedWordsListAdminComponent } from './watched-words-list/watched-words-list-admin.component'
|
||||
|
||||
export const ModerationRoutes: Routes = [
|
||||
export const moderationRoutes: Routes = [
|
||||
{
|
||||
path: 'moderation',
|
||||
children: [
|
||||
|
@ -90,7 +90,7 @@ export const ModerationRoutes: Routes = [
|
|||
},
|
||||
{
|
||||
path: 'video-comments/list',
|
||||
redirectTo: '/admin/comments/list',
|
||||
redirectTo: '/admin/overview/comments/list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
|
||||
|
|
|
@ -66,11 +66,11 @@
|
|||
|
||||
<div class="modal-footer inputs">
|
||||
<input
|
||||
type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button"
|
||||
type="button" role="button" i18n-value value="Cancel" class="peertube-button secondary-button"
|
||||
(click)="hide()" (key.enter)="hide()"
|
||||
>
|
||||
|
||||
<input type="submit" [value]="getSubmitValue()" class="peertube-button orange-button" [disabled]="!form.valid">
|
||||
<input type="submit" [value]="getSubmitValue()" class="peertube-button primary-button" [disabled]="!form.valid">
|
||||
</div>
|
||||
</form>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="user" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Registration requests</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="registrations" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
|
||||
|
@ -16,7 +9,7 @@
|
|||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-action-dropdown
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="primary"
|
||||
[actions]="bulkActions" [entry]="selectedRows"
|
||||
>
|
||||
</my-action-dropdown>
|
||||
|
@ -62,7 +55,7 @@
|
|||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
[ngClass]="{ 'show': expanded }" placement="bottom-right top-right left auto" container="body"
|
||||
i18n-label label="Actions" [actions]="registrationActions" [entry]="registration"
|
||||
i18n-label label="Actions" [actions]="registrationActions" [entry]="registration" buttonSize="small"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="cross" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Video blocks</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="blocklist" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
|
||||
|
@ -48,7 +41,7 @@
|
|||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
[ngClass]="{ 'show': expanded }" placement="bottom-right auto" container="body"
|
||||
i18n-label label="Actions" [actions]="videoBlocklistActions" [entry]="videoBlock"
|
||||
i18n-label label="Actions" [actions]="videoBlocklistActions" [entry]="videoBlock" buttonSize="small"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="eye-open" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Instance watched words lists</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<em class="d-block" i18n>Video name/description and comments that contain any of the watched words are automatically tagged with the name of the list.</em>
|
||||
<em class="d-block mb-3" i18n>These automatic tags can be used to filter comments and videos.</em>
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export * from './video-comment-list.component'
|
||||
export * from './video-comment.routes'
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon class="top--1px" iconName="message-circle" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Video comments</h1>
|
||||
|
||||
<my-feed [syndicationItems]="syndicationItems"></my-feed>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<em i18n>This view also shows comments from muted accounts.</em>
|
||||
|
||||
<my-video-comment-list-admin-owner mode="admin"></my-video-comment-list-admin-owner>
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { Routes } from '@angular/router'
|
||||
import { UserRightGuard } from '@app/core'
|
||||
import { UserRight } from '@peertube/peertube-models'
|
||||
import { VideoCommentListComponent } from './video-comment-list.component'
|
||||
|
||||
export const commentRoutes: Routes = [
|
||||
{
|
||||
path: 'comments',
|
||||
canActivate: [ UserRightGuard ],
|
||||
data: {
|
||||
userRight: UserRight.SEE_ALL_COMMENTS
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: VideoCommentListComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Comments list`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,10 +1,132 @@
|
|||
import { Routes } from '@angular/router'
|
||||
import { commentRoutes } from './comments'
|
||||
import { usersRoutes } from './users'
|
||||
import { videosRoutes } from './videos'
|
||||
import { Routes, UrlSegment } from '@angular/router'
|
||||
import { VideoCommentListComponent } from './comments'
|
||||
import { UserCreateComponent, UserListComponent, UserUpdateComponent } from './users'
|
||||
import { VideoListComponent } from './videos'
|
||||
import { UserRightGuard } from '@app/core'
|
||||
import { UserRight } from '@peertube/peertube-models'
|
||||
|
||||
export const OverviewRoutes: Routes = [
|
||||
...commentRoutes,
|
||||
...usersRoutes,
|
||||
...videosRoutes
|
||||
function basePathRedirect ({ url }: { url: UrlSegment[] }) {
|
||||
return `/admin/overview/${url.map(u => u.path).join('/')}`
|
||||
}
|
||||
|
||||
export const overviewRoutes: Routes = [
|
||||
{
|
||||
path: 'comments',
|
||||
pathMatch: 'prefix',
|
||||
redirectTo: basePathRedirect
|
||||
},
|
||||
|
||||
{
|
||||
path: 'videos',
|
||||
pathMatch: 'prefix',
|
||||
redirectTo: basePathRedirect
|
||||
},
|
||||
|
||||
{
|
||||
path: 'users',
|
||||
pathMatch: 'prefix',
|
||||
redirectTo: basePathRedirect
|
||||
},
|
||||
|
||||
{
|
||||
path: 'overview',
|
||||
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'users'
|
||||
},
|
||||
|
||||
{
|
||||
path: 'comments',
|
||||
canActivate: [ UserRightGuard ],
|
||||
data: {
|
||||
userRight: UserRight.SEE_ALL_COMMENTS
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: VideoCommentListComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Comments list`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: 'users',
|
||||
canActivate: [ UserRightGuard ],
|
||||
data: {
|
||||
userRight: UserRight.MANAGE_USERS
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: UserListComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Users list`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: UserCreateComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Create a user`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'update/:id',
|
||||
component: UserUpdateComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Update a user`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: 'videos',
|
||||
canActivate: [ UserRightGuard ],
|
||||
data: {
|
||||
userRight: UserRight.SEE_ALL_VIDEOS
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: VideoListComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Videos list`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from './user-edit'
|
||||
export * from './user-list'
|
||||
export * from './users.routes'
|
||||
|
|
|
@ -108,7 +108,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
|
|||
.subscribe({
|
||||
next: () => {
|
||||
this.notifier.success($localize`User ${userCreate.username} created.`)
|
||||
this.router.navigate([ '/admin/users/list' ])
|
||||
this.router.navigate([ '/admin/overview/users/list' ])
|
||||
},
|
||||
|
||||
error: err => {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<nav aria-label="breadcrumb">
|
||||
<ol class="pt-breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a routerLink="/admin/users" i18n>Users</a>
|
||||
<a routerLink="/admin/overview/users" i18n>Users</a>
|
||||
</li>
|
||||
|
||||
@if (isCreation()) {
|
||||
<li class="breadcrumb-item active" i18n>Create</li>
|
||||
<li class="breadcrumb-item" i18n>Create</li>
|
||||
} @else {
|
||||
<li class="breadcrumb-item active" i18n>Edit</li>
|
||||
<li class="breadcrumb-item" i18n>Edit</li>
|
||||
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a *ngIf="user" [routerLink]="[ '/a', user?.username ]">{{ user?.username }}</a>
|
||||
</li>
|
||||
}
|
||||
|
@ -207,7 +207,7 @@
|
|||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
<input class="peertube-button orange-button" type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
|
||||
<input class="peertube-button primary-button" type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
|
||||
</form>
|
||||
|
||||
<div class="d-none d-xxl-block col-7">
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
@use 'sass:math';
|
||||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use 'form-mixins' as *;
|
||||
@use '_button-mixins' as *;
|
||||
|
||||
$form-base-input-width: 340px;
|
||||
|
||||
|
@ -54,7 +56,7 @@ my-select-custom-value {
|
|||
> a,
|
||||
> div {
|
||||
padding: 20px;
|
||||
background: pvar(--submenuBackgroundColor);
|
||||
background: pvar(--bg-secondary-400);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
|
@ -65,14 +67,14 @@ my-select-custom-value {
|
|||
.dashboard-text {
|
||||
text-align: center;
|
||||
font-size: 130%;
|
||||
color: pvar(--mainForegroundColor);
|
||||
color: pvar(--fg);
|
||||
line-height: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dashboard-label {
|
||||
font-size: 90%;
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
color: pvar(--fg-300);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="togglePasswordVisibility()" type="button">
|
||||
<ng-container *ngIf="!showPassword" i18n>Show</ng-container>
|
||||
<ng-container *ngIf="!!showPassword" i18n>Hide</ng-container>
|
||||
<button class="btn btn-outline-secondary" (click)="togglePasswordVisibility()" type="button">
|
||||
@if (showPassword) {
|
||||
<ng-container i18n>Hide</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>Show</ng-container>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use '_form-mixins' as *;
|
||||
|
||||
input[type=text],
|
||||
input[type=password] {
|
||||
|
|
|
@ -126,7 +126,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
|||
.subscribe({
|
||||
next: () => {
|
||||
this.notifier.success($localize`User ${this.user.username} updated.`)
|
||||
this.router.navigate([ '/admin/users/list' ])
|
||||
this.router.navigate([ '/admin/overview/users/list' ])
|
||||
},
|
||||
|
||||
error: err => {
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="user" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Users</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
|
||||
|
@ -15,12 +8,12 @@
|
|||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-action-dropdown
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="primary"
|
||||
[actions]="bulkActions" [entry]="selectedRows"
|
||||
>
|
||||
</my-action-dropdown>
|
||||
|
||||
<a *ngIf="!isInSelectionMode()" class="peertube-create-button" routerLink="/admin/users/create">
|
||||
<a *ngIf="!isInSelectionMode()" class="peertube-create-button" routerLink="/admin/overview/users/create">
|
||||
<my-global-icon iconName="user-add" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Create user</ng-container>
|
||||
</a>
|
||||
|
@ -97,7 +90,7 @@
|
|||
<td class="action-cell">
|
||||
<my-user-moderation-dropdown
|
||||
*ngIf="!isInSelectionMode()" [user]="user" [account]="user.accountMutedStatus" [displayOptions]="userModerationDisplayOptions"
|
||||
container="body" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()">
|
||||
container="body" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()" buttonSize="small">
|
||||
</my-user-moderation-dropdown>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
@use '_mixins' as *;
|
||||
@use 'bootstrap/scss/functions' as *;
|
||||
|
||||
.sub-title-container my-global-icon {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.banned-info {
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ type UserForList = User & {
|
|||
]
|
||||
})
|
||||
export class UserListComponent extends RestTable <User> implements OnInit {
|
||||
private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns'
|
||||
private static readonly LS_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns'
|
||||
|
||||
@ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
|
||||
|
||||
|
@ -184,7 +184,7 @@ export class UserListComponent extends RestTable <User> implements OnInit {
|
|||
}
|
||||
|
||||
loadSelectedColumns () {
|
||||
const result = this.peertubeLocalStorage.getItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY)
|
||||
const result = this.peertubeLocalStorage.getItem(UserListComponent.LS_SELECTED_COLUMNS_KEY)
|
||||
|
||||
if (result) {
|
||||
try {
|
||||
|
@ -201,7 +201,7 @@ export class UserListComponent extends RestTable <User> implements OnInit {
|
|||
}
|
||||
|
||||
saveSelectedColumns () {
|
||||
this.peertubeLocalStorage.setItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns))
|
||||
this.peertubeLocalStorage.setItem(UserListComponent.LS_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns))
|
||||
}
|
||||
|
||||
getIdentifier () {
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import { Routes } from '@angular/router'
|
||||
import { UserRightGuard } from '@app/core'
|
||||
import { UserRight } from '@peertube/peertube-models'
|
||||
import { UserCreateComponent, UserUpdateComponent } from './user-edit'
|
||||
import { UserListComponent } from './user-list'
|
||||
|
||||
export const usersRoutes: Routes = [
|
||||
{
|
||||
path: 'users',
|
||||
canActivate: [ UserRightGuard ],
|
||||
data: {
|
||||
userRight: UserRight.MANAGE_USERS
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: UserListComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Users list`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: UserCreateComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Create a user`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'update/:id',
|
||||
component: UserUpdateComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Update a user`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,3 +1,2 @@
|
|||
export * from './video-admin.service'
|
||||
export * from './video-list.component'
|
||||
export * from './video.routes'
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="videos" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Videos</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
|
||||
|
@ -16,7 +9,7 @@
|
|||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-action-dropdown
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="primary"
|
||||
[actions]="bulkActions" [entry]="selectedRows"
|
||||
>
|
||||
</my-action-dropdown>
|
||||
|
@ -63,7 +56,7 @@
|
|||
<td class="action-cell">
|
||||
<my-video-actions-dropdown
|
||||
placement="bottom auto" buttonDirection="horizontal" buttonStyled="true" [video]="video" [displayOptions]="videoActionsOptions"
|
||||
(videoRemoved)="reloadData()" (videoFilesRemoved)="reloadData()" (transcodingCreated)="reloadData()"
|
||||
(videoRemoved)="reloadData()" (videoFilesRemoved)="reloadData()" (transcodingCreated)="reloadData()" buttonSize="small"
|
||||
></my-video-actions-dropdown>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ my-embed {
|
|||
|
||||
.right-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 0.5rem;
|
||||
|
||||
> *:not(:last-child) {
|
||||
@include margin-right(10px);
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { Routes } from '@angular/router'
|
||||
import { UserRightGuard } from '@app/core'
|
||||
import { UserRight } from '@peertube/peertube-models'
|
||||
import { VideoListComponent } from './video-list.component'
|
||||
|
||||
export const videosRoutes: Routes = [
|
||||
{
|
||||
path: 'videos',
|
||||
canActivate: [ UserRightGuard ],
|
||||
data: {
|
||||
userRight: UserRight.SEE_ALL_VIDEOS
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: VideoListComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: $localize`Videos list`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,5 +1,3 @@
|
|||
<my-plugin-navigation [pluginType]="pluginType"></my-plugin-navigation>
|
||||
|
||||
<div class="no-results" *ngIf="pagination.totalItems === 0">
|
||||
{{ getNoResultMessage() }}
|
||||
</div>
|
||||
|
@ -8,10 +6,10 @@
|
|||
<ng-container *ngFor="let plugin of plugins">
|
||||
<my-plugin-card [plugin]="plugin" [version]="plugin.version" [pluginType]="pluginType">
|
||||
<div ngProjectAs="buttons">
|
||||
<my-edit-button
|
||||
<my-button
|
||||
*ngIf="!isTheme(plugin)" [ptRouterLink]="getShowRouterLink(plugin)" label="Settings" i18n-label
|
||||
[responsiveLabel]="true"
|
||||
></my-edit-button>
|
||||
[responsiveLabel]="true" icon="config"
|
||||
></my-button>
|
||||
|
||||
<my-button
|
||||
class="update-button" *ngIf="isUpdateAvailable(plugin)" (click)="update(plugin)" [loading]="isUpdating(plugin)"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Subject } from 'rxjs'
|
||||
import { NgFor, NgIf } from '@angular/common'
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
|
||||
|
@ -6,13 +6,11 @@ import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@ap
|
|||
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||
import { compareSemVer } from '@peertube/peertube-core-utils'
|
||||
import { PeerTubePlugin, PluginType, PluginType_Type } from '@peertube/peertube-models'
|
||||
import { DeleteButtonComponent } from '../../../shared/shared-main/buttons/delete-button.component'
|
||||
import { Subject } from 'rxjs'
|
||||
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
|
||||
import { EditButtonComponent } from '../../../shared/shared-main/buttons/edit-button.component'
|
||||
import { PluginCardComponent } from '../shared/plugin-card.component'
|
||||
import { DeleteButtonComponent } from '../../../shared/shared-main/buttons/delete-button.component'
|
||||
import { InfiniteScrollerDirective } from '../../../shared/shared-main/common/infinite-scroller.directive'
|
||||
import { NgIf, NgFor } from '@angular/common'
|
||||
import { PluginNavigationComponent } from '../shared/plugin-navigation.component'
|
||||
import { PluginCardComponent } from '../shared/plugin-card.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-plugin-list-installed',
|
||||
|
@ -20,12 +18,10 @@ import { PluginNavigationComponent } from '../shared/plugin-navigation.component
|
|||
styleUrls: [ './plugin-list-installed.component.scss' ],
|
||||
standalone: true,
|
||||
imports: [
|
||||
PluginNavigationComponent,
|
||||
NgIf,
|
||||
InfiniteScrollerDirective,
|
||||
NgFor,
|
||||
PluginCardComponent,
|
||||
EditButtonComponent,
|
||||
ButtonComponent,
|
||||
DeleteButtonComponent
|
||||
]
|
||||
|
@ -194,7 +190,7 @@ export class PluginListInstalledComponent implements OnInit {
|
|||
}
|
||||
|
||||
getShowRouterLink (plugin: PeerTubePlugin) {
|
||||
return [ '/admin', 'plugins', 'show', this.pluginService.nameToNpmName(plugin.name, plugin.type) ]
|
||||
return [ '/admin', 'settings', 'plugins', 'show', this.pluginService.nameToNpmName(plugin.name, plugin.type) ]
|
||||
}
|
||||
|
||||
getPluginOrThemeHref (name: string) {
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
<my-plugin-navigation [pluginType]="pluginType"></my-plugin-navigation>
|
||||
|
||||
<my-alert class="mt-3" type="primary" i18n *ngIf="pluginInstalled">
|
||||
To load your new installed plugins or themes, refresh the page.
|
||||
</my-alert>
|
||||
|
||||
<div class="result-and-search">
|
||||
<ng-container *ngIf="!search">
|
||||
@if (!search) {
|
||||
<div>
|
||||
<my-global-icon iconName="trending" aria-hidden="true"></my-global-icon>
|
||||
<ng-container *ngIf="!isThemeSearch()" i18n>Popular plugins</ng-container>
|
||||
<ng-container *ngIf="isThemeSearch()" i18n>Popular themes</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="search && !isSearching">
|
||||
@if (isThemeSearch()) {
|
||||
<ng-container i18n>Popular themes</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>Popular plugins</ng-container>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (search && !isSearching) {
|
||||
<my-global-icon iconName="search"></my-global-icon>
|
||||
|
||||
<ng-container i18n>
|
||||
{{ pagination.totalItems }} {pagination.totalItems, plural, =1 {result} other {results}} for "{{ search }}"
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<div class="search-bar">
|
||||
<input type="text" (input)="onSearchChange($event)" i18n-placeholder placeholder="Search..." myAutofocus />
|
||||
|
@ -34,13 +38,15 @@
|
|||
<div ngProjectAs="badges">
|
||||
<span i18n *ngIf="plugin.installed" class="pt-badge badge-success">Installed</span>
|
||||
|
||||
<span *ngIf="plugin.official" class="pt-badge badge-primary" i18n i18n-title title="This plugin is developed by Framasoft, the not-for-profit that develops PeerTube">
|
||||
Official
|
||||
</span>
|
||||
<span
|
||||
*ngIf="plugin.official" class="pt-badge badge-primary"
|
||||
i18n i18n-title title="This plugin is developed by Framasoft, the not-for-profit that develops PeerTube"
|
||||
>Official</span>
|
||||
|
||||
<span *ngIf="plugin.recommended" class="pt-badge badge-primary" i18n i18n-title title="This plugin is recommended by Framasoft, the not-for-profit that develops PeerTube">
|
||||
Recommended
|
||||
</span>
|
||||
<span
|
||||
*ngIf="plugin.recommended" class="pt-badge badge-primary"
|
||||
i18n i18n-title title="This plugin is recommended by Framasoft, the not-for-profit that develops PeerTube"
|
||||
>Recommended</span>
|
||||
</div>
|
||||
|
||||
<div ngProjectAs="buttons">
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use '_form-mixins' as *;
|
||||
|
||||
.result-and-search {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin: 30px 0 15px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
|
||||
my-global-icon {
|
||||
@include global-icon-size(24px);
|
||||
@include margin-right(5px);
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
@include margin-left(auto);
|
||||
|
||||
input {
|
||||
@include peertube-input-text(500px);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import { EditButtonComponent } from '../../../shared/shared-main/buttons/edit-bu
|
|||
import { AutofocusDirective } from '../../../shared/shared-main/common/autofocus.directive'
|
||||
import { InfiniteScrollerDirective } from '../../../shared/shared-main/common/infinite-scroller.directive'
|
||||
import { PluginCardComponent } from '../shared/plugin-card.component'
|
||||
import { PluginNavigationComponent } from '../shared/plugin-navigation.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-plugin-search',
|
||||
|
@ -22,7 +21,6 @@ import { PluginNavigationComponent } from '../shared/plugin-navigation.component
|
|||
styleUrls: [ './plugin-search.component.scss' ],
|
||||
standalone: true,
|
||||
imports: [
|
||||
PluginNavigationComponent,
|
||||
NgIf,
|
||||
GlobalIconComponent,
|
||||
AutofocusDirective,
|
||||
|
@ -138,7 +136,7 @@ export class PluginSearchComponent implements OnInit {
|
|||
}
|
||||
|
||||
getShowRouterLink (plugin: PeerTubePluginIndex) {
|
||||
return [ '/admin', 'plugins', 'show', this.pluginService.nameToNpmName(plugin.name, this.pluginType) ]
|
||||
return [ '/admin', 'settings', 'plugins', 'show', this.pluginService.nameToNpmName(plugin.name, this.pluginType) ]
|
||||
}
|
||||
|
||||
isThemeSearch () {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<ng-container *ngIf="plugin">
|
||||
|
||||
<h2>
|
||||
<h2 class="mb-4">
|
||||
<ng-container>{{ pluginTypeLabel }}</ng-container>
|
||||
{{ plugin.name }}
|
||||
</h2>
|
||||
|
@ -10,7 +10,7 @@
|
|||
<my-dynamic-form-field [hidden]="isSettingHidden(setting)" [form]="form" [setting]="setting" [formErrors]="formErrors"></my-dynamic-form-field>
|
||||
</div>
|
||||
|
||||
<input type="submit" i18n value="Update plugin settings" [disabled]="!form.valid">
|
||||
<input class="peertube-button primary-button mt-3" type="submit" i18n value="Update plugin settings" [disabled]="!form.valid">
|
||||
</form>
|
||||
|
||||
<div *ngIf="!hasRegisteredSettings()" i18n class="no-settings">
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input[type=submit],
|
||||
button {
|
||||
margin-top: 10px;
|
||||
|
||||
@include peertube-button;
|
||||
@include orange-button;
|
||||
}
|
|
@ -15,7 +15,6 @@ import { BuildFormArgument } from '@app/shared/form-validators/form-validator.mo
|
|||
@Component({
|
||||
selector: 'my-plugin-show-installed',
|
||||
templateUrl: './plugin-show-installed.component.html',
|
||||
styleUrls: [ './plugin-show-installed.component.scss' ],
|
||||
standalone: true,
|
||||
imports: [ NgIf, FormsModule, ReactiveFormsModule, NgFor, DynamicFormFieldComponent ]
|
||||
})
|
||||
|
|
|
@ -5,7 +5,7 @@ import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-in
|
|||
import { UserRightGuard } from '@app/core'
|
||||
import { UserRight } from '@peertube/peertube-models'
|
||||
|
||||
export const PluginsRoutes: Routes = [
|
||||
export const pluginsRoutes: Routes = [
|
||||
{
|
||||
path: 'plugins',
|
||||
canActivate: [ UserRightGuard ],
|
||||
|
@ -15,7 +15,7 @@ export const PluginsRoutes: Routes = [
|
|||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list-installed',
|
||||
redirectTo: 'list-installed?pluginType=1',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
|
||||
<span class="plugin-version">{{ version }}</span>
|
||||
|
||||
<a class="plugin-icon" target="_blank" rel="noopener noreferrer" [href]="plugin.homepage" i18n-title title="Plugin homepage (new window)">
|
||||
<a class="plugin-icon" target="_blank" rel="noopener noreferrer" [href]="plugin.homepage" i18n-title title="Homepage (new window)">
|
||||
<my-global-icon iconName="home"></my-global-icon>
|
||||
</a>
|
||||
|
||||
<a class="plugin-icon" target="_blank" rel="noopener noreferrer" [href]="getPluginOrThemeHref(plugin.name)" i18n-title title="Plugin homepage (new window)">
|
||||
<my-global-icon iconName="npm"></my-global-icon>
|
||||
<a class="plugin-icon" target="_blank" rel="noopener noreferrer" [href]="getPluginOrThemeHref(plugin.name)" i18n-title title="NPM page (new window)">
|
||||
<my-global-icon iconName="registry"></my-global-icon>
|
||||
</a>
|
||||
|
||||
<ng-content select="badges"></ng-content>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
.plugin {
|
||||
margin: 15px 0;
|
||||
background-color: pvar(--submenuBackgroundColor);
|
||||
background-color: pvar(--bg-secondary-400);
|
||||
}
|
||||
|
||||
.first-row {
|
||||
|
@ -25,10 +25,10 @@
|
|||
@include margin-left(10px);
|
||||
|
||||
my-global-icon {
|
||||
@include apply-svg-color(pvar(--greyForegroundColor));
|
||||
color: pvar(--fg-400);
|
||||
|
||||
&[iconName=npm] {
|
||||
@include fill-svg-color(pvar(--greyForegroundColor));
|
||||
@include fill-svg-color(pvar(--fg-400));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,11 +51,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
@include peertube-button-link;
|
||||
@include button-with-icon(21px, 0, -2px);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-view) {
|
||||
.first-row {
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<div class="root">
|
||||
<div class="btn-group select-button" role="group" i18n-aria-label aria-label="Navigate between installed plugins and themes or find new ones">
|
||||
<a i18n routerLink="/admin/plugins/list-installed" [queryParams]="{ pluginType: pluginType }" routerLinkActive="active">Installed</a>
|
||||
<a i18n routerLink="/admin/plugins/search" [queryParams]="{ pluginType: pluginType }" routerLinkActive="active">Search</a>
|
||||
</div>
|
||||
|
||||
<div class="btn-group select-button" role="group" i18n-aria-label aria-label="Navigate between plugins and themes">
|
||||
<a i18n [ngClass]="{ active: pluginType === 1 }" routerLink="." [queryParams]="{ pluginType: 1 }" queryParamsHandling="merge" class="">Plugins</a>
|
||||
<a i18n [ngClass]="{ active: pluginType === 2 }" routerLink="." [queryParams]="{ pluginType: 2 }" queryParamsHandling="merge" class="">Themes</a>
|
||||
</div>
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-group:not(:last-child) {
|
||||
@include margin-right(15px);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { Component, Input } from '@angular/core'
|
||||
import { PluginType_Type } from '@peertube/peertube-models'
|
||||
import { NgClass } from '@angular/common'
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router'
|
||||
|
||||
@Component({
|
||||
selector: 'my-plugin-navigation',
|
||||
templateUrl: './plugin-navigation.component.html',
|
||||
styleUrls: [ './plugin-navigation.component.scss' ],
|
||||
standalone: true,
|
||||
imports: [ RouterLink, RouterLinkActive, NgClass ]
|
||||
})
|
||||
export class PluginNavigationComponent {
|
||||
@Input() pluginType: PluginType_Type
|
||||
}
|
|
@ -1,18 +1,12 @@
|
|||
import { Routes } from '@angular/router'
|
||||
import { ConfigRoutes, EditConfigurationService } from '@app/+admin/config'
|
||||
import { ModerationRoutes } from '@app/+admin/moderation/moderation.routes'
|
||||
import { PluginsRoutes } from '@app/+admin/plugins/plugins.routes'
|
||||
import { DebugService, JobService, LogsService, RunnerService, SystemRoutes } from '@app/+admin/system'
|
||||
import { AdminComponent } from './admin.component'
|
||||
import { FollowsRoutes } from './follows'
|
||||
import { OverviewRoutes, VideoAdminService } from './overview'
|
||||
import { AdminRegistrationService } from './moderation/registration-list'
|
||||
import { PluginApiService } from './plugins/shared/plugin-api.service'
|
||||
import { ConfigService } from './config/shared/config.service'
|
||||
import { CustomPageService } from '@app/shared/shared-main/custom-page/custom-page.service'
|
||||
import { Route, Routes, UrlSegment } from '@angular/router'
|
||||
import { configRoutes, EditConfigurationService } from '@app/+admin/config'
|
||||
import { moderationRoutes } from '@app/+admin/moderation/moderation.routes'
|
||||
import { pluginsRoutes } from '@app/+admin/plugins/plugins.routes'
|
||||
import { DebugService, JobService, LogsService, RunnerService, systemRoutes } from '@app/+admin/system'
|
||||
import { CustomMarkupService } from '@app/shared/shared-custom-markup/custom-markup.service'
|
||||
import { DynamicElementService } from '@app/shared/shared-custom-markup/dynamic-element.service'
|
||||
import { InstanceFollowService } from '@app/shared/shared-instance/instance-follow.service'
|
||||
import { CustomPageService } from '@app/shared/shared-main/custom-page/custom-page.service'
|
||||
import { AbuseService } from '@app/shared/shared-moderation/abuse.service'
|
||||
import { BlocklistService } from '@app/shared/shared-moderation/blocklist.service'
|
||||
import { BulkService } from '@app/shared/shared-moderation/bulk.service'
|
||||
|
@ -24,11 +18,17 @@ import { UserAdminService } from '@app/shared/shared-users/user-admin.service'
|
|||
import { VideoCommentService } from '@app/shared/shared-video-comment/video-comment.service'
|
||||
import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service'
|
||||
import { WatchedWordsListService } from '@app/shared/standalone-watched-words/watched-words-list.service'
|
||||
import { AdminModerationComponent } from './admin-moderation.component'
|
||||
import { AdminOverviewComponent } from './admin-overview.component'
|
||||
import { AdminSettingsComponent } from './admin-settings.component'
|
||||
import { ConfigService } from './config/shared/config.service'
|
||||
import { followsRoutes } from './follows'
|
||||
import { AdminRegistrationService } from './moderation/registration-list'
|
||||
import { overviewRoutes, VideoAdminService } from './overview'
|
||||
import { PluginApiService } from './plugins/shared/plugin-api.service'
|
||||
|
||||
export default [
|
||||
{
|
||||
const commonConfig = {
|
||||
path: '',
|
||||
component: AdminComponent,
|
||||
providers: [
|
||||
BlocklistService,
|
||||
UserAdminService,
|
||||
|
@ -55,20 +55,108 @@ export default [
|
|||
SearchService,
|
||||
VideoPlaylistService,
|
||||
WatchedWordsListService
|
||||
]
|
||||
}
|
||||
|
||||
function isOverviewRoute (segments: UrlSegment[]) {
|
||||
if (segments.length === 0) return false
|
||||
|
||||
const rootPath = segments[0].path
|
||||
|
||||
return overviewRoutes.some(r => r.path === rootPath || r.path.startsWith(`${rootPath}/`))
|
||||
}
|
||||
|
||||
function isModerationRoute (segments: UrlSegment[]) {
|
||||
if (segments.length === 0) return false
|
||||
|
||||
const rootPath = segments[0].path
|
||||
|
||||
return moderationRoutes.some(r => r.path === rootPath || r.path.startsWith(`${rootPath}/`))
|
||||
}
|
||||
|
||||
function baseSettingsPathRedirect ({ url }: { url: UrlSegment[] }) {
|
||||
return `/admin/settings/${url.map(u => u.path).join('/')}`
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'overview'
|
||||
},
|
||||
|
||||
{
|
||||
...commonConfig,
|
||||
|
||||
component: AdminModerationComponent,
|
||||
canMatch: [
|
||||
(_route: Route, segments: UrlSegment[]) => {
|
||||
return isModerationRoute(segments)
|
||||
}
|
||||
],
|
||||
|
||||
children: moderationRoutes
|
||||
},
|
||||
|
||||
{
|
||||
...commonConfig,
|
||||
|
||||
component: AdminOverviewComponent,
|
||||
canMatch: [
|
||||
(_route: Route, segments: UrlSegment[]) => {
|
||||
return isOverviewRoute(segments)
|
||||
}
|
||||
],
|
||||
|
||||
children: overviewRoutes
|
||||
},
|
||||
|
||||
{
|
||||
path: 'config',
|
||||
pathMatch: 'prefix',
|
||||
redirectTo: baseSettingsPathRedirect
|
||||
},
|
||||
|
||||
{
|
||||
path: 'follows',
|
||||
pathMatch: 'prefix',
|
||||
redirectTo: baseSettingsPathRedirect
|
||||
},
|
||||
|
||||
{
|
||||
path: 'system',
|
||||
pathMatch: 'prefix',
|
||||
redirectTo: baseSettingsPathRedirect
|
||||
},
|
||||
|
||||
{
|
||||
path: 'plugins',
|
||||
pathMatch: 'prefix',
|
||||
redirectTo: baseSettingsPathRedirect
|
||||
},
|
||||
|
||||
{
|
||||
...commonConfig,
|
||||
|
||||
path: 'settings',
|
||||
component: AdminSettingsComponent,
|
||||
canMatch: [
|
||||
(_route: Route, segments: UrlSegment[]) => {
|
||||
return !isOverviewRoute(segments) && !isModerationRoute(segments)
|
||||
}
|
||||
],
|
||||
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'users',
|
||||
redirectTo: 'config',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
|
||||
...FollowsRoutes,
|
||||
...OverviewRoutes,
|
||||
...ModerationRoutes,
|
||||
...SystemRoutes,
|
||||
...ConfigRoutes,
|
||||
...PluginsRoutes
|
||||
...configRoutes,
|
||||
...followsRoutes,
|
||||
...systemRoutes,
|
||||
...pluginsRoutes
|
||||
]
|
||||
}
|
||||
] satisfies Routes
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@use '_mixins' as *;
|
||||
|
||||
a {
|
||||
color: pvar(--mainForegroundColor);
|
||||
color: pvar(--fg);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Debug</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="fs-5" i18n>IP address</h2>
|
||||
|
||||
<p i18n>PeerTube thinks your web browser public IP is <strong>{{ debug?.ip }}</strong>.</p>
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="circle-tick" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Local jobs</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-sub-header">
|
||||
<div class="select-filter-block">
|
||||
<label for="jobType" i18n>Job type</label>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use 'form-mixins' as *;
|
||||
|
||||
.select-job-state {
|
||||
display: block;
|
||||
|
|
|
@ -38,8 +38,8 @@ import { JobService } from './job.service'
|
|||
]
|
||||
})
|
||||
export class JobsComponent extends RestTable implements OnInit {
|
||||
private static LOCAL_STORAGE_STATE = 'jobs-list-state'
|
||||
private static LOCAL_STORAGE_TYPE = 'jobs-list-type'
|
||||
private static LS_STATE = 'jobs-list-state'
|
||||
private static LS_TYPE = 'jobs-list-type'
|
||||
|
||||
jobState?: JobStateClient
|
||||
jobStates: JobStateClient[] = [ 'all', 'active', 'completed', 'failed', 'waiting', 'delayed' ]
|
||||
|
@ -175,15 +175,15 @@ export class JobsComponent extends RestTable implements OnInit {
|
|||
}
|
||||
|
||||
private loadJobStateAndType () {
|
||||
const state = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_STATE)
|
||||
const state = peertubeLocalStorage.getItem(JobsComponent.LS_STATE)
|
||||
if (state) this.jobState = state as JobState
|
||||
|
||||
const jobType = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE)
|
||||
const jobType = peertubeLocalStorage.getItem(JobsComponent.LS_TYPE)
|
||||
if (jobType) this.jobType = jobType as JobType
|
||||
}
|
||||
|
||||
private saveJobStateAndType () {
|
||||
peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_STATE, this.jobState)
|
||||
peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_TYPE, this.jobType)
|
||||
peertubeLocalStorage.setItem(JobsComponent.LS_STATE, this.jobState)
|
||||
peertubeLocalStorage.setItem(JobsComponent.LS_TYPE, this.jobType)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon iconName="playlists" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Logs</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
|
||||
<div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
@use '_form-mixins' as *;
|
||||
|
||||
.logs {
|
||||
font-family: monospace;
|
||||
|
@ -92,7 +93,7 @@
|
|||
my-copy-button {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
background: pvar(--mainBackgroundHoverColor);
|
||||
background: pvar(--bg-secondary-300);
|
||||
}
|
||||
|
||||
@include on-small-main-col {
|
||||
|
|
|
@ -31,7 +31,7 @@ import { LogsService } from './logs.service'
|
|||
]
|
||||
})
|
||||
export class LogsComponent implements OnInit {
|
||||
private static LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY = 'admin-logs-log-type-choice'
|
||||
private static LS_LOG_TYPE_CHOICE_KEY = 'admin-logs-log-type-choice'
|
||||
|
||||
@ViewChild('logsElement', { static: true }) logsElement: ElementRef<HTMLElement>
|
||||
@ViewChild('logsContent', { static: true }) logsContent: ElementRef<HTMLElement>
|
||||
|
@ -69,7 +69,7 @@ export class LogsComponent implements OnInit {
|
|||
refresh () {
|
||||
this.logs = []
|
||||
|
||||
this.localStorage.setItem(LogsComponent.LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY, this.logType)
|
||||
this.localStorage.setItem(LogsComponent.LS_LOG_TYPE_CHOICE_KEY, this.logType)
|
||||
|
||||
this.load()
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ export class LogsComponent implements OnInit {
|
|||
}
|
||||
|
||||
private loadPreviousChoices () {
|
||||
this.logType = this.localStorage.getItem(LogsComponent.LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY)
|
||||
this.logType = this.localStorage.getItem(LogsComponent.LS_LOG_TYPE_CHOICE_KEY)
|
||||
|
||||
if (this.logType !== 'standard' && this.logType !== 'audit') this.logType = 'audit'
|
||||
}
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<div class="sub-title">
|
||||
<my-global-icon class="top--1px" iconName="globe" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Runner jobs</h1>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a routerLink="/admin/system/runners/runners-list" class="peertube-button-link peertube-button-icon grey-button">
|
||||
<my-global-icon iconName="codesandbox" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Remote runners</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="runnerJobs" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
|
@ -46,13 +32,13 @@
|
|||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-action-dropdown
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
|
||||
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="primary"
|
||||
[actions]="bulkActions" [entry]="selectedRows"
|
||||
>
|
||||
</my-action-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="ms-auto d-flex">
|
||||
<div class="ms-auto d-flex flex-wrap gap-2">
|
||||
<my-advanced-input-filter class="me-2" [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
|
||||
|
||||
<my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button>
|
||||
|
@ -73,7 +59,7 @@
|
|||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
placement="bottom-right top-right left auto" container="body"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="runnerJob"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="runnerJob" buttonSize="small"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<span class="sub-title">
|
||||
<my-global-icon iconName="codesandbox" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Remote runners</h1>
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<a routerLink="/admin/system/runners/registration-tokens-list" class="peertube-button-link peertube-button-icon grey-button">
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Runner registration tokens</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="runners" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
|
@ -36,7 +22,7 @@
|
|||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
placement="bottom-right top-right left auto" container="body"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="runner"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="runner" buttonSize="small"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
<div class="sub-title-container">
|
||||
<span class="sub-title">
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
<h1 i18n>Runner registration tokens</h1>
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<a routerLink="/admin/system/runners/runners-list" class="peertube-button-link peertube-button-icon grey-button">
|
||||
<my-global-icon iconName="codesandbox" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Remote runners</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-table
|
||||
[value]="registrationTokens" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
|
@ -32,7 +18,7 @@
|
|||
<ng-template pTemplate="caption">
|
||||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-button theme="orange" i18n-label label="Generate token" icon="add" (click)="generateToken()"></my-button>
|
||||
<my-button theme="primary" i18n-label label="Generate token" icon="add" (click)="generateToken()"></my-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -42,7 +28,7 @@
|
|||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
placement="bottom-right top-right left auto" container="body"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="registrationToken"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="registrationToken" buttonSize="small"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { JobsComponent } from './jobs/jobs.component'
|
|||
import { LogsComponent } from './logs'
|
||||
import { RunnersRoutes } from './runners'
|
||||
|
||||
export const SystemRoutes: Routes = [
|
||||
export const systemRoutes: Routes = [
|
||||
{
|
||||
path: 'system',
|
||||
children: [
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
<my-login-link className="peertube-button-big-link orange-button mt-5"></my-login-link>
|
||||
<my-login-link className="peertube-button-big-link primary-button mt-5"></my-login-link>
|
||||
</div>
|
||||
} @else if (status === 403) {
|
||||
<div class="box">
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue