diff --git a/.env.sample b/.env.sample
index 38bdda11fbe..44beb79a15f 100644
--- a/.env.sample
+++ b/.env.sample
@@ -86,3 +86,7 @@ export SENTRY_ENV_API=local
# export TEST_S3_INDEX_REGION=http://127.0.0.1:19000
# export TEST_AWS_ACCESS_KEY=minio
# export TEST_AWS_SECRET_KEY=miniominio
+
+# IDs of GitHub users that are admins on this instance, separated by commas.
+# Whitespace will be ignored.
+export GH_ADMIN_USER_IDS=
diff --git a/Cargo.lock b/Cargo.lock
index 64381d556eb..013b4f17cbc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -681,6 +681,7 @@ dependencies = [
"ipnetwork",
"lettre",
"minijinja",
+ "minijinja-autoreload",
"moka",
"oauth2",
"object_store",
@@ -692,6 +693,7 @@ dependencies = [
"reqwest",
"retry",
"ring",
+ "rust-embed",
"scheduled-thread-pool",
"secrecy",
"semver",
@@ -1126,6 +1128,15 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "futf"
version = "0.1.5"
@@ -1597,6 +1608,26 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags 1.3.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "inout"
version = "0.1.3"
@@ -1701,6 +1732,26 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "kqueue"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -1872,6 +1923,12 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+[[package]]
+name = "memo-map"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aec276c09560ce4447087aaefc19eb0c18d97e31bd05ebac38881c4723400c40"
+
[[package]]
name = "memoffset"
version = "0.9.0"
@@ -1924,9 +1981,21 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822ebf53abf914648234330ca59b88226ed278551b1b42b47e472957c12660d6"
dependencies = [
+ "memo-map",
+ "self_cell",
"serde",
]
+[[package]]
+name = "minijinja-autoreload"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc3c1e0fe1a3a8a88778f4d9624514c35529813b73d2780d832b08fd13fb0799"
+dependencies = [
+ "minijinja",
+ "notify",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -1949,6 +2018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
+ "log",
"wasi",
"windows-sys 0.48.0",
]
@@ -2012,6 +2082,23 @@ dependencies = [
"minimal-lexical",
]
+[[package]]
+name = "notify"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
+dependencies = [
+ "bitflags 1.3.2",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "mio",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -2666,6 +2753,40 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "rust-embed"
+version = "6.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "6.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.25",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "7.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@@ -2826,6 +2947,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "self_cell"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6"
+
[[package]]
name = "semver"
version = "1.0.17"
diff --git a/Cargo.toml b/Cargo.toml
index b08cb44fd14..7ddde731b44 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,7 +63,8 @@ indicatif = "=0.17.5"
ipnetwork = "=0.20.0"
tikv-jemallocator = { version = "=0.5.0", features = ['unprefixed_malloc_on_supported_platforms', 'profiling'] }
lettre = { version = "=0.10.4", default-features = false, features = ["file-transport", "smtp-transport", "native-tls", "hostname", "builder"] }
-minijinja = "=1.0.4"
+minijinja = { version = "=1.0.4", features = ["loader"] }
+minijinja-autoreload = "=1.0.4"
moka = { version = "=0.11.2", features = ["future"] }
oauth2 = { version = "=4.4.1", default-features = false, features = ["reqwest"] }
object_store = { version = "=0.6.1", features = ["aws"] }
@@ -75,6 +76,7 @@ rand = "=0.8.5"
reqwest = { version = "=0.11.18", features = ["blocking", "gzip", "json"] }
retry = "=2.0.0"
ring = "=0.16.20"
+rust-embed = "=6.8.1"
scheduled-thread-pool = "=0.2.7"
secrecy = "=0.8.0"
semver = { version = "=1.0.17", features = ["serde"] }
diff --git a/admin/templates/base.html b/admin/templates/base.html
new file mode 100644
index 00000000000..1fadea264ea
--- /dev/null
+++ b/admin/templates/base.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ {% block title %}
+
+ {% endblock %}:: crates.io
+
+
+
+
+ {% block head %}
+
+ {% endblock %}
+
+
+
+
+
+ {% block content %}
+
+ {% endblock %}
+
+
+
diff --git a/admin/templates/crates/index.html b/admin/templates/crates/index.html
new file mode 100644
index 00000000000..6a959711124
--- /dev/null
+++ b/admin/templates/crates/index.html
@@ -0,0 +1,136 @@
+{% extends 'base.html' %}
+{% from 'macros.html' import datetime, page_list, user %}
+
+{% block head %}
+
+
+
+
+{% endblock %}
+
+{% block title %}
+ Crates
+{% endblock %}
+
+{% block content %}
+
+
Crates
+
+
+
+
+
+
+
+
+ Crate |
+ Version |
+ Published |
+ |
+
+
+
+ {% for version in view.versions %}
+
+
+
+
+ |
+ {{ version.num }} |
+
+ {{ datetime(version.created_at) }}
+ by
+ {{ user(version.publisher) }}
+ |
+
+
+ {% if version.yanked %}
+
+ {% else %}
+
+ {% endif %}
+ |
+
+ {% endfor %}
+
+
+
+ {{ page_list(view.page) }}
+
+
+{% endblock %}
diff --git a/admin/templates/macros.html b/admin/templates/macros.html
new file mode 100644
index 00000000000..555fd8b29af
--- /dev/null
+++ b/admin/templates/macros.html
@@ -0,0 +1,53 @@
+{% macro datetime(dt) %}
+ {{ dt.human }}
+{% endmacro %}
+
+{% macro page_list(page) %}
+ {% if page.paginated %}
+
+ {% endif %}
+{% endmacro %}
+
+{% macro user(user) %}
+
+ {% if user.avatar %}
+
+ {%- endif -%}
+ {{- user.username }}
+
+{% endmacro %}
diff --git a/app/components/header.hbs b/app/components/header.hbs
index 9c1368d7761..cda3d543d3e 100644
--- a/app/components/header.hbs
+++ b/app/components/header.hbs
@@ -29,6 +29,9 @@
Dashboard
Account Settings
Owner Invites
+ {{#if this.session.currentUser.admin}}
+ Site Admin
+ {{/if}}