mirror of
https://github.com/Open-Argon/isotope-server.git
synced 2026-10-28 09:43:51 +00:00
start adding account features
This commit is contained in:
1 parent
7b29ce72ea
commit
30ca8f4fcd
23 files changed
+472
-98
No files matched your search
@@ -1,20 +1,25 @@
|
|||||||
term.time("load")
|
term.time("load")
|
||||||
import "server" as *
|
import "server" as *
|
||||||
import "config" as *
|
import "config" as *
|
||||||
import "save"
|
import "database"
|
||||||
|
|
||||||
# website
|
# website
|
||||||
import "routes/website/home"
|
import "routes/website/home"
|
||||||
import "routes/website/search"
|
import "routes/website/search"
|
||||||
import "routes/website/package"
|
import "routes/website/package"
|
||||||
|
|
||||||
|
# account
|
||||||
|
import "routes/website/account/login"
|
||||||
|
import "routes/website/account/logout"
|
||||||
|
import "routes/website/account/account"
|
||||||
|
|
||||||
# isotope
|
# isotope
|
||||||
import "routes/isotope/push"
|
import "routes/isotope/push"
|
||||||
import "routes/isotope/search"
|
import "routes/isotope/search"
|
||||||
import "routes/isotope/download"
|
import "routes/isotope/download"
|
||||||
|
|
||||||
|
# error pages
|
||||||
import "routes/404"
|
import "routes/404"
|
||||||
|
|
||||||
term.timeEnd("load")
|
app.run()
|
||||||
app.run()
|
term.timeEnd("load")
|
||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
let packages = null
|
||||||
|
let accounts = null
|
||||||
|
let indexes = {username: {}, email: {}, id: {}}
|
||||||
|
let savePath = path.join([program.cwd, "packages-db.json"])
|
||||||
|
|
||||||
|
let changes = {n: 0, last: 0}
|
||||||
|
|
||||||
|
let makeChange() = changes.n = changes.n + 1
|
||||||
|
|
||||||
|
let randstr(length) = do
|
||||||
|
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
let str = []
|
||||||
|
for (i from 0 to length) do
|
||||||
|
str.append(chars[random.int(0, chars.length-1)])
|
||||||
|
return str.join("")
|
||||||
|
|
||||||
|
let load() = do
|
||||||
|
if (path.exists(savePath)) do
|
||||||
|
let file = file.read(savePath)
|
||||||
|
data = file.json()
|
||||||
|
if ("packages" not in data) do
|
||||||
|
packages = data
|
||||||
|
accounts = []
|
||||||
|
makeChange()
|
||||||
|
else do
|
||||||
|
packages = data.packages
|
||||||
|
accounts = data.accounts
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
let forceSave() = do
|
||||||
|
makeChange()
|
||||||
|
save()
|
||||||
|
|
||||||
|
let save() = do
|
||||||
|
if (changes.n == changes.last) return
|
||||||
|
changes.last = changes.n
|
||||||
|
let file = file.write(savePath)
|
||||||
|
file.json({packages: packages, accounts: accounts})
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
let saveThread() = do
|
||||||
|
forever do
|
||||||
|
try do
|
||||||
|
save()
|
||||||
|
catch (e) do
|
||||||
|
term.log(e)
|
||||||
|
time.snooze(10)
|
||||||
|
|
||||||
|
let account = {}
|
||||||
|
|
||||||
|
account.addToIndexes(acc, index) = do
|
||||||
|
indexes.username[acc.username] = index
|
||||||
|
indexes.email[acc.email] = index
|
||||||
|
indexes.id[acc.id] = index
|
||||||
|
|
||||||
|
account.makeId() = do
|
||||||
|
let id = "u-"+randstr(32)
|
||||||
|
if (account.getById(id) != null) return account.makeId()
|
||||||
|
return id
|
||||||
|
|
||||||
|
account.getById(id) = do
|
||||||
|
if (id in indexes.id) return accounts[indexes.id[id]]
|
||||||
|
for (i from 0 to accounts.length) do
|
||||||
|
if (accounts[i].id == id) do
|
||||||
|
account.addToIndexes(accounts[i], i)
|
||||||
|
return accounts[i]
|
||||||
|
return null
|
||||||
|
|
||||||
|
account.getByUsername(username) = do
|
||||||
|
if (username in indexes.username) return accounts[indexes.username[username]]
|
||||||
|
for (i from 0 to accounts.length) do
|
||||||
|
if (accounts[i].username == username) do
|
||||||
|
account.addToIndexes(accounts[i], i)
|
||||||
|
return accounts[i]
|
||||||
|
return null
|
||||||
|
|
||||||
|
account.getByEmail(email) = do
|
||||||
|
if (email in indexes.email) return accounts[indexes.email[email]]
|
||||||
|
for (i from 0 to accounts.length) do
|
||||||
|
if (accounts[i].email == email) do
|
||||||
|
account.addToIndexes(accounts[i], i)
|
||||||
|
return accounts[i]
|
||||||
|
return null
|
||||||
|
|
||||||
|
account.getByToken(token) = do
|
||||||
|
let split = token.splitN("|", 2)
|
||||||
|
if (split.length != 2) return null
|
||||||
|
let id = split[0]
|
||||||
|
let token = split[1]
|
||||||
|
let acc = account.getById(id)
|
||||||
|
if (acc == null) return null
|
||||||
|
if (token not in acc.tokens) return null
|
||||||
|
return acc
|
||||||
|
|
||||||
|
account.deleteToken(token) = do
|
||||||
|
let split = token.split(":")
|
||||||
|
if (split.length != 2) return false
|
||||||
|
let id = split[0]
|
||||||
|
let token = split[1]
|
||||||
|
let acc = account.getById(id)
|
||||||
|
if (acc == null) return false
|
||||||
|
if (token not in acc.tokens) return false
|
||||||
|
acc.tokens = acc.tokens.filter((t) = t != token)
|
||||||
|
forceSave()
|
||||||
|
return true
|
||||||
|
|
||||||
|
account.makeToken(acc) = do
|
||||||
|
let token = randstr(100)
|
||||||
|
acc.tokens.append(token)
|
||||||
|
makeChange()
|
||||||
|
return acc.id+"|"+token
|
||||||
|
|
||||||
|
account.saltHash(password, salt) = sha256(password+salt)
|
||||||
|
|
||||||
|
account.validPassword(password) = do
|
||||||
|
if (password.length < 8) return "Password must be at least 8 characters"
|
||||||
|
# include at least one number
|
||||||
|
let hasNumber = false
|
||||||
|
for (i from 0 to password.length) do
|
||||||
|
if (password[i] in "0123456789") do
|
||||||
|
hasNumber = true
|
||||||
|
break
|
||||||
|
if (not hasNumber) return "Password must include at least one number"
|
||||||
|
# include at least one uppercase letter
|
||||||
|
if (password.lower() == password) return "Password must include at least one uppercase letter"
|
||||||
|
# include at least one lowercase letter
|
||||||
|
if (password.upper() == password) return "Password must include at least one lowercase letter"
|
||||||
|
return null
|
||||||
|
|
||||||
|
account.validEmail(email) = do
|
||||||
|
let split = email.split("@")
|
||||||
|
if (split.length != 2) return false
|
||||||
|
if (split[0].length == 0) return false
|
||||||
|
if (" " in split[0]) return false
|
||||||
|
if (split[1].length == 0) return false
|
||||||
|
if (" " in split[1]) return false
|
||||||
|
if (split[1].split(".").length < 2) return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
account.validUsername(username) = do
|
||||||
|
if (username.length < 3) return false
|
||||||
|
if (username.length > 32) return false
|
||||||
|
for (i from 0 to username.length) do
|
||||||
|
if (username[i] not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
account.create(username, email, password) = do
|
||||||
|
if (not account.validUsername(username)) return {error: "Invalid username"}
|
||||||
|
if (not account.validEmail(email)) return {error: "Invalid email"}
|
||||||
|
if (account.getByUsername(username) != null) return {error: "Username already exists"}
|
||||||
|
if (account.getByEmail(email) != null) return {error: "Email already exists"}
|
||||||
|
if ((let error = account.validPassword(password)) != null) return {error: error}
|
||||||
|
let id = account.makeId()
|
||||||
|
let salt = randstr(100)
|
||||||
|
let hash = account.saltHash(password, salt)
|
||||||
|
let acc = {id: id, username: username, email:email, password: hash, salt: salt, tokens: []}
|
||||||
|
accounts.append(acc)
|
||||||
|
forceSave()
|
||||||
|
return acc
|
||||||
|
|
||||||
|
account.login(user, password) = do
|
||||||
|
let acc = account.getByUsername(user)
|
||||||
|
if (acc == null) do
|
||||||
|
acc = account.getByEmail(user)
|
||||||
|
if (acc == null) return {error: "Icorrect username or email"}
|
||||||
|
let hash = account.saltHash(password, acc.salt)
|
||||||
|
if (hash != acc.password) return {error: "Incorrect password"}
|
||||||
|
return {token: account.makeToken(acc)}
|
||||||
|
|
||||||
|
|
||||||
|
load()
|
||||||
|
t = thread(saveThread)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
term.log(account.create("test1", "sussy@gmail.com", "Password123"))
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import "bleach.ar" as bleach
|
||||||
|
import "database" as *
|
||||||
|
|
||||||
|
let navbar(acc) = do
|
||||||
|
let links = []
|
||||||
|
if (acc) do
|
||||||
|
links.append('<a href="/account">'+bleach.clean(acc.username)+'</a>')
|
||||||
|
else do
|
||||||
|
links.append('<a href="/login">Login</a>')
|
||||||
|
links.append('<a href="/register">Register</a>')
|
||||||
|
links.append('<a href="https://github.com/open-argon/Isotope">Github</a>')
|
||||||
|
return '<div class="navbar"><a href="/"><div class="logo"></div></a><div class="links">'+links.join("")+'</div></div>'
|
||||||
|
|
||||||
|
let middleware(req,res) = do
|
||||||
|
let acc = account.getByToken(req.cookies.get("token", ""))
|
||||||
|
res.templateValues.navbar = navbar(acc)
|
||||||
|
res.next()
|
||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
import "../server" as *
|
import "server" as *
|
||||||
|
import "database" as *
|
||||||
|
|
||||||
let err404(req, res) = do
|
let err404(req, res) = do
|
||||||
res.code = 404
|
res.code = 404
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "../server" as *
|
import "server" as *
|
||||||
import "../config" as *
|
import "config" as *
|
||||||
import "../save" as *
|
import "database" as *
|
||||||
|
|
||||||
let search(req, res) = do
|
let search(req, res) = do
|
||||||
let name = req.query.get("name", null)
|
let name = req.query.get("name", null)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "../server" as *
|
import "server" as *
|
||||||
import "../randstr" as *
|
import "randstr" as *
|
||||||
import "../save" as *
|
import "database" as *
|
||||||
|
|
||||||
let push(req, res) = do
|
let push(req, res) = do
|
||||||
let name = req.formdata.get("name", null)
|
let name = req.formdata.get("name", null)
|
||||||
@@ -35,6 +35,6 @@ let push(req, res) = do
|
|||||||
package.order[version] = time.now().unix()
|
package.order[version] = time.now().unix()
|
||||||
if (latest == "true") do
|
if (latest == "true") do
|
||||||
package.versions['latest'] = {redirect: version}
|
package.versions['latest'] = {redirect: version}
|
||||||
changes.n = changes.n + 1
|
makeChange()
|
||||||
res.send("OK")
|
res.send("OK")
|
||||||
app.route("/isotope-push", ['POST'], push)
|
app.route("/isotope-push", ['POST'], push)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import "../server" as *
|
import "server" as *
|
||||||
import "../config" as config
|
import "config" as config
|
||||||
import "../save" as *
|
import "database" as *
|
||||||
import "url.ar" as urllib
|
import "url.ar" as urllib
|
||||||
|
|
||||||
let search(req, res) = do
|
let search(req, res) = do
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import "server" as *
|
||||||
|
import "database" as *
|
||||||
|
import "url.ar" as url
|
||||||
|
|
||||||
|
let account(req, res) = do
|
||||||
|
let acc = account.getByToken(req.cookies.get("token", ""))
|
||||||
|
if (not acc) return res.redirect("/login?"+url.queryStr({to:req.path}))
|
||||||
|
res.renderTemplate("account.html", {})
|
||||||
|
app.route("/account", null, account)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import "server" as *
|
||||||
|
import "database" as *
|
||||||
|
|
||||||
|
let login(req, res) = do
|
||||||
|
let redirect = req.query.get("to", "/")
|
||||||
|
if (redirect == "/login") redirect = "/"
|
||||||
|
let acc = account.getByToken(req.cookies.get("token", ""))
|
||||||
|
if (acc) do
|
||||||
|
return res.redirect(redirect)
|
||||||
|
else if (req.method == "POST") do
|
||||||
|
let user = req.formdata.get("user")
|
||||||
|
let password = req.formdata.get("password")
|
||||||
|
let errResp(err) = res.renderTemplate("login.html", {errorMsg:err, user:user})
|
||||||
|
if (not user) return errResp("Missing user")
|
||||||
|
else if (not password) return errResp("Missing password")
|
||||||
|
let acc = account.login(user, password)
|
||||||
|
if ("error" in acc) return errResp(acc.error)
|
||||||
|
res.setCookie("token", {value:acc.token, path:"/"})
|
||||||
|
res.redirect(redirect)
|
||||||
|
return
|
||||||
|
res.renderTemplate("login.html", {})
|
||||||
|
app.route("/login", ["GET", "POST"], login)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import "server" as *
|
||||||
|
import "database" as *
|
||||||
|
|
||||||
|
let logout(req, res) = do
|
||||||
|
let redirect = req.query.get("to", "/login")
|
||||||
|
if (redirect == "/logout") redirect = "/login"
|
||||||
|
account.deleteToken(req.cookies.get("token", ""))
|
||||||
|
res.deleteCookie("token")
|
||||||
|
res.redirect(redirect)
|
||||||
|
app.route("/logout", null, logout)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import "../server" as *
|
import "server" as *
|
||||||
|
import "database" as *
|
||||||
|
|
||||||
let home(req, res) = do
|
let home(req, res) = do
|
||||||
res.renderTemplate("index.html", {})
|
res.renderTemplate("index.html", {})
|
||||||
|
|||||||
@@ -1,29 +1,39 @@
|
|||||||
import "../server" as *
|
import "server" as *
|
||||||
import "../save" as *
|
import "database" as *
|
||||||
import "url.ar" as url
|
import "url.ar" as url
|
||||||
import "bleach.ar" as bleach
|
import "bleach.ar" as bleach
|
||||||
import "shlex.ar" as shlex
|
import "shlex.ar" as shlex
|
||||||
|
import "render" as render
|
||||||
|
|
||||||
let package(req, res) = do
|
let package(req, res) = do
|
||||||
let name = req.query.get("name", null)
|
let acc = account.getByToken(req.cookies.get("token", ""))
|
||||||
|
let name = req.query.get("name")
|
||||||
|
if (not name) do
|
||||||
|
res.code = 404
|
||||||
|
res.renderTemplate("404.html", {})
|
||||||
|
return
|
||||||
|
|
||||||
let version = req.query.get("version", "latest")
|
let version = req.query.get("version", "latest")
|
||||||
let commandName = name
|
let commandName = name
|
||||||
if (version != "latest") commandName = commandName + "@"+version
|
if (version != "latest") commandName = commandName + "@"+version
|
||||||
let command = shlex.join(["isotope", "install", commandName])
|
let command = shlex.join(["isotope", "install", commandName])
|
||||||
let package = packages.get(name)
|
let package = packages.get(name)
|
||||||
if (package == null) do
|
if (package == null && version == "latest") do
|
||||||
|
package = {versions:{}, order:{}, name:name}
|
||||||
|
else if (package == null) do
|
||||||
res.code = 404
|
res.code = 404
|
||||||
res.renderTemplate("404.html", {})
|
res.renderTemplate("404.html", {})
|
||||||
return
|
return
|
||||||
if ("order" not in package) do
|
if ("order" not in package) do
|
||||||
package.order = {}
|
package.order = {}
|
||||||
changes.n = changes.n + 1
|
makeChange()
|
||||||
if (version not in package.versions) do
|
if (version not in package.versions && version != "latest") do
|
||||||
res.code = 404
|
res.code = 404
|
||||||
res.renderTemplate("404.html", {})
|
res.renderTemplate("404.html", {})
|
||||||
return
|
return
|
||||||
let versions = []
|
let versions = []
|
||||||
let keys = package.versions.keys()
|
let keys = package.versions.keys()
|
||||||
|
keys.sort()
|
||||||
keys.sort(true, (v) = package.order.get(v, 0))
|
keys.sort(true, (v) = package.order.get(v, 0))
|
||||||
for (i from 0 to keys.length) do
|
for (i from 0 to keys.length) do
|
||||||
let versioni = keys[i]
|
let versioni = keys[i]
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import "../server" as *
|
import "server" as *
|
||||||
import "../save" as *
|
import "database" as *
|
||||||
import "url.ar" as url
|
import "url.ar" as url
|
||||||
import "bleach.ar" as bleach
|
import "bleach.ar" as bleach
|
||||||
|
import "render" as renderer
|
||||||
|
|
||||||
let search(req, res) = do
|
let search(req, res) = do
|
||||||
|
let acc = account.getByToken(req.cookies.get("token", ""))
|
||||||
let query = req.query.get("q", "")
|
let query = req.query.get("q", "")
|
||||||
let keys = packages.keys()
|
let keys = packages.keys()
|
||||||
keys.sort()
|
keys.sort()
|
||||||
@@ -15,5 +17,5 @@ let search(req, res) = do
|
|||||||
if (query.lower() in name.lower()) do
|
if (query.lower() in name.lower()) do
|
||||||
render.append("<a href='/package?"+url.queryStr({name:name})+"'>"+bleach.clean(name)+"</a>")
|
render.append("<a href='/package?"+url.queryStr({name:name})+"'>"+bleach.clean(name)+"</a>")
|
||||||
results = results + 1
|
results = results + 1
|
||||||
res.renderTemplate("search.html", {query:query, numOfResults:results, results:render.join("")})
|
res.renderTemplate("search.html", {query:query, numOfResults:results, results:render.join(""), navbar:renderer.navbar(acc)})
|
||||||
app.route("/search", null, search)
|
app.route("/search", null, search)
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
let packages = {}
|
|
||||||
let savePath = path.join([program.cwd, "packages-db.json"])
|
|
||||||
|
|
||||||
let changes = {n: 0, last: 0}
|
|
||||||
|
|
||||||
let load() = do
|
|
||||||
if (path.exists(savePath)) do
|
|
||||||
let file = file.read(savePath)
|
|
||||||
packages = file.json()
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
let save() = do
|
|
||||||
if (changes.n == changes.last) return
|
|
||||||
changes.last = changes.n
|
|
||||||
let file = file.write(savePath)
|
|
||||||
file.json(packages)
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
let saveThread() = do
|
|
||||||
forever do
|
|
||||||
try do
|
|
||||||
save()
|
|
||||||
catch (e) do
|
|
||||||
term.log(e)
|
|
||||||
time.snooze(10)
|
|
||||||
|
|
||||||
load()
|
|
||||||
t = thread(saveThread)
|
|
||||||
t.start()
|
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import "http.ar" as http
|
import "http.ar" as http
|
||||||
|
import "render" as render
|
||||||
|
|
||||||
let app = http.server()
|
let app = http.server()
|
||||||
|
|
||||||
app.use(http.logger)
|
app.use(http.logger)
|
||||||
|
app.use(http.cookie)
|
||||||
app.use(http.formdata)
|
app.use(http.formdata)
|
||||||
|
app.use(render.middleware)
|
||||||
app.use(http.static("/","static"))
|
app.use(http.static("/","static"))
|
||||||
app.port = 7030
|
app.port = 7030
|
||||||
+53
-3
@@ -49,15 +49,23 @@ h6 {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar li a {
|
.navbar a {
|
||||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .links a {
|
||||||
|
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar li a:hover {
|
.navbar .links {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .links a:hover {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,4 +179,46 @@ pre {
|
|||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authForm {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authForm .input {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
margin: 1rem 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 25px;
|
||||||
|
color: var(--text);
|
||||||
|
background-color: var(--secondary);
|
||||||
|
border: 1px solid var(--primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authForm .submit {
|
||||||
|
width: 100px;
|
||||||
|
border-radius: 25px;
|
||||||
|
margin: auto;
|
||||||
|
color: var(--text);
|
||||||
|
background-color: var(--primary);
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
+1
-8
@@ -48,14 +48,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ul class="navbar">
|
{{%navbar}}
|
||||||
<li>
|
|
||||||
<a href="/"><div class="logo"></div></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+1
-8
@@ -48,14 +48,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ul class="navbar">
|
{{%navbar}}
|
||||||
<li>
|
|
||||||
<a href="/"><div class="logo"></div></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div>
|
<div>
|
||||||
<h1>Error 404</h1>
|
<h1>Error 404</h1>
|
||||||
<p>Page not found</p>
|
<p>Page not found</p>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Isotope</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/styles.css" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="194x194"
|
||||||
|
href="/favicon-194x194.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="192x192"
|
||||||
|
href="/android-chrome-192x192.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#0b1d28" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Isotope" />
|
||||||
|
<meta name="application-name" content="Isotope" />
|
||||||
|
<meta name="msapplication-TileColor" content="#0b1d28" />
|
||||||
|
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
|
||||||
|
<meta name="theme-color" content="#0b1d28" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
{{%navbar}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -48,14 +48,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ul class="navbar">
|
{{%navbar}}
|
||||||
<li>
|
|
||||||
<a href="/"><div class="logo"></div></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<h1>
|
<h1>
|
||||||
The official package manager for
|
The official package manager for
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Login - Isotope</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/styles.css" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="194x194"
|
||||||
|
href="/favicon-194x194.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="192x192"
|
||||||
|
href="/android-chrome-192x192.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#0b1d28" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Isotope" />
|
||||||
|
<meta name="application-name" content="Isotope" />
|
||||||
|
<meta name="msapplication-TileColor" content="#0b1d28" />
|
||||||
|
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
|
||||||
|
<meta name="theme-color" content="#0b1d28" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
{{%navbar}}
|
||||||
|
<form class="authForm" method="post" action="/login">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="user"
|
||||||
|
placeholder="Username or email address"
|
||||||
|
value="{{user}}"
|
||||||
|
required
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Password"
|
||||||
|
required
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
|
<p class="error">{{errorMsg}}</p>
|
||||||
|
<input type="submit" value="Login" class="submit" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -60,17 +60,13 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ul class="navbar">
|
{{%navbar}}
|
||||||
<li>
|
|
||||||
<a href="/"><div class="logo"></div></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div>
|
<div>
|
||||||
<h1>{{name}}@{{version}}</h1>
|
<h1>{{name}}@{{version}}</h1>
|
||||||
<pre onclick="copyToClipboard(event.target.dataset.command)" data-command="{{command}}">{{command}}<div style="float: right;margin-left: 1rem;">📋 Click to Copy</div></pre>
|
<pre
|
||||||
|
onclick="copyToClipboard(event.target.dataset.command)"
|
||||||
|
data-command="{{command}}"
|
||||||
|
>{{command}}<div style="float: right;margin-left: 1rem;">📋 Click to Copy</div></pre>
|
||||||
<h2>Versions</h2>
|
<h2>Versions</h2>
|
||||||
<div class="results">{{%versions}}</div>
|
<div class="results">{{%versions}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,14 +48,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ul class="navbar">
|
{{%navbar}}
|
||||||
<li>
|
|
||||||
<a href="/"><div class="logo"></div></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div>
|
<div>
|
||||||
<form action="/search" method="GET" class="search">
|
<form action="/search" method="GET" class="search">
|
||||||
<input
|
<input
|
||||||
|
|||||||
Reference in new issue
Block a user