diff --git a/website/Dockerfile b/website/Dockerfile index c9ecc7d1..2f9d6f0b 100644 --- a/website/Dockerfile +++ b/website/Dockerfile @@ -27,4 +27,4 @@ COPY ./Caddyfile /etc/caddy/Caddyfile COPY --from=prod /opt/docusaurus/build /usr/share/caddy # Copy the landing page assets. COPY landing/images /usr/share/caddy/images -COPY landing/index.html landing/style.css /usr/share/caddy/ +COPY landing/index.html landing/hub.html landing/style.css /usr/share/caddy/ diff --git a/website/landing/hub.backup.html b/website/landing/hub.backup.html new file mode 100644 index 00000000..7928ba6b --- /dev/null +++ b/website/landing/hub.backup.html @@ -0,0 +1,896 @@ + + + + + + Uncloud Hub | Hosted dashboard and observability for Uncloud + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + + +
+
+
+ + +
+ + +
+
+ +
+
+
+ +
+ + + + + Coming soon +
+

+ See what's happening
+ + in production +

+

+ Uncloud Hub is the UI and Grafana stack for your Uncloud clusters.
+ Get real-time metrics, logs, and alerts out of the box to catch issues early and fix + faster. +

+ +
+
+
+ + +
+ Uncloud Hub services dashboard, design preview +
+ + +
+

+ Hub is built for Uncloud, the open-source orchestrator that runs your apps across machines. +

+ + + + +
+
+
+ + +
+
+
+
+

+ What you actually see +

+

+ Pre-built dashboards and alerts for the failure modes you care about. +

+
+ +
+ +
+
+ + + + +

+ HTTP edge metrics from Caddy +

+
+

+ Per-service latency, error rate, status codes. From the proxy your apps already pass + through. No agent in your app. No code changes. +

+
+ + +
+
+ + + + +

+ Logs next to the spike +

+
+

+ When something goes red, the relevant application logs are right there. No hunting for + log files across machines. No new query language to learn. +

+
+ + +
+
+ + + + +

+ Slow Postgres queries +

+
+

+ Hub watches every Uncloud-managed Postgres for queries eating your latency budget. + Find them before your users do. +

+
+ + +
+
+ + + +

+ Alerts that mean something +

+
+

+ Pre-built rules for the failures you actually care about. Service unavailable. Edge + errors climbing. Disk filling. Postgres replication lag. +

+
+
+
+
+
+ + +
+
+
+
+
+ How it works +
+

+ Connect, collect, see +

+

+ Three steps. The same stack you'd build by hand, set up for you. +

+
+ +
+ +
+
+
+ 1 +
+

Connect

+
+

+ Got an Uncloud cluster running? Connect it in one command. New to Uncloud? Hub walks + you through setting one up from your own machines. +

+
$ uc hub connect
+
+ + +
+
+
+ 2 +
+

Install the agent

+
+

+ Hub detects what's running. Caddy as your ingress, Postgres on machine 2, your worker, + your web app. It configures the right collectors for each. No exporters to install by + hand. +

+
+
Detected Caddy ingress
+
Detected Postgres 16
+
Configuring exporters
+
+
+ + +
+
+
+ 3 +
+

See

+
+

+ Within minutes, dashboards are live. Failed HTTP requests, slow queries, application + logs, system health. Plus alerts that page when users are affected. +

+
+
4 dashboards live
+
12 alert rules active
+
logs streaming
+
+
+
+
+
+
+ + +
+
+
+
+
+

+ Same Grafana stack you'd have built. Full access. +

+

+ Hub does the setup and the ongoing maintenance for you. It ships with opinions about + what's worth watching. Underneath, it's still your data and your tools. +

+
+
+
    +
  • + + + +
    +
    Click any chart, see the query
    +
    No black boxes. The PromQL is right there, + ready to copy or edit. +
    +
    +
  • +
  • + + + +
    +
    Customise our dashboards or add + your own +
    +
    Full Grafana editor. Your panels live + alongside the pre-built ones. +
    +
    +
  • +
  • + + + +
    +
    Hand the data to an AI agent
    +
    Read-only data API. Ask plain-English + questions about your fleet. It's your data. +
    +
    +
  • +
  • + + + +
    +
    Out of the critical path
    +
    If Hub goes down, your apps keep running. + Hub watches. It does not run things. +
    +
    +
  • +
+
+
+
+
+
+ + +
+
+
+
+

+ Built on the back of Uncloud +

+
+
+
+
5K+
+
GitHub stars
+
+
+
19K+
+
Downloads
+
+
+
2x
+
Hacker News front page
+
+
+
In use
+
Radboud University
+
+
+
+
+
+ + +
+
+
+
+

+ A note from us +

+
+
+

+ I'm Pasha. I've spent a decade building commercial software and keeping production + infrastructure healthy. Uncloud started because I wanted a simpler way to run my own apps + across cloud VMs and machines at home, without a Kubernetes degree. +

+

+ Hub is the dashboard we needed for that, productised. If the problem above sounds like your + weekend, We'd like to hear about your setup. The design partner round is small. The + feedback we get now is what shapes what ships. +

+
+ +
+
+
+ + +
+
+
+

+ Where Hub is going +

+
+

+ Hub starts with observability because that's what we keep hearing from people running things + on their own servers. After that, the direction is broader. Shared cluster access without + juggling SSH keys. Connectivity for machines behind NAT. A clean way to reach your cluster + from your laptop as if you were already inside it. +

+

+ None of this is on the page yet because none of it is shipping yet. Observability lands + first. The rest follows once the wedge is real. +

+
+
+
+
+ + +
+
+
+
+

+ Frequently asked questions +

+
+ +
+
+ + What's open source and what's paid? + + + + +

+ Uncloud itself stays open source under Apache-2.0. The on-cluster agent that connects + to Hub will be open source too, with a configurable endpoint. The Hub backend is closed + source and paid. +

+
+ +
+ + Does it work without Uncloud? + + + + +

+ No, by design. Hub knows what's running because Uncloud manages it. That auto-discovery + is the difference between Hub and a generic Grafana you set up yourself. +

+
+ +
+ + Can my team log in too? + + + + +

+ Single user for now. Multi-user and team access are planned. If single user is a + blocker for you, please tell us on the form below. That's a strong signal for + prioritising it sooner. +

+
+ +
+ + Can I self-host Hub itself? + + + + +

+ Planned for orgs that need to keep everything on their own infrastructure. Not in the + first release. +

+
+ +
+ + I already run Grafana. What now? + + + + +

+ Keep it if it's working. Hub can sit alongside as a separate dashboard for the + Uncloud-aware views. If you'd rather stop maintaining your own Grafana, Hub replaces it + entirely. Same Grafana underneath either way. +

+
+ +
+ + When does it ship and how much does it cost? + + + + +

+ First version is in design now. Pricing is per cluster, designed to land below the cost + of the machines you're watching. The form below is the way into the design partner + round. +

+
+ +
+ + What if Hub is down? + + + + +

+ Your apps keep running. Hub watches your cluster from outside it and is not in the + critical path of your traffic, your data, or your deploys. If hub.uncloud.run becomes + unavailable, you lose dashboards and alerts for that period. You do not lose your + applications. +

+
+
+
+
+
+ + +
+
+
+
+

+ Help shape + + Hub + + +

+

+ We're picking 5 to 10 design partners for the first version. If you run things on a few + servers and the Day 2 part is what's grinding you down, We'd like to hear about your setup. +

+ + + + +
+

+ Or drop your email below and we'll send updates as Hub takes shape. +

+
+ + +
+ +
+
+
+
+
+ +
+ + + + +
+ + + + + + diff --git a/website/landing/hub.html b/website/landing/hub.html new file mode 100644 index 00000000..a2eb33c4 --- /dev/null +++ b/website/landing/hub.html @@ -0,0 +1,512 @@ + + + + + + Uncloud Hub | Hosted dashboard and observability for Uncloud + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + + +
+
+
+ + +
+ + +
+
+ +
+
+
+ +
+ + + + + Coming soon +
+

+ See what's happening
+ + in production +

+

+ Uncloud Hub is the UI and Grafana stack for your Uncloud clusters.
+ Get real-time metrics, logs, and alerts out of the box to catch issues early and fix + faster. +

+ +
+
+
+ + +
+ Uncloud Hub services dashboard, design preview +
+ + +
+

+ Hub is built for Uncloud, the open-source orchestrator that runs your apps across machines. +

+ + + + +
+
+
+ + +
+
+
+ + +
+

+ Frustrated by the complexity of setting up observability? +

+

+ Observability is what killing the joy of shipping. +

+
+ + +
+
+ + + + +
+ +
Build
+
The fun part.
+
+ + +
+ +
Ship
+
uc deploy and you're done.
+
+ + +
+ +
Run / Monitor and observe
+

This is what matters.

+
+ + +
+

+ Observability for developers who'd rather be shipping. +

+

+ It's not optional. +

+
+ + +
    +
  • + + Are you finding out about outages from your users? +
  • +
  • + + When something breaks, where do you start looking? +
  • +
  • + + Prometheus or VictoriaMetrics? Loki or VictoriaLogs? +
  • +
  • + + Vector, Grafana Alloy, or Fluent Bit? +
  • +
  • + + Which alerts wake you for real problems, not 3 AM noise? +
  • +
+ + +

+ And that's before you've drawn a single graph. +

+ + +
+ +
+ + + +
+
+ +
+
+
+ +
+
+
+
+

+ Untangle the observability mess and start monitoring instantly +

+

+ A hosted Grafana stack that knows about your Uncloud apps. +

+
+ + ... +
+
+
+ +
+ + + + +
+ + + + + + diff --git a/website/landing/images/hub-hero.webp b/website/landing/images/hub-hero.webp new file mode 100644 index 00000000..8bbf2a10 Binary files /dev/null and b/website/landing/images/hub-hero.webp differ diff --git a/website/landing/js/vendors/alpinejs.min.js b/website/landing/js/vendors/alpinejs.min.js new file mode 100644 index 00000000..aaad63a9 --- /dev/null +++ b/website/landing/js/vendors/alpinejs.min.js @@ -0,0 +1,5 @@ +(()=>{var Ye=!1,Ze=!1,V=[];function Ft(e){mn(e)}function mn(e){V.includes(e)||V.push(e),hn()}function xe(e){let t=V.indexOf(e);t!==-1&&V.splice(t,1)}function hn(){!Ze&&!Ye&&(Ye=!0,queueMicrotask(_n))}function _n(){Ye=!1,Ze=!0;for(let e=0;ee.effect(t,{scheduler:r=>{Xe?Ft(r):r()}}),Qe=e.raw}function et(e){I=e}function zt(e){let t=()=>{};return[n=>{let i=I(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),$(i))},i},()=>{t()}]}var Vt=[],Ht=[],qt=[];function Ut(e){qt.push(e)}function ye(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Ht.push(t))}function Wt(e){Vt.push(e)}function Gt(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function tt(e,t){!e._x_attributeCleanups||Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}var nt=new MutationObserver(rt),it=!1;function ie(){nt.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),it=!0}function ot(){gn(),nt.disconnect(),it=!1}var oe=[],st=!1;function gn(){oe=oe.concat(nt.takeRecords()),oe.length&&!st&&(st=!0,queueMicrotask(()=>{xn(),st=!1}))}function xn(){rt(oe),oe.length=0}function h(e){if(!it)return e();ot();let t=e();return ie(),t}var at=!1,be=[];function Jt(){at=!0}function Yt(){at=!1,rt(be),be=[]}function rt(e){if(at){be=be.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;os.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{tt(s,o)}),n.forEach((o,s)=>{Vt.forEach(a=>a(s,o))});for(let o of r)if(!t.includes(o)&&(Ht.forEach(s=>s(o)),o._x_cleanups))for(;o._x_cleanups.length;)o._x_cleanups.pop()();t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.includes(o)||!o.isConnected||(delete o._x_ignoreSelf,delete o._x_ignore,qt.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function ve(e){return j(L(e))}function M(e,t,r){return e._x_dataStack=[t,...L(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function ct(e,t){let r=e._x_dataStack[0];Object.entries(t).forEach(([n,i])=>{r[n]=i})}function L(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?L(e.host):e.parentNode?L(e.parentNode):[]}function j(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,c=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...c,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function we(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Ee(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>yn(n,i),s=>lt(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function yn(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function lt(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),lt(e[t[0]],t.slice(1),r)}}var Zt={};function y(e,t){Zt[e]=t}function se(e,t){return Object.entries(Zt).forEach(([r,n])=>{Object.defineProperty(e,`$${r}`,{get(){let[i,o]=ut(t);return i={interceptor:Ee,...i},ye(t,o),n(t,i)},enumerable:!1})}),e}function Qt(e,t,r,...n){try{return r(...n)}catch(i){Z(i,e,t)}}function Z(e,t,r=void 0){Object.assign(e,{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message} + +${r?'Expression: "'+r+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var Se=!0;function Xt(e){let t=Se;Se=!1,e(),Se=t}function P(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return er(...e)}var er=ft;function tr(e){er=e}function ft(e,t){let r={};se(r,e);let n=[r,...L(e)];if(typeof t=="function")return bn(n,t);let i=vn(n,t,e);return Qt.bind(null,e,t,i)}function bn(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(j([n,...e]),i);Ae(r,o)}}var dt={};function wn(e,t){if(dt[e])return dt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(async()=>{ ${e} })()`:e,o=(()=>{try{return new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`)}catch(s){return Z(s,t,e),Promise.resolve()}})();return dt[e]=o,o}function vn(e,t,r){let n=wn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=j([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>Z(l,r,t));n.finished?(Ae(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ae(i,l,a,s,r)}).catch(l=>Z(l,r,t)).finally(()=>n.result=void 0)}}}function Ae(e,t,r,n,i){if(Se&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ae(e,s,r,n)).catch(s=>Z(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var pt="x-";function S(e=""){return pt+e}function rr(e){pt=e}var mt={};function p(e,t){return mt[e]=t,{before(r){if(!mt[r]){console.warn("Cannot find directive `${directive}`. `${name}` will use the default order of execution");return}let n=H.indexOf(r)??H.indexOf("DEFAULT");n>=0&&H.splice(n,0,e)}}}function ae(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=ht(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(nr((o,s)=>n[o]=s)).filter(ir).map(Sn(n,r)).sort(An).map(o=>En(e,o))}function ht(e){return Array.from(e).map(nr()).filter(t=>!ir(t))}var _t=!1,ce=new Map,or=Symbol();function sr(e){_t=!0;let t=Symbol();or=t,ce.set(t,[]);let r=()=>{for(;ce.get(t).length;)ce.get(t).shift()();ce.delete(t)},n=()=>{_t=!1,r()};e(r),n()}function ut(e){let t=[],r=a=>t.push(a),[n,i]=zt(e);return t.push(i),[{Alpine:F,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:P.bind(P,e)},()=>t.forEach(a=>a())]}function En(e,t){let r=()=>{},n=mt[t.type]||r,[i,o]=ut(e);Gt(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),_t?ce.get(or).push(n):n())};return s.runCleanups=o,s}var Oe=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Te=e=>e;function nr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=ar.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var ar=[];function Q(e){ar.push(e)}function ir({name:e}){return cr().test(e)}var cr=()=>new RegExp(`^${pt}([^:^.]+)\\b`);function Sn(e,t){return({name:r,value:n})=>{let i=r.match(cr()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var gt="DEFAULT",H=["ignore","ref","data","id","radio","tabs","switch","disclosure","menu","listbox","combobox","bind","init","for","mask","model","modelable","transition","show","if",gt,"teleport"];function An(e,t){let r=H.indexOf(e.type)===-1?gt:e.type,n=H.indexOf(t.type)===-1?gt:t.type;return H.indexOf(r)-H.indexOf(n)}function q(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function A(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>A(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)A(n,t,!1),n=n.nextElementSibling}function C(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function ur(){document.body||C("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `