Field Validation
Create and edit modal forms are now validated for required input.
This commit is contained in:
parent
18c5af9c43
commit
8a5ba5a0b1
|
@ -23,7 +23,7 @@ build-ui:
|
|||
- npm install
|
||||
- npm run build
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
artifacts:
|
||||
paths:
|
||||
- dist/
|
||||
|
@ -36,7 +36,7 @@ build-x64-bin:
|
|||
tags:
|
||||
- linux
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
script:
|
||||
- cargo build --release
|
||||
- cd target/release
|
||||
|
@ -54,7 +54,7 @@ build-musl-bin:
|
|||
stage: build
|
||||
image: 'rust:latest'
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
script:
|
||||
- rustup target add x86_64-unknown-linux-musl
|
||||
- apt update && apt install -y musl-tools musl-dev
|
||||
|
@ -74,7 +74,7 @@ build-arm-bin:
|
|||
stage: build
|
||||
image: 'rust:latest'
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
script:
|
||||
- rustup target add armv7-unknown-linux-gnueabihf
|
||||
- apt update
|
||||
|
@ -113,7 +113,7 @@ deploy-dev-docker:
|
|||
- docker build -t $CI_REGISTRY/vade/vade-mecum .
|
||||
- docker push $CI_REGISTRY/vade/vade-mecum
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
|
||||
deploy-binaries:
|
||||
dependencies:
|
||||
|
|
|
@ -652,6 +652,17 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
|
@ -851,6 +862,22 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.9"
|
||||
|
@ -1738,18 +1765,6 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-named-pipes"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mio 0.6.23",
|
||||
"miow 0.3.7",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-uds"
|
||||
version = "0.6.8"
|
||||
|
@ -1885,6 +1900,15 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.14.0"
|
||||
|
@ -2872,6 +2896,12 @@ dependencies = [
|
|||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
|
@ -2883,6 +2913,21 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
|
@ -3010,20 +3055,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
|
||||
dependencies = [
|
||||
"bytes 0.5.6",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"iovec",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio 0.6.23",
|
||||
"mio-named-pipes",
|
||||
"mio-uds",
|
||||
"num_cpus",
|
||||
"pin-project-lite 0.1.12",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"tokio-macros",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
|
@ -3042,14 +3083,15 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"pin-project-lite 0.2.8",
|
||||
"signal-hook-registry",
|
||||
"tokio-macros",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "0.2.6"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -3375,6 +3417,7 @@ dependencies = [
|
|||
"base64",
|
||||
"bcrypt",
|
||||
"chrono",
|
||||
"clap",
|
||||
"jemallocator",
|
||||
"jsonwebtoken",
|
||||
"mime_guess",
|
||||
|
@ -3385,7 +3428,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokio 0.2.25",
|
||||
"tokio 1.16.1",
|
||||
"tracing",
|
||||
"tracing-actix-web",
|
||||
"tracing-subscriber",
|
||||
|
|
|
@ -26,10 +26,11 @@ bcrypt = "0.10.1"
|
|||
actix-web-httpauth = "0.6.0-beta.7"
|
||||
jsonwebtoken = "8.0.1"
|
||||
rand = "0.8.4"
|
||||
tokio = { verison = "1", features=["full"] }
|
||||
tokio = { version = "1.16.1", features=["full"] }
|
||||
base64 = "0.13.0"
|
||||
sqlx = { version = "^0.5", features=["sqlite", "migrate"] }
|
||||
reqwest = { version = "0.11.9", features = ["rustls-tls"], default-features=false }
|
||||
clap = { version = "3.0.14", features=["cargo", "env"] }
|
||||
|
||||
|
||||
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
|
||||
|
|
41
src/main.rs
41
src/main.rs
|
@ -2,6 +2,7 @@ use std::{collections::HashMap, path::Path};
|
|||
|
||||
use actix::SystemService;
|
||||
use actix_web::{get, web, App, HttpResponse, HttpServer};
|
||||
use clap::crate_version;
|
||||
use rust_embed::RustEmbed;
|
||||
use sea_orm::{prelude::*, Database};
|
||||
use tokio::sync::Mutex;
|
||||
|
@ -27,6 +28,29 @@ pub struct AppState {
|
|||
|
||||
#[actix_rt::main]
|
||||
async fn main() {
|
||||
let opts = clap::App::new("Vade Mecum")
|
||||
.version(crate_version!())
|
||||
.arg(
|
||||
clap::Arg::new("port")
|
||||
.short('p')
|
||||
.env("VADE_PORT")
|
||||
.long("port")
|
||||
.value_name("number")
|
||||
.default_value("8089")
|
||||
.help("Set the port for the HTTP server")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("db")
|
||||
.env("VADE_DB")
|
||||
.long("db")
|
||||
.value_name("path")
|
||||
.default_value("./")
|
||||
.help("Sets the path to the database location")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let subscriber = tracing_subscriber::registry().with(
|
||||
tracing_subscriber::fmt::Layer::new()
|
||||
.pretty()
|
||||
|
@ -36,13 +60,18 @@ async fn main() {
|
|||
);
|
||||
tracing::subscriber::set_global_default(subscriber).expect("Unable to set a global collector");
|
||||
|
||||
let db = setup_database().await.unwrap();
|
||||
let db = setup_database(opts.value_of("db").unwrap_or_default())
|
||||
.await
|
||||
.unwrap();
|
||||
let state = web::Data::new(AppState {
|
||||
db,
|
||||
healthcheck_status: Mutex::new(HashMap::new()),
|
||||
});
|
||||
|
||||
info!("Starting http server on 8080");
|
||||
info!(
|
||||
"Starting http server on {}",
|
||||
opts.value_of("port").unwrap_or_default()
|
||||
);
|
||||
|
||||
let st = state.clone();
|
||||
actix_rt::spawn(async move {
|
||||
|
@ -79,6 +108,8 @@ async fn main() {
|
|||
}
|
||||
});
|
||||
|
||||
let listen_host = format!("0.0.0.0:{}", opts.value_of("port").unwrap_or_default());
|
||||
|
||||
HttpServer::new(move || {
|
||||
let cors = actix_cors::Cors::permissive();
|
||||
App::new()
|
||||
|
@ -89,7 +120,7 @@ async fn main() {
|
|||
.service(api::routes())
|
||||
.service(dist)
|
||||
})
|
||||
.bind("0.0.0.0:8088")
|
||||
.bind(listen_host)
|
||||
.unwrap()
|
||||
.run()
|
||||
.await
|
||||
|
@ -118,10 +149,10 @@ async fn dist(path: web::Path<String>) -> HttpResponse {
|
|||
}
|
||||
|
||||
#[instrument]
|
||||
async fn setup_database() -> error::Result<DatabaseConnection> {
|
||||
async fn setup_database(db_path: &str) -> error::Result<DatabaseConnection> {
|
||||
let db_fname = "data.db";
|
||||
|
||||
if !Path::new(db_fname).exists() {
|
||||
if !Path::new(db_path).join(db_fname).exists() {
|
||||
std::fs::File::create(db_fname)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
<modal :open="open">
|
||||
<template #body>
|
||||
<form @submit.prevent="save">
|
||||
<text-field v-model="category.categoryName" label="Name" />
|
||||
<text-field required v-model="category.categoryName" label="Name" />
|
||||
<icon-picker v-model="category.glyph" />
|
||||
</form>
|
||||
</template>
|
||||
<template #actions>
|
||||
<btn @click="delCategory" label="Delete" danger v-if="!!this.category.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn primary @click="submit" :label="saveLabel" />
|
||||
<btn primary @click="submit" :label="saveLabel" :disabled="!allowSave"/>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
@ -39,6 +39,9 @@ export default {
|
|||
saveLabel() {
|
||||
return this.category.id ? "Update" : "Save";
|
||||
},
|
||||
allowSave() {
|
||||
return !!this.category.categoryName;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
:items="selectCategories"
|
||||
emptyLabel="No Category"
|
||||
/>
|
||||
<text-field v-model="app.appName" label="Name" />
|
||||
<text-field v-model="app.appName" label="Name" required />
|
||||
<text-field v-model="app.description" label="Description" />
|
||||
<text-field v-model="app.url" label="URL" />
|
||||
<text-field v-model="app.url" label="URL" required />
|
||||
<icon-picker v-model="app.glyph" />
|
||||
<switch-field v-model="app.enableHealthcheck" label="Health check" />
|
||||
</form>
|
||||
|
@ -19,7 +19,7 @@
|
|||
<template #actions>
|
||||
<btn @click="delApp" label="Delete" danger v-if="!!this.app.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn primary :label="saveLabel" type="button" @click="save" />
|
||||
<btn :disabled="!saveAllowed" primary :label="saveLabel" type="button" @click="save" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
@ -54,6 +54,9 @@ export default {
|
|||
selectCategories() {
|
||||
return this.categories.map((i) => ({ label: i.categoryName, value: i.id }));
|
||||
},
|
||||
saveAllowed() {
|
||||
return (!!this.app.appName && !!this.app.url)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
<modal :open="open">
|
||||
<template #body>
|
||||
<form @submit.prevent="save">
|
||||
<text-field v-model="category.categoryName" label="Name" />
|
||||
<text-field required v-model="category.categoryName" label="Name" />
|
||||
<icon-picker v-model="category.glyph" />
|
||||
</form>
|
||||
</template>
|
||||
<template #actions>
|
||||
<btn @click="delCategory" label="Delete" danger v-if="!!this.category.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn primary @click="submit" :label="saveLabel" />
|
||||
<btn primary :disabled="!allowSave" @click="submit" :label="saveLabel" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
@ -39,6 +39,9 @@ export default {
|
|||
saveLabel() {
|
||||
return this.category.id ? "Update" : "Save";
|
||||
},
|
||||
allowSave() {
|
||||
return this.category.categoryName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
:items="selectCategories"
|
||||
emptyLabel="No Category"
|
||||
/>
|
||||
<text-field v-model="bookmark.bookmarkName" label="Name" />
|
||||
<text-field v-model="bookmark.url" label="URL" />
|
||||
<text-field required v-model="bookmark.bookmarkName" label="Name" />
|
||||
<text-field required v-model="bookmark.url" label="URL" />
|
||||
</form>
|
||||
</template>
|
||||
<template #actions>
|
||||
<btn @click="delBookmark" label="Delete" danger v-if="!!this.bookmark.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn primary :label="saveLabel" type="button" @click="save" />
|
||||
<btn primary :label="saveLabel" :disabled="!allowSave" type="button" @click="save" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
@ -48,6 +48,9 @@ export default {
|
|||
selectCategories() {
|
||||
return this.categories.map((i) => ({ label: i.categoryName, value: i.id }));
|
||||
},
|
||||
allowSave() {
|
||||
return this.bookmark.bookmarkName && this.bookmark.url
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<button :class="{danger: danger, primary: primary}">{{label}}</button>
|
||||
<button :disabled="disabled" :class="{danger: danger, primary: primary, disabled: disabled}">{{label}}</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -8,6 +8,7 @@ export default {
|
|||
label: String,
|
||||
danger: Boolean,
|
||||
primary: Boolean,
|
||||
disabled: Boolean,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -29,21 +30,18 @@ button:hover {
|
|||
box-shadow: rgba(0, 0, 0, 0.42) 0px 1px 3px, rgba(0, 0, 0, 0.64) 0px 1px 2px;
|
||||
}
|
||||
|
||||
button.danger {
|
||||
background: #41141B;
|
||||
button.danger {background: #41141B;}
|
||||
button.danger:hover {background: #61141B;}
|
||||
|
||||
button.primary {background: #21244B;}
|
||||
button.primary:hover {background: #21246B;}
|
||||
|
||||
button.disabled, button.disabled:hover,
|
||||
button.primary.disabled, button.primary.disabled:hover,
|
||||
button.danger.disabled, button.danger.disabled:hover {
|
||||
color: #777;
|
||||
box-shadow: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
button.danger:hover {
|
||||
background: #61141B;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: #21244B;
|
||||
}
|
||||
|
||||
|
||||
button.primary:hover {
|
||||
background: #21246B;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<form-field>
|
||||
<label v-if="label">{{label}}</label>
|
||||
<input :value="modelValue" ref="field" @input="handleInput" :type="inputType" />
|
||||
<label v-if="label" :class="{error: isError}">{{label}}</label>
|
||||
<input :value="modelValue" ref="field" @input="handleInput" :type="inputType" :class="{error: isError}" />
|
||||
</form-field>
|
||||
</template>
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
|||
import FormField from "./FormField.vue";
|
||||
export default {
|
||||
props: {
|
||||
required: Boolean,
|
||||
label: String,
|
||||
modelValue: String,
|
||||
password: Boolean,
|
||||
|
@ -17,8 +18,13 @@ export default {
|
|||
inputType() {
|
||||
return this.password ? "password" : "text";
|
||||
},
|
||||
isError() {
|
||||
return this.required && !this.modelValue
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FormField
|
||||
},
|
||||
components: { FormField },
|
||||
methods: {
|
||||
handleInput() {
|
||||
this.$emit("update:modelValue", this.$refs.field.value);
|
||||
|
@ -43,5 +49,14 @@ input {
|
|||
input:focus {
|
||||
transition: all 0.2s;
|
||||
border-bottom: 1px solid #336699;
|
||||
}
|
||||
input.error {
|
||||
border-bottom: 1px solid #900;
|
||||
}
|
||||
input.error:focus {
|
||||
border-bottom: 1px solid #c00;
|
||||
}
|
||||
label.error {
|
||||
color: #eee;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -298,7 +298,7 @@ h2 svg {
|
|||
cursor: pointer;
|
||||
font-size: 44px;
|
||||
font-weight: 900;
|
||||
color: #252525;
|
||||
color: rgba(255,255,255,0.035);
|
||||
text-shadow: 1px 1px rgba(0,0,0,0.15);
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
|
|
Loading…
Reference in New Issue