Display dynamically fetched stats
This commit is contained in:
@@ -7,123 +7,107 @@
|
|||||||
|
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
{#if template.type}
|
{#if template.type}
|
||||||
<div class="row">
|
<span class="lbl">Type</span>
|
||||||
<span class="lbl">Type</span>
|
{#if template.type === 1}
|
||||||
{#if template.type === 1}
|
<span class="val">Container</span>
|
||||||
<span>Container</span>
|
{:else if template.type === 2}
|
||||||
{:else if template.type === 2}
|
<span class="val">Swarm</span>
|
||||||
<span>Swarm</span>
|
{:else if template.type === 3}
|
||||||
{:else if template.type === 3}
|
<span class="val">Kubernetes</span>
|
||||||
<span>Kubernetes</span>
|
{:else}
|
||||||
{:else}
|
<span class="val">Unknown</span>
|
||||||
<span>Unknown</span>
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.platform}
|
{#if template.platform}
|
||||||
<div class="row">
|
<span class="lbl">Platform</span>
|
||||||
<span class="lbl">Platform</span>
|
<code class="val">{template.platform}</code>
|
||||||
<code>{template.platform}</code>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.image}
|
{#if template.image}
|
||||||
<div class="row">
|
<span class="lbl">Image</span>
|
||||||
<span class="lbl">Image</span>
|
<code class="val">{template.image}</code>
|
||||||
<code>{template.image}</code>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.command}
|
{#if template.command}
|
||||||
<div class="row">
|
<span class="lbl">Command</span>
|
||||||
<span class="lbl">Command</span>
|
<code class="val">{template.command}</code>
|
||||||
<code>{template.command}</code>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if typeof template.interactive === 'boolean'}
|
{#if typeof template.interactive === 'boolean'}
|
||||||
<div class="row">
|
<span class="lbl">Interactive</span>
|
||||||
<span class="lbl">Interactive</span>
|
<code class="val">{template.interactive ? 'Yes' : 'No'}</code>
|
||||||
<code>{template.interactive ? 'Yes' : 'No'}</code>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.ports}
|
{#if template.ports}
|
||||||
<div class="row">
|
<span class="lbl">Ports</span>
|
||||||
<span class="lbl">Ports</span>
|
<p class="val">
|
||||||
<p>
|
{#each template.ports as port}<code>{port}</code>{/each}
|
||||||
{#each template.ports as port}<code>{port}</code>{/each}
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.volumes}
|
{#if template.volumes}
|
||||||
<div class="row">
|
<span class="lbl">Volumes</span>
|
||||||
<span class="lbl">Volumes</span>
|
<p class="val">
|
||||||
<p>
|
{#each template.volumes as volume}
|
||||||
{#each template.volumes as volume}<code>{volume.container || volume}</code>{/each}
|
<code>
|
||||||
</p>
|
{volume.container || volume}{volume?.bind? ' : ' + volume.bind : ''}
|
||||||
</div>
|
</code>{/each}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.restart_policy}
|
{#if template.restart_policy}
|
||||||
<div class="row">
|
<span class="lbl">Restart Policy</span>
|
||||||
<span class="lbl">Restart Policy</span>
|
<code class="val">{template.restart_policy}</code>
|
||||||
<code>{template.restart_policy}</code>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.repository}
|
{#if template.repository}
|
||||||
<div class="row">
|
|
||||||
<span class="lbl">Sourced</span>
|
<span class="lbl">Sourced</span>
|
||||||
<a href={template.repository.url}>Repo</a>
|
<a class="val" href={template.repository.url}>Repo</a>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.entrypoint}
|
{#if template.entrypoint}
|
||||||
<div class="row">
|
|
||||||
<span class="lbl">Entrypoint</span>
|
<span class="lbl">Entrypoint</span>
|
||||||
<code>{template.entrypoint}</code>
|
<code class="val">{template.entrypoint}</code>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.build}
|
{#if template.build}
|
||||||
<div class="row">
|
|
||||||
<span class="lbl">Build</span>
|
<span class="lbl">Build</span>
|
||||||
<code>{template.build}</code>
|
<code class="val">{template.build}</code>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if template.env}
|
{#if template.env}
|
||||||
<div class="row">
|
|
||||||
<span class="lbl">Env Vars</span>
|
<span class="lbl">Env Vars</span>
|
||||||
<p>
|
<p class="val">
|
||||||
{#each template.env as env}<code>{env.name}={env.set || env.value || env.default}</code>{/each}
|
{#each template.env as env}<code>{env.name}={env.set || env.value || env.default || '\'\''}</code>{/each}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.stats {
|
.stats {
|
||||||
min-width: 15rem;
|
min-width: 15rem;
|
||||||
border: 2px solid var(--background);
|
padding: 0.5rem;
|
||||||
|
gap: 0.5rem;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
.row {
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
place-items: baseline;
|
||||||
|
background: var(--card-2);
|
||||||
|
|
||||||
|
.lbl {
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.val {
|
||||||
|
max-width: 10rem;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space:nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
flex-wrap: wrap;
|
}
|
||||||
padding: 0.5rem;
|
|
||||||
gap: 0.5rem;
|
a {
|
||||||
&:not(:last-child) {
|
color: var(--accent);
|
||||||
border-bottom: 2px dotted var(--background);
|
|
||||||
}
|
|
||||||
span {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.lbl {
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
min-width: 5rem;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1 +1,60 @@
|
|||||||
<p>Temp no Found</p>
|
<script lang="ts">
|
||||||
|
import { gitHubRepo } from '$src/constants';
|
||||||
|
export let templateName: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Template not Found 😢</h2>
|
||||||
|
<p class="subtitle">It doesn't look like there was a templated named "<i>{templateName}</i>"</p>
|
||||||
|
<p>
|
||||||
|
You can try <a href="/">searching for another</a>, or if you think there's a mistake somewhere,
|
||||||
|
please open an issue on the <a href={gitHubRepo} target="_blank">Github Repo</a>.
|
||||||
|
</p>
|
||||||
|
<a class="back-home" href="/">Back Home</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
section {
|
||||||
|
background: var(--card);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 1rem auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 1rem auto;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 40rem;
|
||||||
|
a {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.back-home {
|
||||||
|
background: var(--background);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: Kanit;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
background: var(--gradient);
|
||||||
|
transform: scale(1.1) rotate(-1deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -9,6 +8,7 @@
|
|||||||
--foreground: #ffffff;
|
--foreground: #ffffff;
|
||||||
--accent: #0ba5ec;
|
--accent: #0ba5ec;
|
||||||
--card: #1d2939;
|
--card: #1d2939;
|
||||||
|
--card-2: #192432;
|
||||||
--shadow: 1px 1px 3px 3px #0B9AEC8F;
|
--shadow: 1px 1px 3px 3px #0B9AEC8F;
|
||||||
--gradient: linear-gradient(to right,#0B9AEC 0%,#6EDFDE 100%);
|
--gradient: linear-gradient(to right,#0B9AEC 0%,#6EDFDE 100%);
|
||||||
--max-width: 1800px;
|
--max-width: 1800px;
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
background: var(--background);
|
background: var(--background);
|
||||||
}
|
}
|
||||||
:global(::selection) {
|
:global(::selection) {
|
||||||
background: var(--card);
|
background: var(--accent);
|
||||||
color: var(--accent);
|
color: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,18 +1,107 @@
|
|||||||
import { get } from 'svelte/store';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
|
import { get } from 'svelte/store';
|
||||||
import { templatesUrl } from '$src/constants';
|
import { templatesUrl } from '$src/constants';
|
||||||
import { templates } from '$src/store';
|
import { templates } from '$src/store';
|
||||||
|
|
||||||
export const load = async () => {
|
/* Based on the current page name, find the corresponding template */
|
||||||
if (get(templates) && get(templates).length > 0) {
|
const findTemplate = (templates: any, slug: string) => {
|
||||||
return {
|
return templates.find((temp: Template) =>
|
||||||
templates: get(templates),
|
temp.title.toLowerCase().replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-') === slug
|
||||||
}
|
);
|
||||||
} else {
|
};
|
||||||
const data = await fetch(templatesUrl).then((res) => res.json());
|
|
||||||
templates.set(data.templates);
|
/* With a given image name, fetch stats from DockerHub registry */
|
||||||
return {
|
const getDockerHubStats = async (image: string): Promise<DockerHubResponse | null> => {
|
||||||
templates: data.templates,
|
if (!image) return null;
|
||||||
|
const [imageName, tag] = image.split(':');
|
||||||
|
const [namespace, repo] = imageName.includes('/') ? imageName.split('/') : ['library', imageName];
|
||||||
|
const apiEndpoint = `https://hub.docker.com/v2/repositories/${namespace}/${repo}/`;
|
||||||
|
|
||||||
|
return await fetch(apiEndpoint)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const getServices = async (template): Promise<Service[]> => {
|
||||||
|
try {
|
||||||
|
if (template?.repository) {
|
||||||
|
const { url: repoUrl, stackfile } = template.repository;
|
||||||
|
const path = `${repoUrl.replace('github.com', 'raw.githubusercontent.com')}/HEAD/${stackfile}`;
|
||||||
|
const response = await fetch(path);
|
||||||
|
const data = await response.text();
|
||||||
|
const parsedData = yaml.load(data);
|
||||||
|
const someServices: Service[] = [];
|
||||||
|
if (!parsedData.services) return [];
|
||||||
|
|
||||||
|
Object.keys(parsedData.services).forEach((service) => {
|
||||||
|
const serviceData = parsedData.services[service];
|
||||||
|
someServices.push({
|
||||||
|
name: service,
|
||||||
|
image: serviceData.image,
|
||||||
|
entrypoint: serviceData.entrypoint,
|
||||||
|
command: serviceData.command,
|
||||||
|
ports: serviceData.ports,
|
||||||
|
build: serviceData.build,
|
||||||
|
interactive: serviceData.interactive,
|
||||||
|
volumes: serviceData.volumes?.map((vol) => ({
|
||||||
|
bind: vol.split(':')[0],
|
||||||
|
container: vol.split(':')[1],
|
||||||
|
})),
|
||||||
|
restart_policy: serviceData.restart,
|
||||||
|
env: Object.keys(serviceData.environment || {}).map((envName) => ({
|
||||||
|
name: envName,
|
||||||
|
value: serviceData.environment[envName],
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return someServices;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching or parsing YAML:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Format results for returning to component */
|
||||||
|
const returnResults = async (templates, templateSlug) => {
|
||||||
|
// Find template, based on slug
|
||||||
|
let template = findTemplate(get(templates), templateSlug);
|
||||||
|
|
||||||
|
// Fetch service info from associated stackfile, if it exists
|
||||||
|
let services = template?.repository ? await getServices(template) : [];
|
||||||
|
|
||||||
|
// If only 1 service, merge it with the template
|
||||||
|
if (services.length === 1) {
|
||||||
|
template = {...template, ...services[0]};
|
||||||
|
services = [];
|
||||||
|
} else if (services.length > 1) {
|
||||||
|
// If made up from multiple services, fetch Docker info for each image
|
||||||
|
services = await Promise.all(
|
||||||
|
services.map(async (service) => {
|
||||||
|
const dockerStats = await getDockerHubStats(service.image);
|
||||||
|
return { ...service, dockerStats };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If image specified, fetch Docker image info from DockerHub
|
||||||
|
const dockerStats = template?.image ? await getDockerHubStats(template.image) : null;
|
||||||
|
return { template, dockerStats, services }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const load = async ({ params }) => {
|
||||||
|
const templateSlug = params.slug as string;
|
||||||
|
if (get(templates) && get(templates).length > 0) {
|
||||||
|
return returnResults(templates, templateSlug);
|
||||||
|
} else {
|
||||||
|
const data = await fetch(templatesUrl).then((res) => res.json());
|
||||||
|
templates.set(data.templates);
|
||||||
|
return returnResults(templates, templateSlug);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,95 +1,37 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import yaml from 'js-yaml';
|
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import TemplateNotFound from '$lib/TemplateNotFound.svelte';
|
|
||||||
import type { Template, Service } from '$src/Types';
|
|
||||||
|
|
||||||
|
import Header from '$lib/Header.svelte';
|
||||||
|
import Footer from '$lib/Footer.svelte';
|
||||||
import ServiceStats from '$lib/ServiceStats.svelte';
|
import ServiceStats from '$lib/ServiceStats.svelte';
|
||||||
const templates = $page.data.templates as Template[];
|
import TemplateNotFound from '$lib/TemplateNotFound.svelte';
|
||||||
const templateSlug = $page.params.slug as string;
|
import DockerStats from '$lib/DockerStats.svelte';
|
||||||
|
import MdContent from '$lib/MdContent.svelte';
|
||||||
const template = templates.find((temp: Template) =>
|
import InstallationInstructions from '$lib/InstallationInstructions.svelte';
|
||||||
temp.title.toLowerCase().replace(/[^a-zA-Z ]/g, "").replaceAll(' ', '-') === templateSlug
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(template);
|
import type { Template, Service, DockerHubResponse } from '$src/Types';
|
||||||
|
|
||||||
|
const urlSlug = $page.params.slug;
|
||||||
|
const template = $page.data.template as Template;
|
||||||
|
const dockerStats = $page.data.dockerStats as DockerHubResponse;
|
||||||
|
const services = $page.data.services as Service[];
|
||||||
|
const serviceDockerStats = $page.data.serviceDockerStats as DockerHubResponse[] || null;
|
||||||
|
|
||||||
type Service = {
|
const makeMultiDoc = (services: Service[]) => {
|
||||||
name: string;
|
return services.map((s) => {
|
||||||
image: string;
|
return s?.dockerStats?.full_description ? {
|
||||||
entrypoint: string;
|
name: s.name,
|
||||||
command: string;
|
description: s.dockerStats.description,
|
||||||
ports: string[];
|
content: s.dockerStats.full_description,
|
||||||
build: string;
|
visible: false,
|
||||||
interactive: boolean;
|
} : null;
|
||||||
volumes: { bind: string; container: string }[];
|
}).filter((thingy) => thingy !== null);
|
||||||
restart_policy: string;
|
};
|
||||||
environment: { name: string; value: string }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getServices = async (): Promise<Service[]> => {
|
|
||||||
try {
|
|
||||||
if (template?.repository) {
|
|
||||||
const { url: repoUrl, stackfile } = template.repository;
|
|
||||||
const path = `${repoUrl.replace(
|
|
||||||
'github.com',
|
|
||||||
'raw.githubusercontent.com'
|
|
||||||
)}/HEAD/${stackfile}`;
|
|
||||||
const response = await fetch(path);
|
|
||||||
const data = await response.text();
|
|
||||||
const parsedData = yaml.load(data);
|
|
||||||
const someServices: Service[] = [];
|
|
||||||
if (!parsedData.services) return [];
|
|
||||||
|
|
||||||
console.log(parsedData);
|
|
||||||
Object.keys(parsedData.services).forEach((service) => {
|
|
||||||
const serviceData = parsedData.services[service];
|
|
||||||
someServices.push({
|
|
||||||
name: service,
|
|
||||||
image: serviceData.image,
|
|
||||||
entrypoint: serviceData.entrypoint,
|
|
||||||
command: serviceData.command,
|
|
||||||
ports: serviceData.ports,
|
|
||||||
build: serviceData.build,
|
|
||||||
interactive: serviceData.interactive,
|
|
||||||
volumes: serviceData.volumes?.map((vol) => ({
|
|
||||||
bind: vol.split(':')[0],
|
|
||||||
container: vol.split(':')[1],
|
|
||||||
})),
|
|
||||||
restart_policy: serviceData.restart,
|
|
||||||
env: Object.keys(serviceData.environment || {}).map((envName) => ({
|
|
||||||
name: envName,
|
|
||||||
value: serviceData.environment[envName],
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
console.log(someServices);
|
|
||||||
return someServices;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching or parsing YAML:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const services: Service[] = getServices();
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<Header />
|
||||||
<a class="title" href="/">
|
|
||||||
<img src="https://i.ibb.co/hMymwH0/portainer-templates-small.png" />
|
|
||||||
<h2>Portainer Templates</h2>
|
|
||||||
</a>
|
|
||||||
<nav>
|
|
||||||
<a href="/">Home</a>
|
|
||||||
<a href="https://github.com/lissy93/portainer-templates">View on GitHub</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{#if template}
|
{#if template}
|
||||||
<section class="summary-section">
|
<section class="summary-section">
|
||||||
@@ -105,7 +47,14 @@ const services: Service[] = getServices();
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p class="description">{template.description}</p>
|
<div class="left">
|
||||||
|
<p class="description">{template.description}</p>
|
||||||
|
{#await template then returnedTemplate}
|
||||||
|
{#if dockerStats && dockerStats.name}
|
||||||
|
<DockerStats info={dockerStats} />
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
<ServiceStats template={template} />
|
<ServiceStats template={template} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -116,67 +65,44 @@ const services: Service[] = getServices();
|
|||||||
<h2>Services</h2>
|
<h2>Services</h2>
|
||||||
<div class="service-list">
|
<div class="service-list">
|
||||||
{#each returnedServices as service}
|
{#each returnedServices as service}
|
||||||
<div>
|
<div class="service-each">
|
||||||
<h3>{service.name}</h3>
|
<h3>{service.name}</h3>
|
||||||
|
<div class="service-data">
|
||||||
<ServiceStats template={service} />
|
<ServiceStats template={service} />
|
||||||
|
{#if service.dockerStats && service.dockerStats.name}
|
||||||
|
<DockerStats info={service.dockerStats} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
|
|
||||||
|
<InstallationInstructions portainerTemplate={template} portainerServices={services || null} />
|
||||||
|
|
||||||
|
{#if dockerStats?.full_description}
|
||||||
|
<MdContent content={dockerStats.full_description} />
|
||||||
|
{:else if services.length > 0}
|
||||||
|
<MdContent multiContent={makeMultiDoc(services)} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{:else}
|
{:else}
|
||||||
<TemplateNotFound />
|
<TemplateNotFound templateName={urlSlug} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<Footer />
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: var(--card);
|
|
||||||
padding: 0.25rem 1rem;
|
|
||||||
a.title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1rem;
|
|
||||||
color: var(--foreground);
|
|
||||||
text-decoration: none;
|
|
||||||
h2 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
width: 40px;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
img { transform: rotate(-5deg) scale(1.1); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
<style lang="scss">
|
||||||
display: flex;
|
section {
|
||||||
gap: 1rem;
|
max-width: 1000px;
|
||||||
a {
|
margin: 1rem auto;
|
||||||
color: var(--foreground);
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
transition: all 250ms ease-in-out;
|
|
||||||
&:hover {
|
|
||||||
background: var(--gradient);
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.summary-section {
|
.summary-section {
|
||||||
background: var(--card);
|
background: var(--card);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 1rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
h1 {
|
h1 {
|
||||||
@@ -212,15 +138,24 @@ const services: Service[] = getServices();
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-top: 1rem;
|
||||||
|
.left {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
p.description {
|
p.description {
|
||||||
max-width: 60%;
|
background: var(--card-2);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-section {
|
.service-section {
|
||||||
background: var(--card);
|
background: var(--card);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin: 1rem;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -228,12 +163,18 @@ const services: Service[] = getServices();
|
|||||||
}
|
}
|
||||||
.service-list {
|
.service-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 2rem;
|
||||||
// justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
.service-each {
|
||||||
|
.service-data {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user