This commit is contained in:
parent
9dcb5114f3
commit
32aaa1c464
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
||||||
/target
|
/target
|
||||||
/sled
|
/sled
|
||||||
|
/.envrc
|
||||||
|
/.direnv
|
||||||
|
/result
|
||||||
|
|
436
Cargo.lock
generated
436
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pict-rs-aggregator"
|
name = "pict-rs-aggregator"
|
||||||
description = "A simple image aggregation service for pict-rs"
|
description = "A simple image aggregation service for pict-rs"
|
||||||
version = "0.2.0-beta.2"
|
version = "0.2.0-beta.1"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -18,7 +18,7 @@ default = []
|
||||||
actix-rt = "2.7.0"
|
actix-rt = "2.7.0"
|
||||||
actix-web = { version = "4.0.0", default-features = false }
|
actix-web = { version = "4.0.0", default-features = false }
|
||||||
awc = { version = "3.0.0", default-features = false }
|
awc = { version = "3.0.0", default-features = false }
|
||||||
bcrypt = "0.13"
|
bcrypt = "0.14"
|
||||||
clap = { version = "4.0.2", features = ["derive", "env"] }
|
clap = { version = "4.0.2", features = ["derive", "env"] }
|
||||||
console-subscriber = "0.1"
|
console-subscriber = "0.1"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
@ -46,7 +46,7 @@ uuid = { version = "1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
|
|
||||||
[dependencies.tracing-actix-web]
|
[dependencies.tracing-actix-web]
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["emit_event_on_error", "opentelemetry_0_18"]
|
features = ["emit_event_on_error", "opentelemetry_0_18"]
|
||||||
|
|
||||||
|
|
43
flake.lock
Normal file
43
flake.lock
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1676283394,
|
||||||
|
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1678293141,
|
||||||
|
"narHash": "sha256-lLlQHaR0y+q6nd6kfpydPTGHhl1rS9nU9OQmztzKOYs=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "c90c4025bb6e0c4eaf438128a3b2640314b1c58d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
34
flake.nix
Normal file
34
flake.nix
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
description = "pict-rs-aggregator";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages = rec {
|
||||||
|
pict-rs-aggregator = pkgs.callPackage ./pict-rs-aggregator.nix { };
|
||||||
|
|
||||||
|
default = pict-rs-aggregator;
|
||||||
|
};
|
||||||
|
|
||||||
|
apps = rec {
|
||||||
|
dev = flake-utils.lib.mkApp { drv = self.packages.${system}.pict-rs-aggregator; };
|
||||||
|
default = dev;
|
||||||
|
};
|
||||||
|
|
||||||
|
devShell = with pkgs; mkShell {
|
||||||
|
nativeBuildInputs = [ cargo cargo-outdated cargo-zigbuild clippy gcc protobuf rust-analyzer rustc rustfmt ];
|
||||||
|
|
||||||
|
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
27
pict-rs-aggregator.nix
Normal file
27
pict-rs-aggregator.nix
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{ lib
|
||||||
|
, makeWrapper
|
||||||
|
, nixosTests
|
||||||
|
, protobuf
|
||||||
|
, rustPlatform
|
||||||
|
, stdenv
|
||||||
|
}:
|
||||||
|
|
||||||
|
rustPlatform.buildRustPackage {
|
||||||
|
pname = "pict-rs-aggregator";
|
||||||
|
version = "0.2.0-beta.1";
|
||||||
|
src = ./.;
|
||||||
|
cargoSha256 = "UHTjI4UAVFP4KnkyWuubiVkFev+/z3PvCvDZVOy+Kxs=";
|
||||||
|
|
||||||
|
PROTOC = "${protobuf}/bin/protoc";
|
||||||
|
PROTOC_INCLUDE = "${protobuf}/include";
|
||||||
|
|
||||||
|
nativeBuildInputs = [ ];
|
||||||
|
|
||||||
|
passthru.tests = { inherit (nixosTests) pict-rs-aggregator; };
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "A simple image hosting service";
|
||||||
|
homepage = "https://git.asonix.dog/asonix/pict-rs-aggregator";
|
||||||
|
license = with licenses; [ agpl3Plus ];
|
||||||
|
};
|
||||||
|
}
|
64
src/lib.rs
64
src/lib.rs
|
@ -163,9 +163,9 @@ impl State {
|
||||||
} else if s.is_empty() {
|
} else if s.is_empty() {
|
||||||
self.scope.clone()
|
self.scope.clone()
|
||||||
} else if self.scope.is_empty() {
|
} else if self.scope.is_empty() {
|
||||||
format!("/{s}")
|
format!("/{}", s)
|
||||||
} else {
|
} else {
|
||||||
format!("{}/{s}", self.scope)
|
format!("{}/{}", self.scope, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,31 +174,37 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_collection_path(&self, id: Uuid, token: &ValidToken) -> String {
|
fn edit_collection_path(&self, id: Uuid, token: &ValidToken) -> String {
|
||||||
self.scoped(&format!("{id}?token={}", token.token))
|
self.scoped(&format!("{}?token={}", id, token.token))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_collection_path(&self, id: Uuid, token: &ValidToken) -> String {
|
fn update_collection_path(&self, id: Uuid, token: &ValidToken) -> String {
|
||||||
self.scoped(&format!("{id}?token={}", token.token))
|
self.scoped(&format!("{}?token={}", id, token.token))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_collection_path(&self, id: Uuid, token: &ValidToken, confirmed: bool) -> String {
|
fn delete_collection_path(&self, id: Uuid, token: &ValidToken, confirmed: bool) -> String {
|
||||||
if confirmed {
|
if confirmed {
|
||||||
self.scoped(&format!("{id}/delete?token={}&confirmed=true", token.token))
|
self.scoped(&format!(
|
||||||
|
"{}/delete?token={}&confirmed=true",
|
||||||
|
id, token.token
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
self.scoped(&format!("{id}/delete?token={}", token.token))
|
self.scoped(&format!("{}/delete?token={}", id, token.token))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_collection_path(&self, id: Uuid) -> String {
|
fn public_collection_path(&self, id: Uuid) -> String {
|
||||||
self.scoped(&format!("{id}"))
|
self.scoped(&format!("{}", id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_entry_path(&self, collection_id: Uuid, token: &ValidToken) -> String {
|
fn create_entry_path(&self, collection_id: Uuid, token: &ValidToken) -> String {
|
||||||
self.scoped(&format!("{collection_id}/entry?token={}", token.token))
|
self.scoped(&format!("{}/entry?token={}", collection_id, token.token))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entry_path(&self, collection_id: Uuid, id: Uuid, token: &ValidToken) -> String {
|
fn update_entry_path(&self, collection_id: Uuid, id: Uuid, token: &ValidToken) -> String {
|
||||||
self.scoped(&format!("{collection_id}/entry/{id}?token={}", token.token))
|
self.scoped(&format!(
|
||||||
|
"{}/entry/{}?token={}",
|
||||||
|
collection_id, id, token.token
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_entry_path(
|
fn move_entry_path(
|
||||||
|
@ -209,8 +215,8 @@ impl State {
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
) -> String {
|
) -> String {
|
||||||
self.scoped(&format!(
|
self.scoped(&format!(
|
||||||
"{collection_id}/entry/{id}/move/{direction}?token={}",
|
"{}/entry/{}/move/{}?token={}",
|
||||||
token.token
|
collection_id, id, direction, token.token
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,24 +229,25 @@ impl State {
|
||||||
) -> String {
|
) -> String {
|
||||||
if confirmed {
|
if confirmed {
|
||||||
self.scoped(&format!(
|
self.scoped(&format!(
|
||||||
"{collection_id}/entry/{id}/delete?token={}&confirmed=true",
|
"{}/entry/{}/delete?token={}&confirmed=true",
|
||||||
token.token
|
collection_id, id, token.token
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
self.scoped(&format!(
|
self.scoped(&format!(
|
||||||
"{collection_id}/entry/{id}/delete?token={}",
|
"{}/entry/{}/delete?token={}",
|
||||||
token.token
|
collection_id, id, token.token
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn statics_path(&self, file: &str) -> String {
|
fn statics_path(&self, file: &str) -> String {
|
||||||
self.scoped(&format!("static/{file}"))
|
self.scoped(&format!("static/{}", file))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn thumbnail_path(&self, filename: &str, size: u16, extension: pict::Extension) -> String {
|
fn thumbnail_path(&self, filename: &str, size: u16, extension: pict::Extension) -> String {
|
||||||
self.scoped(&format!(
|
self.scoped(&format!(
|
||||||
"image/thumbnail.{extension}?src={filename}&size={size}",
|
"image/thumbnail.{}?src={}&size={}",
|
||||||
|
extension, filename, size
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,8 +256,9 @@ impl State {
|
||||||
|
|
||||||
for size in connection::VALID_SIZES {
|
for size in connection::VALID_SIZES {
|
||||||
sizes.push(format!(
|
sizes.push(format!(
|
||||||
"{} {size}w",
|
"{} {}w",
|
||||||
self.thumbnail_path(filename, *size, extension),
|
self.thumbnail_path(filename, *size, extension),
|
||||||
|
size,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +266,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn image_path(&self, filename: &str) -> String {
|
fn image_path(&self, filename: &str) -> String {
|
||||||
self.scoped(&format!("image/full/{filename}"))
|
self.scoped(&format!("image/full/{}", filename))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +285,6 @@ pub fn configure(cfg: &mut web::ServiceConfig, state: State, client: Client) {
|
||||||
client,
|
client,
|
||||||
)))
|
)))
|
||||||
.app_data(web::Data::new(state))
|
.app_data(web::Data::new(state))
|
||||||
.route("/healthz", web::get().to(healthz))
|
|
||||||
.service(web::resource("/static/{filename}").route(web::get().to(static_files)))
|
.service(web::resource("/static/{filename}").route(web::get().to(static_files)))
|
||||||
.service(web::resource("/404").route(web::get().to(not_found)))
|
.service(web::resource("/404").route(web::get().to(not_found)))
|
||||||
.service(
|
.service(
|
||||||
|
@ -348,11 +355,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn healthz(state: web::Data<State>) -> Result<HttpResponse, StateError> {
|
|
||||||
state.store.check_health().await.stateful(&state)?;
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(name = "Static files")]
|
#[tracing::instrument(name = "Static files")]
|
||||||
async fn static_files(filename: web::Path<String>, state: web::Data<State>) -> HttpResponse {
|
async fn static_files(filename: web::Path<String>, state: web::Data<State>) -> HttpResponse {
|
||||||
let filename = filename.into_inner();
|
let filename = filename.into_inner();
|
||||||
|
@ -678,7 +680,7 @@ async fn upload(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("{e}");
|
tracing::warn!("{}", e);
|
||||||
let _ = store::DeleteEntry {
|
let _ = store::DeleteEntry {
|
||||||
entry_path: &entry_path2,
|
entry_path: &entry_path2,
|
||||||
}
|
}
|
||||||
|
@ -775,13 +777,7 @@ async fn view_collection(
|
||||||
|
|
||||||
rendered(
|
rendered(
|
||||||
|cursor| {
|
|cursor| {
|
||||||
self::templates::view_collection_html(
|
self::templates::view_collection_html(cursor, path.collection, &collection, &entries, &state)
|
||||||
cursor,
|
|
||||||
path.collection,
|
|
||||||
&collection,
|
|
||||||
&entries,
|
|
||||||
&state,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
HttpResponse::Ok(),
|
HttpResponse::Ok(),
|
||||||
)
|
)
|
||||||
|
@ -980,7 +976,7 @@ fn to_svg_string(qr: &qrcodegen::QrCode, border: i32) -> String {
|
||||||
.checked_add(border.checked_mul(2).unwrap())
|
.checked_add(border.checked_mul(2).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
result += &format!(
|
result += &format!(
|
||||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {dimension} {dimension}\" stroke=\"none\">\n");
|
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {0} {0}\" stroke=\"none\">\n", dimension);
|
||||||
result += "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n";
|
result += "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n";
|
||||||
result += "\t<path d=\"";
|
result += "\t<path d=\"";
|
||||||
for y in 0..qr.size() {
|
for y in 0..qr.size() {
|
||||||
|
|
|
@ -3,22 +3,22 @@
|
||||||
@(text: &str, kind: ButtonKind)
|
@(text: &str, kind: ButtonKind)
|
||||||
|
|
||||||
@match kind {
|
@match kind {
|
||||||
ButtonKind::Submit => {
|
ButtonKind::Submit => {
|
||||||
<div class="button submit">
|
<div class="button submit">
|
||||||
<span>@text</span>
|
<span>@text</span>
|
||||||
<button class="action" type="submit">@text</button>
|
<button class="action" type="submit">@text</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
ButtonKind::Plain => {
|
ButtonKind::Plain => {
|
||||||
<div class="button plain">
|
<div class="button plain">
|
||||||
<span>@text</span>
|
<span>@text</span>
|
||||||
<button class="action">@text</button>
|
<button class="action">@text</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
ButtonKind::Outline => {
|
ButtonKind::Outline => {
|
||||||
<div class="button outline">
|
<div class="button outline">
|
||||||
<span>@text</span>
|
<span>@text</span>
|
||||||
<button class="action">@text</button>
|
<button class="action">@text</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,23 @@
|
||||||
@(text: &str, href: &str, kind: ButtonKind)
|
@(text: &str, href: &str, kind: ButtonKind)
|
||||||
|
|
||||||
@match kind {
|
@match kind {
|
||||||
ButtonKind::Submit => {
|
ButtonKind::Submit => {
|
||||||
<div class="button submit">
|
<div class="button submit">
|
||||||
<span>@text</span>
|
<span>@text</span>
|
||||||
<a class="action" href="@href">@text</a>
|
<a class="action" href="@href">@text</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
ButtonKind::Plain => {
|
ButtonKind::Plain => {
|
||||||
<div class="button plain">
|
<div class="button plain">
|
||||||
<span>@text</span>
|
<span>@text</span>
|
||||||
<a class="action" href="@href">@text</a>
|
<a class="action" href="@href">@text</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
ButtonKind::Outline => {
|
ButtonKind::Outline => {
|
||||||
<div class="button outline">
|
<div class="button outline">
|
||||||
<span>@text</span>
|
<span>@text</span>
|
||||||
<a class="action" href="@href">@text</a>
|
<a class="action" href="@href">@text</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,25 +4,24 @@
|
||||||
|
|
||||||
@(id: Uuid, collection: &Collection, token: &ValidToken, state: &State)
|
@(id: Uuid, collection: &Collection, token: &ValidToken, state: &State)
|
||||||
|
|
||||||
@:layout_html(state, &format!("Delete: {}", collection.title), Some(&format!("Are you sure you want to delete {}",
|
@:layout_html(state, &format!("Delete: {}", collection.title), Some(&format!("Are you sure you want to delete {}", collection.title)), {
|
||||||
collection.title)), {
|
<meta property="og:url" content="@state.delete_collection_path(id, token, false)" />
|
||||||
<meta property="og:url" content="@state.delete_collection_path(id, token, false)" />
|
|
||||||
}, {
|
}, {
|
||||||
<section>
|
<section>
|
||||||
<article>
|
<article>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<h3>Delete @collection.title</h3>
|
<h3>Delete @collection.title</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<p class="subtitle">Are you sure you want to delete @collection.title</p>
|
<p class="subtitle">Are you sure you want to delete @collection.title</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
@:button_link_html("Delete Collection", &state.delete_collection_path(id, token, true), ButtonKind::Submit)
|
@:button_link_html("Delete Collection", &state.delete_collection_path(id, token, true), ButtonKind::Submit)
|
||||||
@:button_link_html("Cancel", &state.edit_collection_path(id, token), ButtonKind::Outline)
|
@:button_link_html("Cancel", &state.edit_collection_path(id, token), ButtonKind::Outline)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
@:return_home_html(state)
|
@:return_home_html(state)
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,29 +5,29 @@
|
||||||
@(collection_id: Uuid, id: Uuid, entry: &Entry, token: &ValidToken, state: &State)
|
@(collection_id: Uuid, id: Uuid, entry: &Entry, token: &ValidToken, state: &State)
|
||||||
|
|
||||||
@:layout_html(state, "Delete Image", Some("Are you sure you want to delete this image?"), {
|
@:layout_html(state, "Delete Image", Some("Are you sure you want to delete this image?"), {
|
||||||
<meta property="og:url" content="@state.delete_entry_path(collection_id, id, token, false)" />
|
<meta property="og:url" content="@state.delete_entry_path(collection_id, id, token, false)" />
|
||||||
}, {
|
}, {
|
||||||
<section>
|
<section>
|
||||||
<article>
|
<article>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<h3>Delete Image</h3>
|
<h3>Delete Image</h3>
|
||||||
</div>
|
|
||||||
<div class="content-group">
|
|
||||||
<div class="edit-row">
|
|
||||||
<div class="edit-item">
|
|
||||||
@:image_html(entry, state)
|
|
||||||
</div>
|
</div>
|
||||||
<div class="edit-item">
|
<div class="content-group">
|
||||||
<p class="delete-confirmation">Are you sure you want to delete this image?</p>
|
<div class="edit-row">
|
||||||
<div class="button-group button-space">
|
<div class="edit-item">
|
||||||
@:button_link_html("Delete Image", &state.delete_entry_path(collection_id, id, token, true),
|
@:image_html(entry, state)
|
||||||
ButtonKind::Submit)
|
</div>
|
||||||
@:button_link_html("Cancel", &state.edit_collection_path(collection_id, token), ButtonKind::Outline)
|
<div class="edit-item">
|
||||||
</div>
|
<p class="delete-confirmation">Are you sure you want to delete this image?</p>
|
||||||
|
<div class="button-group button-space">
|
||||||
|
@:button_link_html("Delete Image", &state.delete_entry_path(collection_id, id, token, true), ButtonKind::Submit)
|
||||||
|
@:button_link_html("Cancel", &state.edit_collection_path(collection_id, token), ButtonKind::Outline)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</section>
|
</section>
|
||||||
@:return_home_html(state)
|
@:return_home_html(state)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
@use crate::{ui::ButtonKind, Collection, Direction, Entry, State, ValidToken};
|
@use crate::{ui::ButtonKind, Collection, Direction, Entry, State, ValidToken};
|
||||||
@use super::{button_html, button_link_html, image_html, file_input_html, layout_html, return_home_html, text_area_html,
|
@use super::{button_html, button_link_html, image_html, file_input_html, layout_html, return_home_html, text_area_html, text_input_html,
|
||||||
text_input_html,
|
|
||||||
statics::file_upload_js};
|
statics::file_upload_js};
|
||||||
@use uuid::Uuid;
|
@use uuid::Uuid;
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
|
|
||||||
@:layout_html(state, "Error", Some(error), {}, {
|
@:layout_html(state, "Error", Some(error), {}, {
|
||||||
<section>
|
<section>
|
||||||
<article>
|
<article>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<h3>Error</h3>
|
<h3>Error</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-group">
|
<div class="content-group">
|
||||||
<p class="subtitle">@error</p>
|
<p class="subtitle">@error</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
@:return_home_html(state)
|
@:return_home_html(state)
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
|
|
||||||
@:layout_html(state, "Not Found", None, {}, {
|
@:layout_html(state, "Not Found", None, {}, {
|
||||||
<section>
|
<section>
|
||||||
<article class="content-group">
|
<article class="content-group">
|
||||||
<h3>Not Found</h3>
|
<h3>Not Found</h3>
|
||||||
</article>
|
</article>
|
||||||
<article class="content-group">
|
<article class="content-group">
|
||||||
<p><a href="@state.create_collection_path()">Return Home</a></p>
|
<p><a href="@state.create_collection_path()">Return Home</a></p>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue