add versioning

This commit is contained in:
Jakob Schrettenbrunner 2020-04-13 17:52:24 +02:00
parent 01c8a1e8c0
commit ee515d0129
26 changed files with 284 additions and 53 deletions

View File

@ -111,24 +111,51 @@ module.exports = {
{ {
title: 'Panel', title: 'Panel',
collapsable: false, collapsable: false,
children: [ path: '/panel/',
'/panel/getting_started', currentVersion: '0.7',
'/panel/webserver_configuration', versions: [
'/panel/upgrading', {
'/panel/configuration', name: '0.6',
'/panel/troubleshooting', status: 'deprecated',
children: []
},
{
name: '0.7',
status: 'stable',
children: [
'/getting_started',
'/webserver_configuration',
'/upgrading',
'/configuration',
'/troubleshooting',
]
},
{
name: '1.0',
status: 'beta',
children: [
'/getting_started',
]
}
] ]
}, },
{ {
title: 'Daemon', title: 'Daemon',
collapsable: false, collapsable: false,
children: [ path: '/daemon/',
'/daemon/installing', currentVersion: '0.6',
'/daemon/upgrading', versions: [
'/daemon/configuration', {
'/daemon/kernel_modifications', name: '0.6',
'/daemon/debian_8_docker', children: [
'/daemon/standalone_sftp', '/installing',
'/upgrading',
'/configuration',
'/kernel_modifications',
'/debian_8_docker',
'/standalone_sftp',
]
}
] ]
}, },
{ {

View File

@ -1,28 +1,79 @@
<template> <template>
<div class="sidebar-group" :class="{ first, collapsable }"> <div class="sidebar-group" :class="{ first, collapsable }">
<p class="sidebar-heading" :class="{ open }" @click="$emit('toggle')"> <p class="sidebar-heading" :class="{ open }" @click="$emit('toggle')">
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
<span class="arrow" <span class="arrow" v-if="collapsable" :class="open ? 'down' : 'right'"></span>
v-if="collapsable" <VersionSelect
:class="open ? 'down' : 'right'"></span> class="float-right"
</p> v-if="isVersioned"
<DropdownTransition> :versions="item.versions"
<ul class="sidebar-group-items" ref="items" v-if="open || !collapsable"> v-model="versionSelect"
<li v-for="child in item.children"> />
<SidebarLink :item="child"/> </p>
</li> <DropdownTransition>
</ul> <ul class="sidebar-group-items" ref="items" v-if="open || !collapsable">
</DropdownTransition> <li v-for="child in children">
</div> <SidebarLink :item="child" />
</li>
</ul>
</DropdownTransition>
</div>
</template> </template>
<script> <script>
import SidebarLink from './SidebarLink.vue'; import SidebarLink from "./SidebarLink.vue";
import DropdownTransition from './DropdownTransition.vue'; import DropdownTransition from "./DropdownTransition.vue";
import VersionSelect from "./VersionSelect.vue";
export default { export default {
name: 'SidebarGroup', name: "SidebarGroup",
props: ['item', 'first', 'open', 'collapsable'], props: ["item", "first", "open", "collapsable"],
components: { SidebarLink, DropdownTransition } components: { SidebarLink, DropdownTransition, VersionSelect },
data: function() {
let isVersioned = this.item.versions.length > 0;
return {
isVersioned,
versionSelect: isVersioned ? getSelectedVersion(this.item) : ""
}; };
},
watch: {
versionSelect(newVersion, oldVersion) {
if (
oldVersion !== newVersion &&
this.$router.currentRoute.path.startsWith(this.item.path) &&
this.selectedVersion.children.length > 0
) {
this.$router.push(this.selectedVersion.children[0]);
}
},
$route(to, from) {
if (this.isVersioned && to.path.startsWith(this.item.path)) {
const pathVersion = to.path.split("/")[2];
if (~this.item.versions.map(v => v.name).indexOf(pathVersion)) {
this.versionSelect = pathVersion;
}
}
}
},
computed: {
selectedVersion: function() {
return this.item.versions.find(v => v.name == this.versionSelect);
},
children: function() {
return this.isVersioned
? this.selectedVersion.children
: this.item.children;
}
}
};
function getSelectedVersion(item) {
if (window.location.pathname.startsWith(item.path)) {
const pathVersion = window.location.pathname.split("/")[2];
return ~item.versions.map(v => v.name).indexOf(pathVersion)
? pathVersion
: item.currentVersion;
}
return item.currentVersion;
}
</script> </script>

View File

@ -0,0 +1,63 @@
<template>
<div class="version-select custom-select" :tabindex="tabindex" @blur="open = false">
<div class="selected" :class="{open: open}" @click="open = !open">
<VersionSelectItem :version="selected" />
<span class="arrow down"></span>
</div>
<div class="items" :class="{hidden: !open}">
<div
class="item"
v-for="version in versions"
:key="version.name"
@click="selected=version; open=false; $emit('input', version.name)"
>
<VersionSelectItem :version="version" />
</div>
</div>
</div>
</template>
<script>
import VersionSelectItem from "./VersionSelectItem.vue";
export default {
name: "VersionSelect",
components: { VersionSelectItem },
props: {
versions: {
type: Array,
required: true
},
tabindex: {
type: Number,
required: false,
default: 0
},
value: {
type: String,
required: false
}
},
data: function() {
return {
selected:
this.versions.find(v => v.name === this.value) ||
(this.versions.length > 0 ? this.versions[0] : null),
open: false
};
},
watch: {
value(newValue, oldValue) {
if (newValue !== oldValue) {
let version = this.versions.find(v => v.name === this.value);
if (version) {
this.selected = version;
}
}
}
},
mounted() {
this.$emit("input", this.selected.name);
}
};
</script>

View File

@ -0,0 +1,32 @@
<template>
<div class="inline-block">
{{ version.name }}
<span class="rounded-full px-2 ml-1" :class="classes">{{version.status}}</span>
</div>
</template>
<script>
import VersionSelectItem from "./VersionSelectItem.vue";
export default {
name: "VersionSelectItem",
props: {
version: {
type: Object,
required: true
}
},
computed: {
classes() {
return (
{
deprecated: ["text-orange"],
current: ["text-green-dark"],
stable: ["text-green-dark"],
beta: ["text-blue"]
}[this.version.status] || ["text-grey"]
);
}
}
};
</script>

View File

@ -90,6 +90,55 @@ $arrow-bg: #000;
&.open .arrow { &.open .arrow {
top: -0.18em; top: -0.18em;
} }
.version-select {
@apply .relative .inline-block .text-sm;
&:focus {
@apply .outline-none;
}
.selected {
@apply .border .select-none .cursor-pointer .px-4;
border-radius: 1rem;
&.open {
@apply .border-blue .outline-none;
}
&:after {
position: absolute;
content: "";
top: .5em;
right: 10px;
width: 0;
height: 0;
border: 4px solid transparent;
border-color: #fff transparent transparent transparent;
}
}
.items {
@apply .absolute .pin-r .bg-white .border .p-1 .mt-1;
border-radius: 1rem;
z-index: 100;
.item {
@apply .select-none .rounded-full .cursor-pointer .px-2 .border .border-white;
white-space: nowrap;
margin-top: .125rem;
margin-bottom: .125rem;
&:hover, &:focus {
@apply .bg-grey-lighter;
}
&:last-child {
@apply .mb-0;
}
}
}
}
} }
.sidebar-group-items { .sidebar-group-items {

View File

@ -3,32 +3,32 @@ export const extRE = /\.(md|html)$/
export const endingSlashRE = /\/$/ export const endingSlashRE = /\/$/
export const outboundRE = /^(https?:|mailto:|tel:)/ export const outboundRE = /^(https?:|mailto:|tel:)/
export function normalize (path) { export function normalize(path) {
return decodeURI(path) return decodeURI(path)
.replace(hashRE, '') .replace(hashRE, '')
.replace(extRE, '') .replace(extRE, '')
} }
export function getHash (path) { export function getHash(path) {
const match = path.match(hashRE) const match = path.match(hashRE)
if (match) { if (match) {
return match[0] return match[0]
} }
} }
export function isExternal (path) { export function isExternal(path) {
return outboundRE.test(path) return outboundRE.test(path)
} }
export function isMailto (path) { export function isMailto(path) {
return /^mailto:/.test(path) return /^mailto:/.test(path)
} }
export function isTel (path) { export function isTel(path) {
return /^tel:/.test(path) return /^tel:/.test(path)
} }
export function ensureExt (path) { export function ensureExt(path) {
if (isExternal(path)) { if (isExternal(path)) {
return path return path
} }
@ -42,7 +42,7 @@ export function ensureExt (path) {
return normalized + '.html' + hash return normalized + '.html' + hash
} }
export function isActive (route, path) { export function isActive(route, path) {
const routeHash = route.hash const routeHash = route.hash
const linkHash = getHash(path) const linkHash = getHash(path)
if (linkHash && routeHash !== linkHash) { if (linkHash && routeHash !== linkHash) {
@ -53,7 +53,7 @@ export function isActive (route, path) {
return routePath === pagePath return routePath === pagePath
} }
export function resolvePage (pages, rawPath, base) { export function resolvePage(pages, rawPath, base) {
if (base) { if (base) {
rawPath = resolvePath(rawPath, base) rawPath = resolvePath(rawPath, base)
} }
@ -70,7 +70,7 @@ export function resolvePage (pages, rawPath, base) {
return {} return {}
} }
function resolvePath (relative, base, append) { function resolvePath(relative, base, append) {
const firstChar = relative.charAt(0) const firstChar = relative.charAt(0)
if (firstChar === '/') { if (firstChar === '/') {
return relative return relative
@ -108,7 +108,7 @@ function resolvePath (relative, base, append) {
return stack.join('/') return stack.join('/')
} }
export function resolveSidebarItems (page, route, site, localePath) { export function resolveSidebarItems(page, route, site, localePath) {
const { pages, themeConfig } = site const { pages, themeConfig } = site
const localeConfig = localePath && themeConfig.locales const localeConfig = localePath && themeConfig.locales
@ -131,7 +131,7 @@ export function resolveSidebarItems (page, route, site, localePath) {
} }
} }
function resolveHeaders (page) { function resolveHeaders(page) {
const headers = groupHeaders(page.headers || []) const headers = groupHeaders(page.headers || [])
return [{ return [{
type: 'group', type: 'group',
@ -147,7 +147,7 @@ function resolveHeaders (page) {
}] }]
} }
export function groupHeaders (headers) { export function groupHeaders(headers) {
// group h3s under h2 // group h3s under h2
headers = headers.map(h => Object.assign({}, h)) headers = headers.map(h => Object.assign({}, h))
let lastH2 let lastH2
@ -161,13 +161,13 @@ export function groupHeaders (headers) {
return headers.filter(h => h.level === 2) return headers.filter(h => h.level === 2)
} }
export function resolveNavLinkItem (linkItem) { export function resolveNavLinkItem(linkItem) {
return Object.assign(linkItem, { return Object.assign(linkItem, {
type: linkItem.items && linkItem.items.length ? 'links' : 'link' type: linkItem.items && linkItem.items.length ? 'links' : 'link'
}) })
} }
export function resolveMatchingConfig (route, config) { export function resolveMatchingConfig(route, config) {
if (Array.isArray(config)) { if (Array.isArray(config)) {
return { return {
base: '/', base: '/',
@ -185,13 +185,13 @@ export function resolveMatchingConfig (route, config) {
return {} return {}
} }
function ensureEndingSlash (path) { function ensureEndingSlash(path) {
return /(\.html|\/)$/.test(path) return /(\.html|\/)$/.test(path)
? path ? path
: path + '/' : path + '/'
} }
function resolveItem (item, pages, base, isNested) { function resolveItem(item, pages, base, isNested) {
if (typeof item === 'string') { if (typeof item === 'string') {
return resolvePage(pages, item, base) return resolvePage(pages, item, base)
} else if (Array.isArray(item)) { } else if (Array.isArray(item)) {
@ -206,11 +206,18 @@ function resolveItem (item, pages, base, isNested) {
) )
} }
const children = item.children || [] const children = item.children || []
const versions = item.versions || []
return { return {
type: 'group', type: 'group',
title: item.title, ...item,
children: children.map(child => resolveItem(child, pages, base, true)), children: children.map(child => resolveItem(child, pages, base, true)),
collapsable: item.collapsable !== false collapsable: item.collapsable !== false,
versions: versions.map(version => ({
...version,
status: version.name === item.currentVersion ? "current" : version.status,
children: version.children.map(child => resolveItem(item.path + version.name + child, pages, base, true))
})),
} }
} }
} }

View File

@ -148,7 +148,7 @@ Once you have done that there will be a tab called Configuration when you view t
Simply copy and paste the code block and paste it into a file called `core.json` in `/srv/daemon/config` and save it. Simply copy and paste the code block and paste it into a file called `core.json` in `/srv/daemon/config` and save it.
You may also use the Auto-Deployment feature rather than manually creating the files. You may also use the Auto-Deployment feature rather than manually creating the files.
![](./../.vuepress/public/daemon_configuration_example.png) ![](./../../.vuepress/public/daemon_configuration_example.png)
## Starting the Daemon ## Starting the Daemon
To start your daemon simply move into the daemon directory and run the command below which will start the daemon in To start your daemon simply move into the daemon directory and run the command below which will start the daemon in

View File

@ -1,5 +1,7 @@
# Getting Started # Getting Started
BETA
[[toc]] [[toc]]
Pterodactyl Panel is designed to run on your own web server. You will need to have root access to your server in order to run and use this panel. Pterodactyl Panel is designed to run on your own web server. You will need to have root access to your server in order to run and use this panel.