diff --git a/.dist/home.js b/.dist/home.js new file mode 100644 index 0000000..ffb50ee --- /dev/null +++ b/.dist/home.js @@ -0,0 +1,62 @@ +import { mobile } from "./login.js"; +console.log(mobile); +const serverbox = document.getElementById("instancebox"); +fetch("/instances.json").then(_ => _.json()).then((json) => { + console.warn(json); + for (const instance of json) { + if (instance.display === false) { + continue; + } + const div = document.createElement("div"); + div.classList.add("flexltr", "instance"); + if (instance.image) { + const img = document.createElement("img"); + img.src = instance.image; + div.append(img); + } + const statbox = document.createElement("div"); + statbox.classList.add("flexttb"); + { + const textbox = document.createElement("div"); + textbox.classList.add("flexttb", "instatancetextbox"); + const title = document.createElement("h2"); + title.innerText = instance.name; + if (instance.online !== undefined) { + const status = document.createElement("span"); + status.innerText = instance.online ? "Online" : "Offline"; + status.classList.add("instanceStatus"); + title.append(status); + } + textbox.append(title); + if (instance.description || instance.descriptionLong) { + const p = document.createElement("p"); + if (instance.descriptionLong) { + p.innerText = instance.descriptionLong; + } + else { + p.innerText = instance.description; + } + textbox.append(p); + } + statbox.append(textbox); + } + { + const stats = document.createElement("div"); + stats.classList.add("flexltr"); + const span = document.createElement("span"); + span.innerText = `Uptime: All time: ${Math.floor(instance.uptime.alltime * 100)}% This week: ${Math.floor(instance.uptime.weektime * 100)}% Today: ${Math.floor(instance.uptime.daytime * 100)}%`; + stats.append(span); + statbox.append(stats); + } + div.append(statbox); + div.onclick = _ => { + if (instance.online) { + window.location.href = "/register.html?instance=" + encodeURI(instance.name); + } + else { + alert("Instance is offline, can't connect"); + } + }; + serverbox.append(div); + } +}); diff --git a/.dist/login.js b/.dist/login.js index b4f0817..46aa5b3 100644 --- a/.dist/login.js +++ b/.dist/login.js @@ -419,7 +419,6 @@ if (datalist) { console.warn(json); if (instancein && instancein.value === "") { instancein.value = json[0].name; - setTimeout(checkInstance, 10); } for (const instance of json) { if (instance.display === false) { @@ -447,5 +446,6 @@ if (datalist) { } datalist.append(option); } + checkInstance(""); }); } diff --git a/index.js b/index.js index a27f368..9ac4858 100755 --- a/index.js +++ b/index.js @@ -4,7 +4,8 @@ const compression = require('compression') const express = require('express'); const fs = require('fs'); const app = express(); -const instances=require("./webpage/instances.json") +const instances=require("./webpage/instances.json"); +const stats=require("./stats.js"); const instancenames=new Map(); for(const instance of instances){ instancenames.set(instance.name,instance); @@ -12,7 +13,6 @@ for(const instance of instances){ app.use(compression()) fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json").then(_=>_.json()).then(json=>{ for(const instance of json){ - console.log(instance); if(!instancenames.has(instance.name)){ instances.push(instance); }else{ @@ -24,6 +24,7 @@ fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instan } } } + stats.observe(instances) }) app.use("/getupdates",(req, res) => { @@ -145,6 +146,10 @@ async function inviteres(req,res){ app.use('/services/oembed', (req, res) => { inviteres(req, res); }) +app.use("/uptime",(req,res)=>{ + const uptime=stats.uptime[req.query.name]; + res.send(uptime); +}) app.use('/', async (req, res) => { const scheme = req.secure ? 'https' : 'http'; const host=`${scheme}://${req.get("Host")}`; @@ -156,13 +161,14 @@ app.use('/', async (req, res) => { }else{ console.log(req); } - + if(req.path==="/"){ + res.sendFile(`./webpage/home.html`, {root: __dirname}); + } if(debugging&&req.path.startsWith("/service.js")){ res.send("dud"); return; } if(req.path.startsWith("/instances.json")){ - console.log("grabbed") res.send(JSON.stringify(instances)); return; } @@ -189,3 +195,5 @@ app.use('/', async (req, res) => { const PORT = process.env.PORT || +process.argv[1] || 8080; app.listen(PORT, () => {}); console.log("this ran :P"); + +exports.getapiurls=getapiurls; diff --git a/stats.js b/stats.js new file mode 100644 index 0000000..385ca5e --- /dev/null +++ b/stats.js @@ -0,0 +1,142 @@ +const index = require('./index.js'); +const fs=require("fs"); +let uptimeObject={}; +if(fs.existsSync("./uptime.json")){ + try{ + uptimeObject=JSON.parse(fs.readFileSync('./uptime.json', 'utf8')); + }catch{ + uptimeObject={}; + } +} + +async function observe(instances){ + const active=new Set(); + async function resolveinstance(instance){ + calcStats(instance); + let api; + if(instance.urls){ + api=instance.urls.api; + }else if(instance.url){ + const urls=await index.getapiurls(instance.url); + if(urls){ + api=urls.api; + } + } + if(!api||api===""){ + + setSatus(instance,false); + console.warn(instance.name+" does not resolve api URL"); + setTimeout(_=>{resolveinstance(instance)},1000*60*30,); + return + } + active.add(instance.name); + api+=api.endsWith("/")?"":"/" + function check(){ + fetch(api+"ping").then(_=>{ + setSatus(instance,_.ok); + }) + } + setTimeout( + _=>{ + check(); + setInterval(_=>{ + check(); + },1000*60*30) + },Math.random()*1000*60*10 + ) + } + const promlist=[]; + for(const instance of instances){ + promlist.push(resolveinstance(instance)); + } + await Promise.allSettled(promlist); + for(const key of Object.keys(uptimeObject)){ + if(!active.has(key)){ + setSatus(key,false); + } + } +} +function calcStats(instance){ + let obj=uptimeObject[instance.name]; + if(!obj) return; + const day=Date.now()-1000*60*60*24; + const week=Date.now()-1000*60*60*24*7; + let alltime=-1; + let totalTimePassed=0; + let laststamp=0; + let daytime=-1; + let weektime=-1; + let online=false; + for(const thing of obj){ + const stamp=thing.time; + if(alltime===-1){ + laststamp=stamp; + alltime=0; + } + const timepassed=stamp-laststamp; + totalTimePassed+=timepassed; + alltime+=online*timepassed; + if(stamp>week){ + if(weektime===-1){ + weektime=online*(stamp-week); + }else{ + weektime+=online*timepassed; + } + if(stamp>day){ + if(daytime===-1){ + daytime=online*(stamp-day); + }else{ + daytime+=online*timepassed; + } + } + } + online=thing.online; + } + instance.online=online; + const timepassed=Date.now()-laststamp; + totalTimePassed+=timepassed; + daytime+=online*timepassed; + weektime+=online*timepassed; + alltime+=online*timepassed; + alltime/=totalTimePassed; + if(timepassed>1000*60*60*24){ + daytime/=1000*60*60*24; + if(timepassed>1000*60*60*24*7){ + weektime/=1000*60*60*24*7; + }else{ + weektime=alltime; + } + }else{ + weektime=alltime + daytime=alltime; + } + instance.uptime={daytime,weektime,alltime} +} +/** + * @param {string} name + * @param {boolean} status + */ +function setSatus(instance,status){ + const name=instance.name; + let obj=uptimeObject[name]; + let needSetting=false; + if(!obj){ + obj=[]; + uptimeObject[name]=obj; + needSetting=true; + }else{ + if(obj[obj.length-1].online!==status){ + needSetting=true; + } + } + if(needSetting){ + obj.push({time:Date.now(),online:status}); + updatejson(); + } + calcStats(instance); +} +function updatejson(){ + fs.writeFile('./uptime.json',JSON.stringify(uptimeObject),_=>{}); +} +exports.observe=observe; +exports.uptime=uptimeObject; diff --git a/webpage/home.html b/webpage/home.html new file mode 100644 index 0000000..a6ffaab --- /dev/null +++ b/webpage/home.html @@ -0,0 +1,48 @@ + + + + + + Jank Client + + + + + + + + + +
+ +

Jank Client

+

Spacebar Guild

+

Github

+
+
+ +

Welcome to Jank Client

+
+

Jank Client is a spacebar compatible client seeking to be as good as it can be with many features including:

+ +
+
+

Spacebar compatible Instances:

+
+
+
+
+

Contribute to Jank Client

+

We always appreciate some help, wether that be in the form of bug reports, or code, or even just pointing out some typos.


+

Github

+
+
+ + + diff --git a/webpage/home.ts b/webpage/home.ts new file mode 100644 index 0000000..73aaeec --- /dev/null +++ b/webpage/home.ts @@ -0,0 +1,64 @@ +import {mobile} from "./login.js"; +console.log(mobile); +const serverbox=document.getElementById("instancebox") as HTMLDivElement; + +fetch("/instances.json").then(_=>_.json()).then((json:{name:string,description?:string,descriptionLong?:string,image?:string,url?:string,display?:boolean,online?:boolean, + uptime:{alltime:number,daytime:number,weektime:number}, + urls:{wellknown:string,api:string,cdn:string,gateway:string,login?:string}}[])=>{ + console.warn(json); + for(const instance of json){ + if(instance.display===false){ + continue; + } + const div=document.createElement("div"); + div.classList.add("flexltr","instance"); + if(instance.image){ + const img=document.createElement("img"); + img.src=instance.image; + div.append(img); + } + const statbox=document.createElement("div"); + statbox.classList.add("flexttb"); + + { + const textbox=document.createElement("div"); + textbox.classList.add("flexttb","instatancetextbox"); + const title=document.createElement("h2"); + title.innerText=instance.name; + if(instance.online!==undefined){ + const status=document.createElement("span"); + status.innerText=instance.online?"Online":"Offline"; + status.classList.add("instanceStatus"); + title.append(status); + } + textbox.append(title); + if(instance.description||instance.descriptionLong){ + const p=document.createElement("p"); + if(instance.descriptionLong){ + p.innerText=instance.descriptionLong; + }else{ + p.innerText=instance.description; + } + textbox.append(p); + } + statbox.append(textbox) + } + { + const stats=document.createElement("div"); + stats.classList.add("flexltr"); + const span=document.createElement("span"); + span.innerText=`Uptime: All time: ${Math.floor(instance.uptime.alltime*100)}% This week: ${Math.floor(instance.uptime.weektime*100)}% Today: ${Math.floor(instance.uptime.daytime*100)}%` + stats.append(span); + statbox.append(stats); + } + div.append(statbox); + div.onclick=_=>{ + if(instance.online){ + window.location.href="/register.html?instance="+encodeURI(instance.name); + }else{ + alert("Instance is offline, can't connect"); + } + } + serverbox.append(div); + } + }) diff --git a/webpage/login.ts b/webpage/login.ts index d9e50ae..b3aff49 100644 --- a/webpage/login.ts +++ b/webpage/login.ts @@ -409,7 +409,6 @@ if(datalist){ console.warn(json); if(instancein&&instancein.value===""){ instancein.value=json[0].name; - setTimeout(checkInstance,10); } for(const instance of json){ if(instance.display===false){ @@ -434,5 +433,6 @@ if(datalist){ } datalist.append(option); } + checkInstance(""); }) } diff --git a/webpage/style.css b/webpage/style.css index 9ab7b19..390b8f5 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -1257,6 +1257,7 @@ span { /* padding-bottom: .1in; */ align-items: flex-start; width: 100%; + height: 100%; } .settingbuttons{ padding-top:.075in; @@ -1958,12 +1959,12 @@ form div{ flex-direction: row; align-items: center; width: 100%; - padding: .03in .15in; + padding: .03in .1in; background: var(--profile-bg); border-bottom: solid var(--black); - position: absolute; + justify-content: center; } -#TitleButtons{ +.TitleButtons{ color:var(--primary-text); margin-left: .03in; padding: .05in; @@ -1971,10 +1972,82 @@ form div{ border:solid .03in var(--black); box-shadow: 0 0 .07in var(--shadow); transition: box-shadow .3s; + display: block; + width: fit-content; } -#TitleButtons:hover{ +.TitleButtons:hover{ box-shadow: 0 0 .01in var(--shadow); } #pageTitle{ margin-right:.1in; } +.pagehead{ + background: var(--channel-hover); + height:1in; + display: flex; + align-items: center; + border-bottom: solid var(--black); + justify-content: center; + font-size: .25in; + flex-shrink: 0; +} +.pagebox{ + padding: .06in; + background: var(--channel-hover); + margin: .1in; + min-height:2in; + border-radius:.1in; + box-sizing: border-box; + border: solid .04in black; + border-left: solid .05in darkblue; + box-shadow: .03in .04in .1in var(--black); + flex-shrink: 0; +} +#instancebox{ + display:flex; + flex-direction: row; + flex-wrap: wrap; + width: fit-content; +} +.instance{ + img{ + width:.6in; + height:.6in; + margin-right:.1in; + border-radius:.2in; + border:solid .03in var(--black); + } + h2{ + font-size:.25in; + } + flex-grow:0; + width:4.8in; + height: 1.6in; + background:var(--button-bg); + border-radius:.1in; + padding:.06in; + margin:.05in; + border:solid .04in var(--black); + box-shadow:0 0 .1in var(--shadow); + display: flex; + justify-content: center; + cursor:pointer; + user-select:none; +} +.instatancetextbox{ + background:var(--message-bg-hover); + border-radius:.1in; + padding:0.03in .05in; + border:solid .03in var(--black); + height: 1.2in; + display:flex; + justify-content: center; + width:3.9in; + margin-bottom:.1in; +} +.instanceStatus{ + font-size:.125in; + font-weight:100; + margin-left:.05in; +} +