Compare commits
No commits in common. "b1b3d21d1a4a1653510e032a479d71f401ac9d8f" and "873eaad79caedc3c952d0257542eb9ff59a54209" have entirely different histories.
b1b3d21d1a
...
873eaad79c
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
ALTER TABLE application ADD COLUMN favicon Boolean DEFAULT false;
|
|
||||||
ALTER TABLE bookmark ADD COLUMN favicon Boolean DEFAULT false;
|
|
|
@ -15,7 +15,6 @@ struct ApiApplication {
|
||||||
pub application_category_id: Option<i32>,
|
pub application_category_id: Option<i32>,
|
||||||
pub enable_healthcheck: bool,
|
pub enable_healthcheck: bool,
|
||||||
pub healthcheck_status: Option<bool>,
|
pub healthcheck_status: Option<bool>,
|
||||||
pub favicon: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<application::Model> for ApiApplication {
|
impl From<application::Model> for ApiApplication {
|
||||||
|
@ -30,7 +29,6 @@ impl From<application::Model> for ApiApplication {
|
||||||
application_category_id: model.application_category_id,
|
application_category_id: model.application_category_id,
|
||||||
enable_healthcheck: model.enable_healthcheck,
|
enable_healthcheck: model.enable_healthcheck,
|
||||||
healthcheck_status: None,
|
healthcheck_status: None,
|
||||||
favicon: model.favicon,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +65,6 @@ pub async fn new_application(
|
||||||
glyph: Set(data.0.glyph),
|
glyph: Set(data.0.glyph),
|
||||||
application_category_id: Set(data.0.application_category_id),
|
application_category_id: Set(data.0.application_category_id),
|
||||||
enable_healthcheck: Set(data.0.enable_healthcheck),
|
enable_healthcheck: Set(data.0.enable_healthcheck),
|
||||||
favicon: Set(data.0.favicon),
|
|
||||||
};
|
};
|
||||||
let app = model.insert(&state.db).await?;
|
let app = model.insert(&state.db).await?;
|
||||||
Ok(HttpResponse::Ok().json(app))
|
Ok(HttpResponse::Ok().json(app))
|
||||||
|
@ -109,7 +106,6 @@ pub async fn update_applications(
|
||||||
application_category_id: Set(data.application_category_id),
|
application_category_id: Set(data.application_category_id),
|
||||||
glyph: Set(data.glyph),
|
glyph: Set(data.glyph),
|
||||||
enable_healthcheck: Set(data.enable_healthcheck),
|
enable_healthcheck: Set(data.enable_healthcheck),
|
||||||
favicon: Set(data.favicon),
|
|
||||||
};
|
};
|
||||||
let model = ret.update(&state.db).await?;
|
let model = ret.update(&state.db).await?;
|
||||||
Ok(HttpResponse::Ok().json(model))
|
Ok(HttpResponse::Ok().json(model))
|
||||||
|
@ -214,7 +210,6 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
application_category_id: None,
|
application_category_id: None,
|
||||||
enable_healthcheck: false,
|
enable_healthcheck: false,
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = setup_state().await?;
|
let state = setup_state().await?;
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub struct UpdatePasswordRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
#[put("password")]
|
#[post("password")]
|
||||||
pub async fn update_password(
|
pub async fn update_password(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
req: web::Json<UpdatePasswordRequest>,
|
req: web::Json<UpdatePasswordRequest>,
|
||||||
|
|
|
@ -15,7 +15,6 @@ pub struct Model {
|
||||||
pub glyph: Option<String>,
|
pub glyph: Option<String>,
|
||||||
pub application_category_id: Option<i32>,
|
pub application_category_id: Option<i32>,
|
||||||
pub enable_healthcheck: bool,
|
pub enable_healthcheck: bool,
|
||||||
pub favicon: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
@ -53,7 +52,6 @@ impl Default for Model {
|
||||||
glyph: Default::default(),
|
glyph: Default::default(),
|
||||||
application_category_id: Default::default(),
|
application_category_id: Default::default(),
|
||||||
enable_healthcheck: false,
|
enable_healthcheck: false,
|
||||||
favicon: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ impl Handler<Event> for EventBroker {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: Event, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: Event, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
tracing::info!("Event received");
|
||||||
for (_, ses) in self.sessions.iter() {
|
for (_, ses) in self.sessions.iter() {
|
||||||
let _ = ses.addr.do_send(msg.clone());
|
let _ = ses.addr.do_send(msg.clone());
|
||||||
}
|
}
|
||||||
|
|
47
src/main.rs
47
src/main.rs
|
@ -6,6 +6,7 @@ use actix_web::{
|
||||||
web::{self, Data},
|
web::{self, Data},
|
||||||
App, HttpResponse, HttpServer,
|
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;
|
||||||
|
@ -99,21 +100,13 @@ async fn main() {
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(res) if res.status() == 200 => {
|
Ok(res) if res.status() == 200 => {
|
||||||
if !st
|
st.healthcheck_status.lock().await.insert(app.id, true);
|
||||||
.healthcheck_status
|
let _ = events::EventBroker::from_registry()
|
||||||
.lock()
|
.send(events::Event::HealthcheckChange {
|
||||||
.await
|
app_id: app.id,
|
||||||
.get(&app.id)
|
alive: true,
|
||||||
.unwrap_or(&false)
|
})
|
||||||
{
|
.await;
|
||||||
st.healthcheck_status.lock().await.insert(app.id, true);
|
|
||||||
let _ = events::EventBroker::from_registry()
|
|
||||||
.send(events::Event::HealthcheckChange {
|
|
||||||
app_id: app.id,
|
|
||||||
alive: true,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("Error performing healthcheck: {}", e);
|
tracing::warn!("Error performing healthcheck: {}", e);
|
||||||
|
@ -126,22 +119,14 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
if *st
|
tracing::warn!("Non 200 status code: {}", res.status());
|
||||||
.healthcheck_status
|
st.healthcheck_status.lock().await.insert(app.id, false);
|
||||||
.lock()
|
let _ = events::EventBroker::from_registry()
|
||||||
.await
|
.send(events::Event::HealthcheckChange {
|
||||||
.get(&app.id)
|
app_id: app.id,
|
||||||
.unwrap_or(&true)
|
alive: false,
|
||||||
{
|
})
|
||||||
tracing::warn!("Non 200 status code: {}", res.status());
|
.await;
|
||||||
st.healthcheck_status.lock().await.insert(app.id, false);
|
|
||||||
let _ = events::EventBroker::from_registry()
|
|
||||||
.send(events::Event::HealthcheckChange {
|
|
||||||
app_id: app.id,
|
|
||||||
alive: false,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
<text-field v-model="app.appName" label="Name" required />
|
<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" required />
|
<text-field v-model="app.url" label="URL" required />
|
||||||
<switch-field v-model="app.favicon" label="Use favicon" />
|
<icon-picker v-model="app.glyph" />
|
||||||
<icon-picker v-model="app.glyph" v-if="!app.favicon" />
|
|
||||||
<switch-field v-model="app.enableHealthcheck" label="Health check" />
|
<switch-field v-model="app.enableHealthcheck" label="Health check" />
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -85,7 +84,6 @@ export default {
|
||||||
let resp = await axios.post("/api/applications", {
|
let resp = await axios.post("/api/applications", {
|
||||||
active: true,
|
active: true,
|
||||||
enableHealthcheck: !!this.app.enableHealthcheck,
|
enableHealthcheck: !!this.app.enableHealthcheck,
|
||||||
favicon: !!this.app.favicon,
|
|
||||||
...this.app,
|
...this.app,
|
||||||
});
|
});
|
||||||
if (resp.status == 200) {
|
if (resp.status == 200) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="{tile: true, alive: appData.healthcheckStatus === true, dead: appData.healthcheckStatus === false}" @click="click">
|
<div :class="{tile: true, alive: appData.healthcheckStatus === true, dead: appData.healthcheckStatus === false}" @click="click">
|
||||||
<font-awesome-icon v-if="appData.glyph && !appData.favicon" :icon="appData.glyph" size="2x" />
|
<font-awesome-icon v-if="appData.glyph" :icon="appData.glyph" size="2x" />
|
||||||
<img :src="favicon" v-if="appData.favicon" width="30" height="30"/>
|
<font-awesome-icon v-if="!appData.glyph && appData.healthcheckStatus === false" icon="ban" size="2x" />
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<div class="title">{{appData.appName}}</div>
|
<div class="title">{{appData.appName}}</div>
|
||||||
<div class="description">{{appData.description}}</div>
|
<div class="description">{{appData.description}}</div>
|
||||||
|
@ -12,16 +12,6 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: ["appData"],
|
props: ["appData"],
|
||||||
computed: {
|
|
||||||
favicon() {
|
|
||||||
try {
|
|
||||||
const url = new URL(this.appData.url);
|
|
||||||
return `${url.protocol}${url.hostname}/favicon.ico`;
|
|
||||||
} catch {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
click(e) {
|
click(e) {
|
||||||
this.$emit("clicked", e, this.appData);
|
this.$emit("clicked", e, this.appData);
|
||||||
|
@ -29,7 +19,7 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style>
|
||||||
.tile {
|
.tile {
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
padding: 16px 25px;
|
padding: 16px 25px;
|
||||||
|
@ -50,7 +40,7 @@ export default {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
svg, img {
|
svg {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +52,7 @@ svg, img {
|
||||||
.tile.alive svg {
|
.tile.alive svg {
|
||||||
color: #009900;
|
color: #009900;
|
||||||
}
|
}
|
||||||
.tile.dead svg {
|
.tile.dead svg {
|
||||||
color: #900;
|
color: #900;
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
|
@ -75,7 +65,6 @@ svg, img {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-top: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,7 +78,4 @@ svg, img {
|
||||||
.lightMode .tile:hover {
|
.lightMode .tile:hover {
|
||||||
background-color: rgba(200,200,200, 0.5);
|
background-color: rgba(200,200,200, 0.5);
|
||||||
}
|
}
|
||||||
.tile.dead {
|
|
||||||
background: rgba(50,0,0,0.4);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="bookmark-tile" @click="click">
|
<div class="bookmark-tile" @click="click">
|
||||||
<font-awesome-icon v-if="!showFavIcon" icon="external-link-alt" size="2x"/>
|
<font-awesome-icon icon="external-link-alt"/>
|
||||||
<img v-if="showFavIcon" :src="favicon" @error="faviconError"/>
|
|
||||||
{{bookmark.bookmarkName}}
|
{{bookmark.bookmarkName}}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,45 +8,19 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: ["bookmark"],
|
props: ["bookmark"],
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showFavIcon: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
favicon() {
|
|
||||||
try {
|
|
||||||
const url = new URL(this.bookmark.url);
|
|
||||||
return `${url.protocol}${url.hostname}/favicon.ico`;
|
|
||||||
} catch {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
click() {
|
click() {
|
||||||
this.$emit("clicked", this.bookmark);
|
this.$emit("clicked", this.bookmark);
|
||||||
},
|
},
|
||||||
faviconError() {
|
|
||||||
this.showFavIcon = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
.bookmark-tile {
|
.bookmark-tile {
|
||||||
padding: 5px 25px;
|
padding: 5px 25px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
line-height: 20px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bookmark-tile img, .bookmark-tile svg {
|
|
||||||
margin-right: 20px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-tile, .bookmark-tile svg {
|
.bookmark-tile, .bookmark-tile svg {
|
||||||
|
@ -69,4 +42,6 @@ export default {
|
||||||
background: rgba(255,255,255,0.25);
|
background: rgba(255,255,255,0.25);
|
||||||
color: #222;
|
color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -14,34 +14,9 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
svg {
|
|
||||||
color: #fff;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.new {
|
.tile.new {
|
||||||
transition: all 0.5s;
|
|
||||||
padding: 16px 25px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
background: rgba(0,0,0,0.8);
|
background: rgba(0,0,0,0.8);
|
||||||
}
|
}
|
||||||
.label {
|
|
||||||
font-weight: 900;
|
|
||||||
flex: 1;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.tile:hover {
|
|
||||||
transition: all 0.1s;
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.5) 0px 20px 30px -10px;
|
|
||||||
}
|
|
||||||
.lightMode .tile.new {
|
.lightMode .tile.new {
|
||||||
background: rgba(255,255,255,0.8);
|
background: rgba(255,255,255,0.8);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<form-field>
|
<form-field>
|
||||||
<label v-if="label" :class="{error: isError}">{{label}}</label>
|
<label v-if="label" :class="{error: isError}">{{label}}</label>
|
||||||
<input :placeholder="placeholder" :value="modelValue" ref="field" @input="handleInput" :type="inputType" :class="{error: isError}" />
|
<input :value="modelValue" ref="field" @input="handleInput" :type="inputType" :class="{error: isError}" />
|
||||||
</form-field>
|
</form-field>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@
|
||||||
import FormField from "./FormField.vue";
|
import FormField from "./FormField.vue";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
placeholder: String,
|
|
||||||
required: Boolean,
|
required: Boolean,
|
||||||
label: String,
|
label: String,
|
||||||
modelValue: String,
|
modelValue: String,
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
<div class="login-wrapper">
|
<div class="login-wrapper">
|
||||||
<form @submit.prevent="goLogin">
|
<form @submit.prevent="goLogin">
|
||||||
<panel>
|
<panel>
|
||||||
<div class="error" v-if="error">{{error}}</div>
|
<input v-model="password" type="password" autofocus />
|
||||||
<input v-model="password" type="password" autofocus @input="resetError"/>
|
|
||||||
<button @clicked="goLogin" type="submit">Authenticate</button>
|
<button @clicked="goLogin" type="submit">Authenticate</button>
|
||||||
</panel>
|
</panel>
|
||||||
</form>
|
</form>
|
||||||
|
@ -22,23 +21,15 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
password: "",
|
password: "",
|
||||||
error: ""
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resetError() {
|
|
||||||
this.error = "";
|
|
||||||
},
|
|
||||||
async goLogin() {
|
async goLogin() {
|
||||||
const response = await axios.post("/api/authorize", {
|
const response = await axios.post("/api/authorize", {
|
||||||
password: this.password,
|
password: this.password,
|
||||||
});
|
});
|
||||||
if (response.status == 200) {
|
localStorage.setItem("token", response.data.token);
|
||||||
localStorage.setItem("token", response.data.token);
|
this.$router.push("/");
|
||||||
this.$router.push("/");
|
|
||||||
} else {
|
|
||||||
this.error = "Could not authenticate";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -80,13 +71,4 @@ button:hover {
|
||||||
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px,
|
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px,
|
||||||
rgba(0, 0, 0, 0.22) 0px 10px 10px;
|
rgba(0, 0, 0, 0.22) 0px 10px 10px;
|
||||||
}
|
}
|
||||||
.error {
|
|
||||||
color: #f33;
|
|
||||||
background: #311;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 8px 12px;
|
|
||||||
}
|
|
||||||
</style>>
|
</style>>
|
||||||
|
|
|
@ -16,17 +16,7 @@
|
||||||
<btn label="Update" @click="updateMode" />
|
<btn label="Update" @click="updateMode" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
|
||||||
<h1>Password</h1>
|
|
||||||
<h2>Change password</h2>
|
|
||||||
<text-field v-model="password" password placeholder="password" />
|
|
||||||
<text-field v-model="confirmPassword" password placeholder="confirm password" />
|
|
||||||
<div class="actions">
|
|
||||||
<btn :disabled="password !== confirmPassword || !password.length" label="Change Password" @click="updatePassword" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-buttons">
|
<div class="control-buttons">
|
||||||
<btn danger label="Logout" @click="logout" />
|
|
||||||
<btn primary label="Back to Dashboard" @click="backClicked" />
|
<btn primary label="Back to Dashboard" @click="backClicked" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,16 +26,13 @@
|
||||||
import Btn from "../components/Button.vue";
|
import Btn from "../components/Button.vue";
|
||||||
import SwitchField from "../components/Switch.vue";
|
import SwitchField from "../components/Switch.vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import TextField from '../components/TextField.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Btn, SwitchField, TextField },
|
components: { Btn, SwitchField },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
file: "",
|
file: "",
|
||||||
darkMode: true,
|
darkMode: true,
|
||||||
password: "",
|
|
||||||
confirmPassword: "",
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
@ -64,20 +51,8 @@ export default {
|
||||||
mode: this.darkMode ? "dark" : "light"
|
mode: this.darkMode ? "dark" : "light"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async updatePassword() {
|
|
||||||
const res = await axios.put("/api/password", {
|
|
||||||
password: this.password,
|
|
||||||
});
|
|
||||||
if (res.status === 200) {
|
|
||||||
this.$router.push("/login");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backClicked() {
|
backClicked() {
|
||||||
this.$router.push("/");
|
this.$router.push("/");
|
||||||
},
|
|
||||||
logout() {
|
|
||||||
localStorage.removeItem("token");
|
|
||||||
this.$router.push("/login");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +96,4 @@ export default {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.control-buttons button + button {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue