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