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")
|
||||
import "server" as *
|
||||
import "config" as *
|
||||
import "save"
|
||||
import "database"
|
||||
|
||||
# website
|
||||
import "routes/website/home"
|
||||
import "routes/website/search"
|
||||
import "routes/website/package"
|
||||
|
||||
# account
|
||||
import "routes/website/account/login"
|
||||
import "routes/website/account/logout"
|
||||
import "routes/website/account/account"
|
||||
|
||||
# isotope
|
||||
import "routes/isotope/push"
|
||||
import "routes/isotope/search"
|
||||
import "routes/isotope/download"
|
||||
|
||||
|
||||
# error pages
|
||||
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
|
||||
res.code = 404
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "../server" as *
|
||||
import "../config" as *
|
||||
import "../save" as *
|
||||
import "server" as *
|
||||
import "config" as *
|
||||
import "database" as *
|
||||
|
||||
let search(req, res) = do
|
||||
let name = req.query.get("name", null)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "../server" as *
|
||||
import "../randstr" as *
|
||||
import "../save" as *
|
||||
import "server" as *
|
||||
import "randstr" as *
|
||||
import "database" as *
|
||||
|
||||
let push(req, res) = do
|
||||
let name = req.formdata.get("name", null)
|
||||
@@ -35,6 +35,6 @@ let push(req, res) = do
|
||||
package.order[version] = time.now().unix()
|
||||
if (latest == "true") do
|
||||
package.versions['latest'] = {redirect: version}
|
||||
changes.n = changes.n + 1
|
||||
makeChange()
|
||||
res.send("OK")
|
||||
app.route("/isotope-push", ['POST'], push)
|
||||
@@ -1,6 +1,6 @@
|
||||
import "../server" as *
|
||||
import "../config" as config
|
||||
import "../save" as *
|
||||
import "server" as *
|
||||
import "config" as config
|
||||
import "database" as *
|
||||
import "url.ar" as urllib
|
||||
|
||||
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
|
||||
res.renderTemplate("index.html", {})
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
import "../server" as *
|
||||
import "../save" as *
|
||||
import "server" as *
|
||||
import "database" as *
|
||||
import "url.ar" as url
|
||||
import "bleach.ar" as bleach
|
||||
import "shlex.ar" as shlex
|
||||
import "render" as render
|
||||
|
||||
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 commandName = name
|
||||
if (version != "latest") commandName = commandName + "@"+version
|
||||
let command = shlex.join(["isotope", "install", commandName])
|
||||
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.renderTemplate("404.html", {})
|
||||
return
|
||||
if ("order" not in package) do
|
||||
package.order = {}
|
||||
changes.n = changes.n + 1
|
||||
if (version not in package.versions) do
|
||||
makeChange()
|
||||
if (version not in package.versions && version != "latest") do
|
||||
res.code = 404
|
||||
res.renderTemplate("404.html", {})
|
||||
return
|
||||
let versions = []
|
||||
let keys = package.versions.keys()
|
||||
keys.sort()
|
||||
keys.sort(true, (v) = package.order.get(v, 0))
|
||||
for (i from 0 to keys.length) do
|
||||
let versioni = keys[i]
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import "../server" as *
|
||||
import "../save" as *
|
||||
import "server" as *
|
||||
import "database" as *
|
||||
import "url.ar" as url
|
||||
import "bleach.ar" as bleach
|
||||
import "render" as renderer
|
||||
|
||||
let search(req, res) = do
|
||||
let acc = account.getByToken(req.cookies.get("token", ""))
|
||||
let query = req.query.get("q", "")
|
||||
let keys = packages.keys()
|
||||
keys.sort()
|
||||
@@ -15,5 +17,5 @@ let search(req, res) = do
|
||||
if (query.lower() in name.lower()) do
|
||||
render.append("<a href='/package?"+url.queryStr({name:name})+"'>"+bleach.clean(name)+"</a>")
|
||||
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)
|
||||
@@ -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 "render" as render
|
||||
|
||||
let app = http.server()
|
||||
|
||||
app.use(http.logger)
|
||||
app.use(http.cookie)
|
||||
app.use(http.formdata)
|
||||
app.use(render.middleware)
|
||||
app.use(http.static("/","static"))
|
||||
app.port = 7030
|
||||
+53
-3
@@ -49,15 +49,23 @@ h6 {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navbar li a {
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
.navbar a {
|
||||
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-weight: 700;
|
||||
color: var(--accent);
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.navbar li a:hover {
|
||||
.navbar .links {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.navbar .links a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
@@ -171,4 +179,46 @@ pre {
|
||||
-moz-user-select: none;
|
||||
-ms-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>
|
||||
<body>
|
||||
<div class="content">
|
||||
<ul class="navbar">
|
||||
<li>
|
||||
<a href="/"><div class="logo"></div></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{%navbar}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+1
-8
@@ -48,14 +48,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<ul class="navbar">
|
||||
<li>
|
||||
<a href="/"><div class="logo"></div></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{%navbar}}
|
||||
<div>
|
||||
<h1>Error 404</h1>
|
||||
<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>
|
||||
<body>
|
||||
<div class="content">
|
||||
<ul class="navbar">
|
||||
<li>
|
||||
<a href="/"><div class="logo"></div></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{%navbar}}
|
||||
<div class="welcome">
|
||||
<h1>
|
||||
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>
|
||||
<body>
|
||||
<div class="content">
|
||||
<ul class="navbar">
|
||||
<li>
|
||||
<a href="/"><div class="logo"></div></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{%navbar}}
|
||||
<div>
|
||||
<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>
|
||||
<div class="results">{{%versions}}</div>
|
||||
</div>
|
||||
|
||||
@@ -48,14 +48,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<ul class="navbar">
|
||||
<li>
|
||||
<a href="/"><div class="logo"></div></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/open-argon/Isotope">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{%navbar}}
|
||||
<div>
|
||||
<form action="/search" method="GET" class="search">
|
||||
<input
|
||||
|
||||
Reference in new issue
Block a user