diff --git a/src/download.html b/src/download.html index cb25d89..4c8cfa6 100644 --- a/src/download.html +++ b/src/download.html @@ -2,32 +2,33 @@ Download Caddy - {{import "/old/includes/head.html"}} - {{template "head"}} - - - - + {{import "/includes/head.html"}} + {{template "head" .}} + + + -
-
-
- -
a project
+
+ {{include "/includes/header.html" "dark-header"}} + +
+
+

+ Download +
+ Build your own caddy binary with modules. +
+

- {{include "/old/includes/header-nav.html"}} -
- -
- ⚠️ Due to multiple outstanding bugs in the go command, we are aware that some downloads may hang or fail. In the meantime, you can download Caddy from the latest release on GitHub, or use xcaddy for custom builds. (Remember, this download page comes with no guarantees or SLAs.) Sorry for the inconvenience.
- -
-
-
- Platform: - @@ -56,39 +57,61 @@ + + + +
+ +
+
+							xcaddy build
+						
+ + + + +
-
-
- Standard features: ☑️ -
+
+ +
+ + +
+ +
+
-
-
- Extra features: 0 -
-
-
- Download +
+ - + {{include "/includes/footer.html"}} -
- ⚠️ Only choose plugins you need and trust -
-
- ⚠️ Run the following against the downloaded binary: - xattr -d com.apple.quarantine - -
- -
- -
-
- - {{include "/old/includes/footer.html"}} + + diff --git a/src/includes/card.html b/src/includes/card.html new file mode 100644 index 0000000..e332d0a --- /dev/null +++ b/src/includes/card.html @@ -0,0 +1,48 @@ +
+
+
+

+ ${item.name} +

+ + ${item.path} + +
+
+ + + + + + + + ${item.downloads} + + + + + + + + + + +
+
+
+

+ ${item.description} +

+
+ +
+
+
\ No newline at end of file diff --git a/src/resources/css/download.css b/src/resources/css/download.css new file mode 100644 index 0000000..8c1c626 --- /dev/null +++ b/src/resources/css/download.css @@ -0,0 +1,298 @@ +* { + --border-download: 1px solid rgba(226, 232, 240, 0.8); + --radius: 0.5rem; +} + +.card { + border: var(--border-download); + border-radius: 16px; + padding: 16px; + width: 100%; + height: 100%; + background-color: var(--body-bg); + display: flex; + flex-direction: column; + gap: 16px; +} + +html { + scroll-behavior: smooth; +} + +.shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06); +} + +.shadow-lg { + box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +.wrapper.list { + display: flex; +} + +#side-panel-packages { + padding-bottom: 32px; +} + +#side-panel-packages>div { + padding: 32px 1rem 1rem 0; + overflow-y: scroll; + width: 250px; + max-height: 100vh; + display: flex; + flex-direction: column; + position: sticky; + top: 100px; +} + +#download-link>button { + font-size: 1rem; +} + +#platform { + width: unset; + min-height: unset; + height: unset; + line-height: unset; +} + +#packages { + width: 100%; + padding-top: 32px; + padding-bottom: 32px; + display: grid; + gap: 48px; +} + +#download { + position: sticky; + top: 0; + padding-bottom: 32px; + z-index: 10; +} + +#download>div { + border: var(--border-download); + background-color: var(--body-bg); + border-radius: var(--radius); + padding: 1rem; +} + +#download>div { + border: var(--border-download); + background-color: var(--body-bg); + border-radius: var(--radius); + padding: 1rem; +} + +#downloader>button { + margin: 0 auto; + min-width: fit-content; + white-space: nowrap; + font-size: 80%; + font-weight: bold; +} + +#downloader { + padding-bottom: 16px; +} + +#command { + border-radius: var(--radius); + border: 1px solid var(--button-border-color); + padding: 0.5rem 0.75rem; + display: inline-flex; + width: 100%; +} + +#command>pre { + width: 100%; + overflow-x: scroll; + display: inline-flex; + white-space: nowrap; + font-size: 1rem; +} + +#command>svg { + height: 1em; + cursor: pointer; +} + +#command-builder::before { + content: '$'; + color: var(--text-color-muted); + margin-right: 0.25rem; +} + +h2 { + padding-bottom: 16px; + color: rgb(14, 110, 189); + border-color: rgb(14, 110, 189); +} + +.card-list { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 32px; +} + +.card-header { + width: 100%; + display: flex; + justify-content: space-between; +} + +.card-title { + width: 100%; + display: flex; + padding-left: 16px; + justify-content: space-between; +} + +.card-title-name { + display: flex; + flex-direction: column; + font-weight: lighter; + color: var(--text-color); +} + +.card-title-name>a { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow-x: hidden; +} + +.card-title-info { + display: inline-flex; + gap: 8px; + font-size: 90%; + color: var(--text-color-muted); +} + +.card-title-info>span>svg { + margin-right: -4px; +} + +.card-header>:first-child>span:first-child { + font-weight: bold; +} + +.card-description { + display: flex; + color: var(--text-color-muted); + justify-content: space-between; + height: 100%; + gap: 8px; +} + +.card-description>p { + font-weight: 400; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 4; + overflow: hidden; + word-break: break-word; +} + +.card-actions { + margin-top: auto; +} + +.card-button { + margin: 0 auto; + min-width: fit-content; + white-space: nowrap; + font-size: 80%; + font-weight: bold; +} + +article { + margin-left: auto; + margin-right: auto; +} + +.filters { + display: grid; + grid-template-columns: 2fr 1fr 2fr; + gap: 16px; +} + +@media (max-width: 1400px) { + .card-description { + flex-direction: column; + } + + .card-list { + grid-template-columns: 1fr; + } + + .filters { + grid-template-columns: 1fr; + } + + .wrapper.list { + flex-direction: column; + } + + #side-panel-packages>div { + overflow: unset; + width: 100%; + } +} + +input, +select { + padding: 0.5rem; + height: 3rem; + font-size: 100%; + font-weight: 600; + border: 1px solid var(--button-border-color); + color: var(--text-color); + background-color: var(--body-bg); + width: 100%; + border-radius: var(--radius); +} + +.filters>div { + flex-direction: column; +} + +select { + cursor: pointer; + -webkit-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding-left: 1rem; + padding-right: 2.5rem; + font-size: .875rem; + line-height: 1.25rem; + line-height: 2; + min-height: 3rem; + border: 1px solid var(--button-border-color); + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%); + background-size: 4px 4px, 4px 4px; + background-repeat: no-repeat; +} + +.package-version { + height: 50%; + font-size: 80%; + width: 4rem; + padding-top: unset; + padding-bottom: unset; +} + +section { + padding: unset; + background-color: unset; +} + +h3.blue { + padding-left: unset; + border: unset; +} \ No newline at end of file diff --git a/src/resources/js/download.js b/src/resources/js/download.js new file mode 100644 index 0000000..7e4d530 --- /dev/null +++ b/src/resources/js/download.js @@ -0,0 +1,206 @@ +const BASE_API_PATH = '/api'; +const pkgURL = `${BASE_API_PATH}/packages`; +const downloadURL = `${BASE_API_PATH}/download`; + +class Package { + /** + * @typedef {Object} Module + * @property {string} docs + * @property {string} name + * @property {string} package + * @property {string} repo + */ + + /** + * @typedef {Object} Pkg + * @property {string} id + * @property {string} path + * @property {string} published + * @property {boolean} listed + * @property {boolean} available + * @property {number} downloads + * @property {ReadonlyArray} modules + * @property {string} repo + * @property {string} name + */ + + /** + * @type {ReadonlyArray} + */ + packages = []; + + /** + * @type {string} + */ + filter = ''; + + /** + * @returns Promise<> + */ + getPackages() { + return fetch(pkgURL, { headers: { 'X-Requested-With': 'XMLHttpRequest', Origin: 'https://caddyserver.com' } }) + .then(res => res.json()) + .then(({ result }) => this.packages = result.sort((a, b) => a.downloads - b.downloads).map(item => ({ + ...item, + description: item.modules?.map(m => m.docs ?? m.name).join('\n') ?? '', + name: (item.repo || item.path).split('/').pop().toLowerCase(), + }))); + } + + setFilterValue(value) { + this.filter = value; + } + + getSearchPackages(pkgs) { + if (!this.filter) { + return pkgs; + } + + return pkgs.filter(pkg => pkg.name.includes(this.filter) || pkg.repo.includes(this.filter) || pkg.description.includes(this.filter)); + } + + /** + * @param {'alphabetically' | 'type' | 'download'} groupBy + * @return { + * Record | ReadonlyArray a.name.localeCompare(b.name)); + case 'download': + return pkgs.sort((a, b) => b.downloads - a.downloads); + case 'type': + return pkgs.reduce((acc, current) => { + if (!current?.modules?.length) { + + return acc; + } + + current.modules.forEach(module => { + let moduleName = module.name + if (module.name.includes('.')) { + const splitted = module.name.split('.') + moduleName = `${splitted[0]}.${splitted[1]}` + } + if (acc[moduleName]) { + acc[moduleName] = [...acc[moduleName], current]; + } + }) + + return acc; + }, { + "http.handlers": [], + "http.matchers": [], + "dns.providers": [], + "http.encoders": [], + "caddy.config_loaders": [], + "caddy.fs": [], + "caddy.listeners": [], + "caddy.logging.encoders": [], + "caddy.logging.encoders.filter": [], + "caddy.logging.writers": [], + "caddy.storage": [], + "events.handlers": [], + "http.authentication.hashes": [], + "http.authentication.providers": [], + "http.ip_sources": [], + "http.precompressed": [], + "http.reverse_proxy.circuit_breakers": [], + "http.reverse_proxy.selection_policies": [], + "http.reverse_proxy.transport": [], + "http.reverse_proxy.upstreams": [], + "tls.certificates": [], + "tls.client_auth": [], + "tls.handshake_match": [], + "tls.issuance": [], + "tls.get_certificate": [], + "tls.stek": [], + }) + } + } +} + +const packageManager = new Package(); + +const params = new URLSearchParams(window.location.search?.slice(1)); +let versions = params.getAll('p').reduce((acc, current) => { + [p, v] = current.split('@'); + + acc[p] = v ?? ''; + + return acc; +}, {}); + +function togglePackage({ target: { dataset: { module } } }) { + const element = document.getElementById('packages').querySelector(`button[data-module="${module}"]`); + if (module in versions) { + delete versions[module]; + const countVersions = Object.keys(versions).length; + if (!countVersions) { + modulesCount.innerHTML = ''; + } else { + modulesCount.innerHTML = `with ${countVersions} extra module${countVersions > 1 ? 's' : ''}`; + } + + element.innerHTML = "Add this module"; + } else { + versions[module] = ''; + const countVersions = Object.keys(versions).length; + element.innerHTML = "Remove this module"; + modulesCount.innerHTML = `with ${countVersions} extra module${countVersions > 1 ? 's' : ''}`; + } + + setDownloadLink(); +} + +function setDownloadLink() { + document.getElementById('command-builder').innerText = getCommand(); + document + .getElementById('download-link') + .setAttribute('href', `${downloadURL}?${new URLSearchParams(Object.entries(versions).map(([p, v]) => ['p', `${p}${!!v ? `@${v}` : ''}`])).toString()}`); +} + +function getCommand() { + return `xcaddy build${Object.entries(versions ?? {}).map(([p, v]) => ` --with ${p}${!!v ? `@${v}` : ''}`).join('')}` +} + +function copyCommand() { + navigator.clipboard.writeText(getCommand()) +} + +function renderList(list) { + if (groupBy === 'type') { + const groupedData = Object.entries(packageManager.group(groupBy)).filter(([_, items]) => !!items.length) + document.getElementById('side-panel-packages').innerHTML = ` +
+

Namespaces

+${groupedData.map(([k]) => ` ${k}`).join('')} +
`; + document.getElementById('packages').innerHTML = groupedData.map(([category, items]) => ` +
+

${category}

+
${items.map(item => getCardTemplate({ ...item, state: versions[item.path] })).join('')}
+
`).join('') + return; + } + + document.getElementById('side-panel-packages').innerHTML = ''; + document.getElementById('packages').innerHTML = ` +
+${list.map(item => getCardTemplate({ ...item, state: versions[item.path] })).join('')} +
`; +}; + +packageManager.getPackages().then(() => { + renderList(packageManager.group(groupBy)); + const countVersions = Object.keys(versions).length; + modulesCount.innerHTML = countVersions ? `with ${countVersions} extra module${countVersions > 1 ? 's' : ''}` : ''; + setDownloadLink(); +}) + +function updateVersion({ target: { value } }, pkg) { + versions[pkg] = value; + setDownloadLink(); +} \ No newline at end of file