10 Commits

Author SHA1 Message Date
Johanna Kuehner
6d6b36bdd9 implemented authentication with sidebase/nuxt-auth 2024-03-11 18:19:11 +01:00
Johanna Kuehner
7c19ee9215 implemented authentication 2024-02-29 21:05:08 +01:00
Johanna Kuehner
601449d7e0 added page titles and icon 2024-02-27 16:53:28 +01:00
3a79ed12fa fixed padding 2024-02-26 12:57:56 +01:00
84c17ad855 Merge branch 'addConfigitem' 2024-02-26 12:36:30 +01:00
49fb1ef397 add config item 2024-02-26 12:31:58 +01:00
Johanna Kuehner
e4aa11b902 smoothed out navbar slide (unfinished) 2024-02-23 21:58:32 +01:00
Johanna Kuehner
8a77f18237 Merge branch 'main' of https://git.tueit.de/jo.kuehner/TueIT_App into main 2024-02-23 17:54:09 +01:00
Johanna Kuehner
e5a67b7e14 added client employee and department pages 2024-02-23 17:17:58 +01:00
13096ce679 Merge pull request 'communication from client to nuxt server' (#16) from loginFunctionality into main
Reviewed-on: jo.kuehner/TueIT_App#16

es funktioniert jetzt wie folgt:
- vom entsprechenden component wird eine axios Anfrage an den api Server Endpunkt geschickt
- parallel greift die passende server middleware ein und diese macht den call ins backend
- daraufhin übermittelt die server middleware die Antwort an den api Endpunkt. Dieser schickt dann eine Antwort zurück an den Component, wo dann die Antwort dem client übermittelt wird

was ich schon alles gemacht habe:
- cors Fehler behoben mit entsprechender Konfig und headern
- login (noch nicht initial dorthin geroutet)
- signup aber nur hardgecoded mit einem Beispieluser, da ich einen brauchte fürs Login
- get all Funktionen für die Tabellen aller Seiten (customer existiert schon aber ist noch nicht in die customer Seite eingebunden)
- bin gerade bei CI. hier funktioniert schon ein einziges Item getten, updaten und löschen. Als nächstes mache ich noch die Erstellung eines neuen CI. Du kannst dich daran orientieren.
2024-02-22 16:03:15 +00:00
62 changed files with 14987 additions and 2466 deletions

37
app.vue
View File

@@ -7,24 +7,41 @@
<script setup>
//import { createPinia } from "pinia";
//import piniaPluginPersistedState from "pinia-plugin-persistedstate"
//const layout = "empty";
//const layout = "empty";
//const route = useRoute();
//const pinia = createPinia();
//pinia.use(piniaPluginPersistedState);
useHead({
//title: `Tüit ERP - ${route.meta.title}`,
title: `Tüit ERP`,
link: [{ rel: "icon", type: "image/png", href: "/favicon-gelb-rot-32x32.png" }]
})
</script>
<style>
html, template, body, #__nuxt, #__layout {
height: 100vh;
width: 100vw;
height: 100%;
width: 100%;
margin: 0;
background-color: #212121;
font-size: 1rem;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, template, body, #__nuxt, #__layout {
min-height: 100vh;
width: 100vw;
min-height: 100%;
width: 100%;
background-color: #212121;
font-size: 1rem;
}
</style>

View File

@@ -1,14 +1,30 @@
import axios from 'axios';
import axios, {AxiosError} from 'axios';
import clientsideConfig from './clientsideConfig'
//create axios instance
const Axios = axios.create({
// baseURL: `https://${serversideConfig.url}:${serversideConfig.port}`,
//baseURL: `https://${serversideConfig.url}:${serversideConfig.port}/`,
baseURL: `https://${clientsideConfig.url}:${clientsideConfig.port}/`,
headers: {
// 'Accept': 'application/json',
'Content-Type': 'application/json',
//Authorization: `Bearer`,
Accept: "*",
},
withCredentials: true,
credentials: true,
})
Axios.interceptors.response.use((response) => response, (error) => {
if (error instanceof AxiosError) {
console.error('Status: ', error.response?.status, '\nHeaders: '. error.response?.headers, '\nMessage: '. error.response?.data.message)
} else { console.error('Error: ', error); };
if (error.response?.status === 403) { window.location.href = '/login'; };
return Promise.reject(error);
});
export default Axios;

View File

@@ -93,7 +93,15 @@ export const login = async (req, res, next) => {
}
if (bResult) {
// password match
const token = jwt.sign(
const authtoken = jwt.sign(
{
username: result[0].username,
userId: result[0].id,
},
'SECRETTUEITKEY',
{ expiresIn: '300s' } // 5min
);
const refreshtoken = jwt.sign(
{
username: result[0].username,
userId: result[0].id,
@@ -115,7 +123,7 @@ export const login = async (req, res, next) => {
const results = await ownConn.query(sql1, [dateTimeString, result[0].id]);
return res.status(200).send({
message: 'Logged in!',
token,
token: { authToken: authtoken, refreshToken: refreshtoken },
user: result[0],
});
}

View File

@@ -76,6 +76,7 @@ export const updateConfigItemById = async (data, result) => {
export const insertConfigItem = async (data, result) => {
try {
const results = await ownConn.query(`INSERT INTO changedb(assetName, customerID, customer, location, remoteLocation, type, description, notes, state, lastView, user, hardwareBool, model, serialnumber, CPU, RAM, storageConfiguration, miscellaneous, softwareBool, software, version, license, networkBool, IPv4, IPv6, MAC, subnetmask) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [data.assetName, data.customerID, data.customer, data.location, data.remoteLocation, data.type, data.description, data.notes, data.state, data.lastView, data.user, data.hardwareBool, data.model, data.serialnumber, data.CPU, data.RAM, data.storageConfiguration, data.miscellaneous, data.softwareBool, data.software, data.version, data.license, data.networkBool, data.IPv4, data.IPv6, data.MAC, data.subnetmask])
results.insertId = results.insertId.toString();
result(null, results);
}
catch (err) {

View File

@@ -1,14 +1,35 @@
<template>
<section :class="['client-search', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Client</div>
<pre :class="['data', darkMode ? 'pre-darkmode' : 'pre-lightmode']">...</pre>
<pre v-if="!filtered" :class="['data', darkMode ? 'pre-darkmode' : 'pre-lightmode']">{{ clientFilter }}</pre>
<input v-if="filtered" v-model="clientFilter" @change="filterConfigItemList()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</section>
</template>
<script setup>
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { useStore } from 'vuex';
import { computed } from 'vue';
const store = useStore();
const filtered = computed(() => store.state.assetFiltered);
const filteredTerm = computed(() => store.state.filteredAssetbyClient);
const clientFilter = ref(store.state.filteredAssetbyClient);
const darkMode = ref(true)
// update filtered term
const updateFilterTerm = () => {
clientFilter.value = filteredTerm.value
}
// update the filtered term in the store
const filterConfigItemList = () => {
store.commit('updateAssetFilterbyClient', clientFilter.value);
}
watch(filteredTerm, updateFilterTerm)
</script>
<script>
@@ -76,4 +97,18 @@ export default {
background-color: #EBEBEB;
color: #000;
}
.input {
border: none;
}
.data-darkmode {
background-color: #212121;
color: #fff;
}
.data-lightmode {
background-color: #EBEBEB;
color: #000;
}
</style>

View File

@@ -4,13 +4,13 @@
<span class="title-icon" id="logo-icon">
<img loading="lazy" srcSet="../favicon-gelb-rot-32x32.png" />
</span>
<pre :class="['title', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Login</pre>
<pre :class="['title', darkMode ? 'title-darkmode' : 'title-lightmode']">Login</pre>
</div>
<div class="login-field">
<div class="form-field" id="username-field">
<label for="username-input" id="username-label">
<span :class="['icon', darkMode ? 'icon-darkmode' : 'icon-lightmode']" id="username-icon">
<img loading="lazy" src="../icons/Mail-Icon.svg" />
<img loading="lazy" src="/icons/Mail-Icon.svg" />
</span>
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Username:</div>
</label>
@@ -21,14 +21,13 @@
<div class="form-field" id="password-field">
<label for="password-input" id="password-label">
<span :class="['icon', darkMode ? 'icon-darkmode' : 'icon-lightmode']" id="password-icon">
<img loading="lazy" src="../icons/Lock-Icon.svg" />
<img loading="lazy" src="/icons/Lock-Icon.svg" />
</span>
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Password:</div>
</label>
<div :class="['input-field', darkMode ? 'input-darkmode' : 'input-lightmode']">
<input type="text" id="password-input" placeholder="*******">
<input type="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="show-password-toggle"
value="Show">
<input type="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="show-password-toggle" value="Show">
</div>
</div>
</div>
@@ -37,10 +36,10 @@
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']"> {{ errorMsg }} </div>
</label>
</div>
<input type="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="login-button" value="Login"
@click="handleLogin">
<input type="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="login-button" value="Signup"
@click="testFunctionSignup">
<div class="buttons">
<input type="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="login-button" value="Login" @click="handleLogin">
<input type="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="signup-button" value="Signup" @click="testFunctionSignup">
</div>
</form>
</template>
@@ -51,6 +50,7 @@ import { ref } from 'vue';
import Axios from '../axios.config.js';
import clientsideConfig from '../clientsideConfig.js';
const { signIn } = useAuth()
const router = useRouter();
const darkMode = ref(true);
const isError = ref(false);
@@ -63,19 +63,29 @@ const handleLogin = async () => {
const username = document.getElementById('username-input').value;
const password = document.getElementById('password-input').value;
const requestBody = {
//const requestBody = {
const credentials = {
username: username,
password: password,
}
try {
let res = await Axios.post(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/login`, requestBody);
/*let res = await Axios.post(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/login`, requestBody);
const sessionToken = useCookie('token', {maxAge: 604800, sameSite: true});
sessionToken.value = res.data.token;
Axios.defaults.headers.common['Authorization'] = `${username}`;
const userToken = useCookie('user', {maxAge: 604800, sameSite: true});
userToken.value = username;
// something to do with the res?
console.log(res.data)
console.log(sessionToken)
console.log(res.data.message)
// sucessfully logged in
router.push('/')
router.push('/home')*/
let res = await signIn( credentials, { callbackUrl: '/home' })
console.log("res", res)
} catch (err) {
// handle the error
console.log(err.response.statusText)
@@ -162,22 +172,22 @@ export default {
align-items: center;
justify-content: center;
width: 31.25rem;
height: 33rem;
min-height: 33rem;
height: fit-content;
border-radius: 0.625rem;
padding: 2.5rem 1.875rem;
gap: 1.875rem;
}
.form-darkmode {
border: 0.0625rem solid #000;
background-color: #2c2c2c;
}
.form-lightmode {
border: 0.0625rem solid #8e8e8e;
background-color: #fff;
}
.title-field {
display: flex;
flex-direction: column;
@@ -192,7 +202,6 @@ export default {
width: 3.125rem;
height: 3.125rem;
}
.title-icon>img {
width: 3.125rem;
height: 3.125rem;
@@ -203,20 +212,14 @@ export default {
.title {
margin: 0;
letter-spacing: 5%;
letter-spacing: 0.05rem;
white-space: nowrap;
font: 400 1.875rem/1.875rem Overpass, sans-serif;
}
.title-darkmode { color: #ffffff; }
.title-lightmode { color: #000000; }
.pre-darkmode,
.title-darkmode {
color: #fff;
}
.pre-lightmode,
.title-lightmode {
color: #000;
}
.login-field {
display: flex;
@@ -227,6 +230,7 @@ export default {
gap: 1.25rem;
}
.form-field {
display: flex;
flex-direction: column;
@@ -238,16 +242,6 @@ export default {
gap: 0.625rem;
}
.form-field-error-msg {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 25rem;
height: 3rem;
gap: 0.4rem;
}
label {
display: flex;
flex-direction: row;
@@ -265,43 +259,28 @@ label {
width: 1.875rem;
height: 1.875rem;
}
.icon>img {
object-fit: contain;
object-position: center;
overflow: hidden;
}
.icon-darkmode>img {
filter: invert(100%);
}
.icon-lightmode>img {
filter: invert(0%);
}
#username-icon>img {
width: auto;
height: 0.9375rem;
}
}
#password-icon>img {
width: 0.9375rem;
height: auto;
}
.icon-darkmode>img { filter: invert(100%); }
.icon-lightmode>img { filter: invert(0%); }
.label {
letter-spacing: 2%;
letter-spacing: 0.02rem;
font: 400 0.9375rem/1.875rem Overpass, sans-serif;
}
.label-darkmode {
color: #fff;
}
.label-lightmode {
color: #000;
}
.label-darkmode { color: #ffffff; }
.label-lightmode { color: #000000; }
.input-field {
display: flex;
@@ -314,15 +293,9 @@ label {
border-radius: 0.3125rem;
padding: 0.1875rem 0.625rem;
box-shadow: 0.0625rem 0.0625rem 0.25rem 0rem rgba(0, 0, 0, 0.25) inset;
}
.input-darkmode {
background-color: #212121;
}
.input-lightmode {
background-color: #EBEBEB;
}
}
.input-darkmode { background-color: #212121; }
.input-lightmode { background-color: #EBEBEB; }
input[type=text] {
width: 100%;
@@ -330,40 +303,63 @@ input[type=text] {
background-color: #00000000;
border: none;
color: #8e8e8e;
letter-spacing: 5%;
letter-spacing: 0.01rem;
white-space: nowrap;
font: 100 0.75rem/1.25rem Overpass, sans-serif;
}
input[type=button] {
#show-password-toggle {
width: fit-content;
height: auto;
align-self: flex-end;
padding: 0;
border-radius: 0;
background-color: #00000000;
background: none;
border: none;
letter-spacing: 5%;
letter-spacing: 0.01rem;
white-space: nowrap;
font: 300 0.75rem/1.25rem Overpass, sans-serif;
}
.button-darkmode {
color: #fff;
.form-field-error-msg {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 25rem;
height: 3rem;
gap: 0.4rem;
}
.button-lightmode {
color: #000;
.buttons {
display: flex;
flex-direction: row;
height: fit-content;
width: 100%;
align-items: center;
justify-content: center;
padding: 0.625rem 0.625rem 0;
gap: 2.5rem;
}
#login-button {
width: 13.75rem;
height: 4.375rem;
input[type=button] {
width: 9.375rem;
height: 3.125rem;
padding: 0.625rem;
border-radius: 0.625rem;
align-self: center;
border: none;
color: #000;
background: linear-gradient(93deg, #ff0f00 3.67%, #ffe608 100%);
letter-spacing: 2%;
letter-spacing: 0.02rem;
white-space: nowrap;
font: 600 1.25rem/1.875rem Overpass, sans-serif;
font: 600 1.125rem/1.875rem Overpass, sans-serif;
}
.button-darkmode { color: #ffffff; }
.button-lightmode { color: #000000; }
</style>

View File

@@ -1,6 +1,7 @@
<template>
<section :class="['information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<h2 v-if="!editable" :class="['asset-name', darkMode ? 'h2-darkmode' : 'h2-lightmode']">{{ item.assetName }}</h2>
<section v-if="!addAssetBool" :class="['information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<h2 v-if="!editable" :class="['asset-name', darkMode ? 'h2-darkmode' : 'h2-lightmode']">{{
item.assetName }}</h2>
<input v-if="editable" v-model="item.assetName" @change="updateConfigItem()"
:class="['data', 'asset-name-input', darkMode ? 'h2-darkmode' : 'h2-lightmode', darkMode ? 'h2-input-darkmode' : 'h2-input-lightmode', 'input']">
<div class="asset-data">
@@ -9,8 +10,6 @@
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Client:</pre>
<pre v-if="!editable"
:class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']"> {{ item.customer }}</pre>
<!-- <input v-if="editable" v-model="item.customer" @change="updateCustomerID()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']"> -->
<select v-if="editable" id="customersDropDownChosenCI" v-model="item.customer"
@change="updateCustomerID()" :class="[darkMode ? 'select-darkmode' : 'select-lightmode']">
<option v-for="cust in customers" :key="cust.primaryID">
@@ -77,7 +76,72 @@
</div>
</div>
</div>
</section>
<section v-if="addAssetBool" :class="['information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<input v-model="newAssetName" @change="updateAsset()"
:class="['data', 'asset-name-input', darkMode ? 'h2-darkmode' : 'h2-lightmode', darkMode ? 'h2-input-darkmode' : 'h2-input-lightmode', 'input']">
<div class="asset-data">
<div class="client-location">
<div class="data-field" id="client">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Client:</pre>
<select id="customersDropDownChosenCI" v-model="newCustomer" @change="updateNewCustomerID()"
:class="[darkMode ? 'select-darkmode' : 'select-lightmode']">
<option v-for="cust in customers" :key="cust.primaryID">
{{ cust.customername }}
</option>
</select>
</div>
<div class="data-field" id="location">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Location:</pre>
<input v-model="newLocation" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
</div>
<div class="info">
<div class="id-type">
<div class="data-field" id="id">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">ID:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']"> ... </pre>
</div>
<div class="data-field" id="type">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Type:</pre>
<input v-model="newType" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
</div>
<div class="remoteLocation-state">
<div class="data-field" id="remote-location">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Remote location:</pre>
<input v-model="newRemoteLocation" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
<div class="data-field" id="state">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">State:</pre>
<input v-model="newState" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
</div>
</div>
<div class="rectangle-container">
<div :class="['rectangle', darkMode ? 'rectangle-darkmode' : 'rectangle-lightmode']"></div>
</div>
</div>
<div class="asset-data">
<div class="additional">
<div class="description">
<h3 :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Description:</h3>
<input v-model="newDescription" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']" id="description">
</div>
</div>
<div class="additional">
<div class="notes">
<h3 :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Notes:</h3>
<input v-model="newNotes" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']" id="notes">
</div>
</div>
</div>
</section>
</template>
@@ -91,26 +155,55 @@ import { computed } from 'vue';
const store = useStore();
const editable = computed(() => store.state.assetEditable);
const chosenAssetId = computed(() => store.state.chosenAssetId);
const deleteAssetBool = computed(() => store.state.deleteAsset)
const deleteAssetBool = computed(() => store.state.deleteAsset);
const addAssetBool = computed(() => store.state.newAsset);
const darkMode = ref(true)
const darkMode = ref(true);
const item = ref({});
const customer = ref({});
const configItems = ref([]);
const customers = ref([]);
const newAssetName = ref('');
const newCustomerID = ref('');
const newCustomer = ref('');
const newLocation = ref('');
const newRemoteLocation = ref('');
const newType = ref('');
const newDescription = ref('');
const newNotes = ref('');
const newState = ref('');
// get config item from id
const getItemById = async () => {
try {
const response = await Axios.get(
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getConfigItem/${chosenAssetId.value}`
);
item.value = response.data;
} catch (err) {
console.log(err.response.statusText);
if (!addAssetBool.value) {
try {
const response = await Axios.get(
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getConfigItem/${chosenAssetId.value}`
);
item.value = response.data;
} catch (err) {
console.log(err.response.statusText);
}
}
}
// update asset fields in the store
const updateAsset = () => {
const asset = {
assetName: newAssetName.value,
customerId: newCustomerID.value,
customer: newCustomer.value,
location: newLocation.value,
remoteLocation: newRemoteLocation.value,
type: newType.value,
description: newDescription.value,
notes: newNotes.value,
state: newState.value
};
store.commit('updateAssetComponent', asset);
}
//update data
const updateConfigItem = async () => {
if (item.value.assetName.trim() === "") {
@@ -124,8 +217,9 @@ const updateConfigItem = async () => {
counter += 1;
}
});
if (counter == 2) {
if (counter == 1) {
alert("This asset name already exists. Please choose an unique asset name or modify respectively delete the old one!");
item.value.assetName = '';
return;
}
try {
@@ -218,12 +312,30 @@ const updateCustomerID = async () => {
await updateConfigItem();
}
// update customerid if customer was changed
const updateNewCustomerID = async () => {
try {
const response = await Axios.get(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getCustomerByName/${newCustomer.value}`);
newCustomerID.value = response.data.customerID;
} catch (err) {
console.log(err.response.statusText);
}
updateAsset();
}
// include delay to avoid 503 error
const triggerBackendCallsWithDelay = async (fetchDataFunc) => {
setTimeout(() => {
fetchDataFunc();
}, 1500);
}
watch(deleteAssetBool, confirmDeleteAsset);
onMounted(() => {
getItemById();
getConfigItems();
getCustomers();
triggerBackendCallsWithDelay(getCustomers);
});
</script>
@@ -241,7 +353,6 @@ export default {
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
@@ -289,7 +400,7 @@ export default {
align-items: center;
justify-content: flex-start;
width: 100%;
padding: 0 1.875rem;
padding: 0.8rem 1.875rem;
gap: 1.25rem;
border-radius: 0.625rem;
}

View File

@@ -1,4 +1,9 @@
<template>
<section v-if="assetSearchable" :class="['asset-search', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['clientLabel', darkMode ? 'label-darkmode' : 'label-lightmode']">Config item</div>
<input v-model="assetSearchFilter" @change="searchConfigItem()"
:class="['dataInput', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</section>
<div :class="['data', darkMode ? 'div-darkmode' : 'div-lightmode']">
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Last viewed:</div>
<table class="data-table" id="asset-table">
@@ -49,10 +54,11 @@
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref, onMounted, watch } from 'vue';
import Axios from '../axios.config.js';
import clientsideConfig from '../../clientsideConfig.js';
import { useStore } from 'vuex';
import { computed } from 'vue';
// get accesss to the store
const store = useStore()
@@ -60,20 +66,60 @@ const goToChosenAsset = (id) => {
store.commit('setChosenAsset', id);
store.commit('changeToAsset');
};
const clientFilter = computed(() => store.state.filteredAssetbyClient);
const assetSearchable = computed(() => store.state.assetSearchable);
const assetSearchFilter = ref('');
const darkMode = ref(true)
const configItemList = ref([]);
// update search term
const updateSearchTerm = async () => {
assetSearchFilter.value = '';
await getConfigItems();
}
//get all config items
const getConfigItems = async () => {
if (!(clientFilter.value === '')) {
await getFilteredConfigItemsByClient();
} else {
try {
const response = await Axios.get(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getAllConfigItems`);
configItemList.value = response.data;
} catch (err) {
console.log(err.response.statusText);
}
}
}
//get all config items based on the searched client
const getFilteredConfigItemsByClient = async () => {
try {
const response = await Axios.get(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getAllConfigItems`);
const response = await Axios.get(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getSelectedConfigItemsByClient/${clientFilter.value}`);
configItemList.value = response.data;
} catch (err) {
console.log(err.response.statusText);
}
}
//get all config items based on the searched asset name
const searchConfigItem = async () => {
if (assetSearchFilter.value === '') {
await getConfigItems();
} else {
try {
const response = await Axios.get(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getSelectedConfigItemsByAsset/${assetSearchFilter.value}`);
configItemList.value = response.data;
} catch (err) {
console.log(err.response.statusText);
}
}
}
watch(clientFilter, getConfigItems);
watch(assetSearchable, updateSearchTerm);
onMounted(async () => {
await getConfigItems();
});
@@ -93,13 +139,58 @@ export default {
align-items: flex-start;
justify-content: center;
align-self: stretch;
width: 100%;
/* width: 100%; */
padding: 1.25rem 1.875rem;
gap: 1.25rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.asset-search {
display: flex;
align-items: center;
padding: 0.625em 1.875em;
gap: 1.25em;
width: 30.125em;
height: 3.125em;
box-shadow: 0.25em 0.25em 0.25em rgba(0, 0, 0, 0.25);
border-radius: 0.625em;
box-sizing: border-box;
}
.dataInput {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 0 0.625rem;
border-radius: 0.3125rem;
box-shadow: 0.0625rem 0.0625rem 0.25rem 0rem rgba(0, 0, 0, 0.25) inset;
letter-spacing: 5%;
font: 400 0.75rem/250% Overpass, sans-serif;
}
.section-darkmode {
background-color: #2c2c2c;
}
.section-lightmode {
background-color: #fff;
}
.input {
border: none;
}
.data-darkmode {
background-color: #212121;
color: #fff;
}
.data-lightmode {
background-color: #EBEBEB;
color: #000;
}
.div-darkmode {
background-color: #2c2c2c;
}
@@ -228,6 +319,17 @@ th {
letter-spacing: 0.05em;
}
.clientLabel {
width: 5.5625em;
height: 1.875em;
font-family: "Overpass";
font-style: normal;
font-weight: 400;
font-size: 0.875em;
line-height: 1.875;
letter-spacing: 0.05em;
}
.label-darkmode {
color: #FFFFFF;
}

View File

@@ -1,13 +1,13 @@
<template>
<section :class="['information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<h2 :class="['client-name', darkMode ? 'h2-darkmode' : 'h2-lightmode']">Client name</h2>
<section :class="[darkMode ? 'section-darkmode' : 'section-lightmode']">
<pre :class="['title', darkMode ? 'title-darkmode' : 'title-lightmode']">Client name</pre>
<div class="data-field" id="client-id">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">ID:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="client-data">
<div class="contact">
<h3 :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Contact:</h3>
<pre :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Contact:</pre>
<div class="data-field" id="contact-person">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Contact person:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
@@ -22,7 +22,7 @@
</div>
</div>
<div class="address">
<h3 :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Address:</h3>
<pre :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Address:</pre>
<div class="street-address">
<div class="data-field" id="street-name">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Street:</pre>
@@ -44,7 +44,7 @@
</div>
</div>
<div class="notes">
<h3 :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Notes:</h3>
<pre :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Notes:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']" id="notes">...</pre>
</div>
</section>
@@ -65,138 +65,102 @@ export default {
<style scoped>
.information {
section {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.section-darkmode {
background-color: #2c2c2c;
}
.section-lightmode {
background-color: #fff;
}
align-items: stretch;
justify-content: center;
}
.section-darkmode { background-color: #2c2c2c; }
.section-lightmode { background-color: #ffffff; }
.client-name {
align-self: stretch;
.title {
padding: 1.25rem 0;
letter-spacing: 5%;
letter-spacing: 0.05rem;
text-decoration-line: underline;
font: italic 400 1rem/187.5% Overpass, -apple-system, Roboto, Helvetica,
sans-serif;
}
font: italic 400 1rem/1.875rem Overpass, sans-serif;
}
.title-darkmode { color: #ffffff; }
.title-lightmode { color: #000000; }
.h2-darkmode {
color: #fff;
}
.h2-lightmode {
color: #000;
}
.data-field {
display: flex;
flex: auto;
flex-direction: row;
padding: 0 1.875rem;
align-items: center;
justify-content: flex-start;
width: 100%;
padding: 0 1.875rem;
gap: 1.25rem;
border-radius: 0.625rem;
}
.data-field#client-id {
padding: 0.625rem 1.875rem;
}
.data-field#street-name {
width: 70%;
}
.data-field#street-no {
width: 30%;
}
}
.data-field#client-id { padding: 0.625rem 1.875rem; }
.data-field#street-name { flex: 3; }
.label {
letter-spacing: 5%;
letter-spacing: 0.03rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
.pre-darkmode {
color: #fff;
}
.pre-lightmode {
color: #000;
pre {
margin: 0;
}
.pre-darkmode { color: #ffffff; }
.pre-lightmode { color: #000000; }
.data {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 0 0.625rem;
border-radius: 0.3125rem;
box-shadow: 0.0625rem 0.0625rem 0.25rem 0rem rgba(0, 0, 0, 0.25) inset;
background-color: #212121;
align-items: center;
justify-content: flex-start;
letter-spacing: 5%;
font: 400 0.75rem/250% Overpass, sans-serif;
}
}
.data-darkmode {
background-color: #212121;
color: #fff;
}
background-color: #212121;
color: #ffffff;
}
.data-lightmode {
background-color: #EBEBEB;
color: #000;
}
background-color: #ebebeb;
color: #000000;
}
.data#notes {
align-self: stretch;
}
.client-data {
display: flex;
flex: auto;
flex-direction: row;
padding: 1.25rem 1.875rem;
align-items: center;
justify-content: flex-start;
align-self: stretch;
padding: 0 0.625rem;
justify-content: stretch;
gap: 0.625rem
}
.contact,
.address {
.contact, .address {
display: flex;
flex: auto;
flex-direction: column;
align-items: flex-start;
justify-content: center;
line-height: normal;
width: 50%;
padding: 1.25rem 0.625rem;
border-radius: 0.3125rem;
gap: 0.625rem;
}
.area-title {
letter-spacing: 5%;
letter-spacing: 0.03rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
.h3-darkmode {
color: #fff;
}
.h3-lightmode {
color: #000;
}
.h3-darkmode { color: #ffffff; }
.h3-lightmode { color: #000000; }
.street-address {
@@ -205,15 +169,17 @@ export default {
align-items: center;
justify-content: space-between;
align-self: stretch;
padding: 0 1.875rem 0 0;
gap: 0.625rem;
}
.notes {
display: flex;
flex-direction: column;
padding: 0.625rem 1.875rem;
align-items: flex-start;
justify-content: center;
align-self: stretch;
padding: 0.625rem 1.875rem 0.625rem 1.25rem;
}</style>
gap: 0.625rem;
}
#notes { align-self: stretch; }
</style>

View File

@@ -0,0 +1,143 @@
<template>
<section :class="[darkMode ? 'section-darkmode' : 'section-lightmode']">
<pre :class="['title', darkMode ? 'title-darkmode' : 'title-lightmode']">Department name</pre>
<div class="data-group">
<div class="data-field" id="id">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">ID:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="client-id">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Client ID:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="head">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Head:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
</div>
<div class="notes">
<pre :class="['area-title', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Notes:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']" id="notes">...</pre>
</div>
</section>
</template>
<script setup>
import { ref } from 'vue';
const darkMode = ref(true)
</script>
<script>
export default {
name: "ClientDepartment",
};
</script>
<style scoped>
* {
box-sizing: border-box;
margin: 0;
}
section {
display: flex;
flex-direction: column;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
align-items: stretch;
justify-content: center;
}
.section-darkmode { background-color: #2c2c2c; }
.section-lightmode { background-color: #ffffff; }
.title {
padding: 1.25rem 0;
letter-spacing: 0.04rem;
text-decoration-line: underline;
font: italic 400 1rem/1.875rem Overpass, sans-serif;
}
.title-darkmode { color: #ffffff; }
.title-lightmode { color: #000000; }
.data-group {
display: flex;
flex: auto;
width: 100%;
flex-direction: row;
padding: 0.625rem 0;
align-items: center;
justify-content: stretch;
}
.data-field {
display: flex;
flex: auto;
width: 30%;
flex-direction: row;
padding: 0.625rem 1.875rem;
align-items: center;
justify-content: flex-start;
gap: 1.25rem;
}
#head { width: 40%; }
.label {
letter-spacing: 0.02rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
.pre-darkmode { color: #ffffff; }
.pre-lightmode { color: #000000; }
.data {
display: flex;
flex-direction: row;
padding: 0 0.625rem;
border-radius: 0.3125rem;
box-shadow: 0.0625rem 0.0625rem 0.25rem 0rem rgba(0, 0, 0, 0.25) inset;
background-color: #212121;
align-items: center;
justify-content: flex-start;
letter-spacing: 0.02rem;
font: 400 0.75rem/1.875rem Overpass, sans-serif;
}
.data-darkmode {
background-color: #212121;
color: #ffffff;
}
.data-lightmode {
background-color: #ebebeb;
color: #000000;
}
.notes {
display: flex;
flex: auto;
flex-direction: column;
padding: 1.25rem 1.875rem 0.625rem;
align-items: flex-start;
justify-content: center;
gap: 0.625rem;
}
.area-title {
letter-spacing: 0.03rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
#notes { align-self: stretch; }
</style>

View File

@@ -0,0 +1,117 @@
<template>
<section :class="['data', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Employees:</div>
<table class="data-table" id="client-employee-table">
<tr :class="['table-row', darkMode ? 'tr-head-darkmode' : 'tr-head-lightmode']" id="table-head">
<th :class="['ID', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'ID-darkmode' : 'ID-lightmode']">ID</th>
<th :class="['Name', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Name-darkmode' : 'Name-lightmode']">Name</th>
<th :class="['JobTitle', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'JobTitle-darkmode' : 'JobTitle-lightmode']">Job Title</th>
<th :class="['Pronouns', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Pronouns-darkmode' : 'Pronouns-lightmode']">Pronouns</th>
</tr>
<tr :class="['table-row', darkMode ? 'tr-darkmode' : 'tr-lightmode']" id="row-1">
<td :class="['ID', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'ID-darkmode' : 'ID-lightmode']">...</td>
<td :class="['Name', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Name-darkmode' : 'Name-lightmode']">...</td>
<td :class="['JobTitle', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'JobTitle-darkmode' : 'JobTitle-lightmode']">...</td>
<td :class="['Pronouns', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Pronouns-darkmode' : 'Pronouns-lightmode']">...</td>
</tr>
</table>
</section>
</template>
<script setup>
import { ref } from 'vue';
const darkMode = ref(true)
</script>
<script>
export default {
name: "ClientDepartmentEmployeeList",
};
</script>
<style scoped>
.data {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
align-self: stretch;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.section-darkmode { background-color: #2c2c2c; }
.section-lightmode { background-color: #ffffff; }
.label {
padding: 1.25rem 0;
letter-spacing: 0.04rem;
text-decoration-line: underline;
font: 400 1rem/1.875rem Overpass, sans-serif;
}
.label-darkmode { color: #ffffff; }
.label-lightmode { color: #000000; }
.data-table {
width: 100%;
padding: 0 0.625rem;
table-layout: fixed;
border-collapse: collapse;
}
.table-row {
display: flex;
flex-direction: row;
align-items: center;
height: 3.125rem;
padding: 0.625rem;
gap: 0.625rem;
}
.tr-head-darkmode {
border-top: none;
border-bottom: 0.0625rem solid #000000;
}
.tr-head-lightmode {
border-top: none;
border-bottom: 0.0625rem solid #8e8e8e;
}
.tr-darkmode { border-top: 0.0625rem solid #000000; }
.tr-lightmode { border-top: 0.0625rem solid #8e8e8e; }
th, td {
height: 1.875rem;
width: 35%;
padding: 0;
text-align: left;
border-left: none;
letter-spacing: 0.02rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
th { font: 700 0.875rem/1.875rem Overpass, sans-serif; }
.th-darkmode, .td-darkmode {
color: #ffffff;
border-right: 0.0625rem solid #000000;
}
.th-lightmode, .td-lightmode {
color: #000000;
border-right: 0.0625rem solid #8e8e8e;
}
.ID { width: 20%; }
.Pronouns { width: 10%; }
.Pronouns-darkmode { border-right: none; }
.Pronouns-lightmode { border-right: none; }
</style>

View File

@@ -0,0 +1,112 @@
<template>
<section :class="['data', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Departments:</div>
<table class="data-table" id="client-employee-table">
<tr :class="['table-row', darkMode ? 'tr-head-darkmode' : 'tr-head-lightmode']" id="table-head">
<th :class="['ID', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'ID-darkmode' : 'ID-lightmode']">ID</th>
<th :class="['Name', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Name-darkmode' : 'Name-lightmode']">Name</th>
<th :class="['DHead', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'DHead-darkmode' : 'DHead-lightmode']">Head</th>
</tr>
<tr :class="['table-row', darkMode ? 'tr-darkmode' : 'tr-lightmode']" id="row-1">
<td :class="['ID', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'ID-darkmode' : 'ID-lightmode']">...</td>
<td :class="['Name', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Name-darkmode' : 'Name-lightmode']">...</td>
<td :class="['DHead', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'DHead-darkmode' : 'DHead-lightmode']">...</td>
</tr>
</table>
</section>
</template>
<script setup>
import { ref } from 'vue';
const darkMode = ref(true)
</script>
<script>
export default {
name: "ClientDepartmentList",
};
</script>
<style scoped>
.data {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
align-self: stretch;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.section-darkmode { background-color: #2c2c2c; }
.section-lightmode { background-color: #ffffff; }
.label {
padding: 1.25rem 0;
letter-spacing: 0.04rem;
text-decoration-line: underline;
font: 400 1rem/1.875rem Overpass, sans-serif;
}
.label-darkmode { color: #ffffff; }
.label-lightmode { color: #000000; }
.data-table {
width: 100%;
padding: 0 0.625rem;
table-layout: fixed;
border-collapse: collapse;
}
.table-row {
display: flex;
flex-direction: row;
align-items: center;
height: 3.125rem;
padding: 0.625rem;
gap: 0.625rem;
}
.tr-head-darkmode {
border-top: none;
border-bottom: 0.0625rem solid #000000;
}
.tr-head-lightmode {
border-top: none;
border-bottom: 0.0625rem solid #8e8e8e;
}
.tr-darkmode { border-top: 0.0625rem solid #000000; }
.tr-lightmode { border-top: 0.0625rem solid #8e8e8e; }
th, td {
height: 1.875rem;
width: 40%;
padding: 0;
text-align: left;
border-left: none;
letter-spacing: 0.02rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
th { font: 700 0.875rem/1.875rem Overpass, sans-serif; }
.th-darkmode, .td-darkmode {
color: #ffffff;
border-right: 0.0625rem solid #000000;
}
.th-lightmode, .td-lightmode {
color: #000000;
border-right: 0.0625rem solid #8e8e8e;
}
.ID { width: 20%; }
.DHead { border-right: none; }
</style>

View File

@@ -0,0 +1,191 @@
<template>
<section :class="[darkMode ? 'section-darkmode' : 'section-lightmode']">
<pre :class="['title', darkMode ? 'title-darkmode' : 'title-lightmode']">Employee name</pre>
<div class="data-group" id="personal-data">
<div class="data-group" id="full-name">
<div class="data-field" id="ntitle">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Title/-s:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="first-name">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">First Name/-s:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="last-name">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Last Name/-s:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
</div>
<div class="data-group" id="other">
<div class="data-field" id="id">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">ID:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="pronouns">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Pronouns:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="preferred-name">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Preferred Name:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
</div>
</div>
<div class="data-group" id="employment-data">
<div class="data-field" id="client-id">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Client ID:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="department">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Department:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="job-title">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Job Title:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
</div>
<div class="data-group" id="contact-data">
<div class="data-field" id="phone">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Phone No.:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
<div class="data-field" id="mail">
<pre :class="['label', darkMode ? 'pre-darkmode' : 'pre-lightmode']">E-Mail:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']">...</pre>
</div>
</div>
<div class="notes">
<pre :class="['area-title', darkMode ? 'pre-darkmode' : 'pre-lightmode']">Notes:</pre>
<pre :class="['data', darkMode ? 'data-darkmode' : 'data-lightmode']" id="notes">...</pre>
</div>
</section>
</template>
<script setup>
import { ref } from 'vue';
const darkMode = ref(true)
</script>
<script>
export default {
name: "ClientEmployee",
};
</script>
<style scoped>
* {
box-sizing: border-box;
margin: 0;
}
section {
display: flex;
flex-direction: column;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
align-items: stretch;
justify-content: center;
}
.section-darkmode { background-color: #2c2c2c; }
.section-lightmode { background-color: #ffffff; }
.title {
padding: 1.25rem 0;
letter-spacing: 0.04rem;
text-decoration-line: underline;
font: italic 400 1rem/1.875rem Overpass, sans-serif;
}
.title-darkmode { color: #ffffff; }
.title-lightmode { color: #000000; }
.data-group {
display: flex;
flex: auto;
width: 100%;
flex-direction: row;
padding: 1.25rem 0;
align-items: center;
justify-content: stretch;
}
#personal-data {
flex-direction: column;
align-items: stretch;
justify-content: center;
gap: 0.625rem;
}
#full-name, #other { padding: 0; }
.data-field {
display: flex;
flex: auto;
width: 20%;
flex-direction: row;
padding: 0 1.875rem;
align-items: center;
justify-content: flex-start;
gap: 1.25rem;
}
#first-name, #last-name, #department, #job-title, #phone { width: 40%; }
#preferred-name, #mail { width: 60%; }
.label {
letter-spacing: 0.02rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
.pre-darkmode { color: #ffffff; }
.pre-lightmode { color: #000000; }
.data {
display: flex;
flex-direction: row;
padding: 0 0.625rem;
border-radius: 0.3125rem;
box-shadow: 0.0625rem 0.0625rem 0.25rem 0rem rgba(0, 0, 0, 0.25) inset;
background-color: #212121;
align-items: center;
justify-content: flex-start;
letter-spacing: 0.02rem;
font: 400 0.75rem/1.875rem Overpass, sans-serif;
}
.data-darkmode {
background-color: #212121;
color: #ffffff;
}
.data-lightmode {
background-color: #ebebeb;
color: #000000;
}
.notes {
display: flex;
flex: auto;
flex-direction: column;
padding: 1.25rem 1.875rem 0.625rem;
align-items: flex-start;
justify-content: center;
gap: 0.625rem;
}
.area-title {
letter-spacing: 0.03rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
#notes { align-self: stretch; }
</style>

View File

@@ -0,0 +1,116 @@
<template>
<section :class="['data', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Employees:</div>
<table class="data-table" id="client-employee-table">
<tr :class="['table-row', darkMode ? 'tr-head-darkmode' : 'tr-head-lightmode']" id="table-head">
<th :class="['ID', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'ID-darkmode' : 'ID-lightmode']">ID</th>
<th :class="['Name', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Name-darkmode' : 'Name-lightmode']">Name</th>
<th :class="['Department', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Department-darkmode' : 'Department-lightmode']">Department / Job Title</th>
<th :class="['Pronouns', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Pronouns-darkmode' : 'Pronouns-lightmode']">Pronouns</th>
</tr>
<tr :class="['table-row', darkMode ? 'tr-darkmode' : 'tr-lightmode']" id="row-1">
<td :class="['ID', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'ID-darkmode' : 'ID-lightmode']">...</td>
<td :class="['Name', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Name-darkmode' : 'Name-lightmode']">...</td>
<td :class="['Department', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Department-darkmode' : 'Department-lightmode']">...</td>
<td :class="['Pronouns', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Pronouns-darkmode' : 'Pronouns-lightmode']">...</td>
</tr>
</table>
</section>
</template>
<script setup>
import { ref } from 'vue';
const darkMode = ref(true)
</script>
<script>
export default {
name: "ClientEmployeeList",
};
</script>
<style scoped>
.data {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
align-self: stretch;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.section-darkmode { background-color: #2c2c2c; }
.section-lightmode { background-color: #ffffff; }
.label {
padding: 1.25rem 0;
letter-spacing: 0.04rem;
text-decoration-line: underline;
font: 400 1rem/1.875rem Overpass, sans-serif;
}
.label-darkmode { color: #ffffff; }
.label-lightmode { color: #000000; }
.data-table {
width: 100%;
padding: 0 0.625rem;
table-layout: fixed;
border-collapse: collapse;
}
.table-row {
display: flex;
flex-direction: row;
align-items: center;
height: 3.125rem;
padding: 0.625rem;
gap: 0.625rem;
}
.tr-head-darkmode {
border-top: none;
border-bottom: 0.0625rem solid #000000;
}
.tr-head-lightmode {
border-top: none;
border-bottom: 0.0625rem solid #8e8e8e;
}
.tr-darkmode { border-top: 0.0625rem solid #000000; }
.tr-lightmode { border-top: 0.0625rem solid #8e8e8e; }
th, td {
height: 1.875rem;
width: 35%;
padding: 0;
text-align: left;
border-left: none;
letter-spacing: 0.02rem;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
th { font: 700 0.875rem/1.875rem Overpass, sans-serif; }
.th-darkmode, .td-darkmode {
color: #ffffff;
border-right: 0.0625rem solid #000000;
}
.th-lightmode, .td-lightmode {
color: #000000;
border-right: 0.0625rem solid #8e8e8e;
}
.ID { width: 20%; }
.Pronouns { width: 10%; }
.Pronouns-darkmode { border-right: none; }
.Pronouns-lightmode { border-right: none; }
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div :class="['data', darkMode ? 'div-darkmode' : 'div-lightmode']">
<section :class="['data', darkMode ? 'section-darkmode' : 'section-lightmode']">
<table class="data-table" id="client-table">
<tbody>
<tr :class="['table-row', darkMode ? 'tr-head-darkmode' : 'tr-head-lightmode']" id="table-head">
@@ -21,7 +21,7 @@
</tr>
</tbody>
</table>
</div>
</section>
</template>
@@ -52,12 +52,12 @@ export default {
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.div-darkmode {
.section-darkmode {
background-color: #2c2c2c;
}
.div-lightmode {
background-color: #fff;
.section-lightmode {
background-color: #ffffff;
}
.data-table {
@@ -94,8 +94,7 @@ export default {
border-top: 0.0625rem solid #8e8e8e;
}
th,
td {
th, td {
height: 1.875rem;
text-align: left;
padding: 0;
@@ -103,14 +102,12 @@ td {
font: 400 0.875rem/1.875rem Overpass, sans-serif;
}
.th-darkmode,
.td-darkmode {
.th-darkmode, .td-darkmode {
color: #ffffff;
}
.th-lightmode,
.td-lightmode {
color: #000;
.th-lightmode, .td-lightmode {
color: #000000;
}
th {

View File

@@ -1,5 +1,6 @@
<template>
<section v-if="hardwareBoolean" :class="['hardware-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<section v-if="hardwareBoolean && !addAssetBool"
:class="['hardware-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']" id="hardware">Hardware specifications:</div>
<div class="asset-data">
<div class="model-CPU">
@@ -53,6 +54,52 @@
</div>
</div>
</section>
<section v-if="addAssetBool" :class="['hardware-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']" id="hardware">Hardware specifications:</div>
<div class="asset-data">
<div class="model-CPU">
<div class="data-field" id="model">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">Model:</pre>
<input v-model="newModel" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
<div class="data-field" id="CPU">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">CPU:</pre>
<input v-model="newCPU" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
</div>
<div class="serial-number-RAM">
<div class="data-field" id="serial-number">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">Serial number:</pre>
<input v-model="newSerialnumber" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
<div class="data-field" id="RAM">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">RAM:</pre>
<input v-model="newRAM" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
</div>
</div>
<div class="asset-data">
<div class="additional">
<div class="storage-configuration">
<h3 :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Storage configuration:</h3>
<input v-model="newStorageConfiguration" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']"
id="storage-configuration">
</div>
</div>
<div class="additional">
<div class="miscellaneous">
<h3 :class="['area-title', darkMode ? 'h3-darkmode' : 'h3-lightmode']">Miscellaneous:</h3>
<input v-model="newMiscellaneous" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']" id="miscellaneous">
</div>
</div>
</div>
</section>
</template>
@@ -66,6 +113,15 @@ import { computed } from 'vue';
const store = useStore();
const editable = computed(() => store.state.assetEditable);
const chosenAssetId = computed(() => store.state.chosenAssetId);
const addAssetBool = computed(() => store.state.newAsset);
const newHardwareBool = ref(true);
const newModel = ref('');
const newSerialnumber = ref('');
const newCPU = ref('');
const newRAM = ref('');
const newStorageConfiguration = ref('');
const newMiscellaneous = ref('');
const darkMode = ref(true)
const item = ref({});
@@ -78,10 +134,6 @@ const getItemById = async () => {
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getConfigItem/${chosenAssetId.value}`
);
item.value = response.data;
if ((item.value.hardwareBool == 1) && (item.value.model === (null | "")) && (item.value.serialnumber.trim() === "") && (item.value.CPU.trim() === "") && (item.value.RAM.trim() === "") && (item.value.storageConfiguration.trim() === "") && (item.value.miscellaneous.trim() === "")) {
item.value.hardwareBool = 0;
hardwareBoolean.value = false;
};
if (item.value.hardwareBool == 1) {
hardwareBoolean.value = true;
};
@@ -90,8 +142,28 @@ const getItemById = async () => {
}
}
// update hardware fields in the store
const updateAsset = () => {
if (newModel.value.length === 0 && newSerialnumber.value.length === 0 && newCPU.value.length === 0 && newRAM.value.length === 0 && newStorageConfiguration.value.length === 0 && newMiscellaneous.value.length === 0) {
newHardwareBool.value = false
}
const asset = {
hardwareBool: newHardwareBool.value,
model: newModel.value,
serialnumber: newSerialnumber.value,
CPU: newCPU.value,
RAM: newRAM.value,
storageConfig: newStorageConfiguration.value,
miscellaneous: newMiscellaneous.value,
};
store.commit('updateHardwareComponent', asset);
}
//update data
const updateConfigItem = async () => {
if (item.value.model.length === 0 && item.value.serialnumber.length === 0 && item.value.CPU.length === 0 && item.value.RAM.length === 0 && item.value.storageConfiguration.length === 0 && item.value.miscellaneous.length === 0) {
item.value.hardwareBool = 0;
}
try {
await Axios.put(
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/updateConfigItem`,
@@ -150,7 +222,6 @@ export default {
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
@@ -171,7 +242,7 @@ export default {
align-items: center;
justify-content: flex-start;
width: 100%;
padding: 0 1.875rem;
padding: 0.8rem 1.875rem;
gap: 1.25rem;
border-radius: 0.625rem;
}

View File

@@ -1,5 +1,6 @@
<template>
<section v-if="networkBoolean" :class="['network-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<section v-if="networkBoolean && !addAssetBool"
:class="['network-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']" id="network">Network specifications:</div>
<div class="asset-data">
<div class="IPv4-MAC">
@@ -36,6 +37,38 @@
</div>
</div>
</section>
<section v-if="addAssetBool" :class="['network-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']" id="network">Network specifications:</div>
<div class="asset-data">
<div class="IPv4-MAC">
<div class="data-field" id="IPv4">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">IPv4:</pre>
<input v-model="newIPv4" @change="updateConfigItem()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
<div class="data-field" id="MAC">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">MAC:</pre>
<input v-model="newMAC" @change="updateConfigItem()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
</div>
<div class="IPv6-subnetmask">
<div class="data-field" id="IPv6">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">IPv6:</pre>
<input v-model="newIPv6" @change="updateConfigItem()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
<div class="data-field" id="subnetmask">
<pre :class="['label', darkMode ? 'div-darkmode' : 'div-lightmode']">Subnetmask:</pre>
<input v-model="newSubnetmask" @change="updateConfigItem()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</div>
</div>
</div>
</section>
<section v-if="addAssetBool" id="saveNewItem">
<button :class="[darkMode ? 'saveNewItem-darkmode' : 'saveNewItem-lightmode']" @click="addItem()">Safe</button>
</section>
</template>
@@ -49,40 +82,66 @@ import { computed } from 'vue';
const store = useStore();
const editable = computed(() => store.state.assetEditable);
const chosenAssetId = computed(() => store.state.chosenAssetId);
const addAssetBool = computed(() => store.state.newAsset);
const newAssetName = computed(() => store.state.newAssetName);
const newCustomerID = computed(() => store.state.newCustomerID);
const newCustomer = computed(() => store.state.newCustomer);
const newLocation = computed(() => store.state.newLocation);
const newRemoteLocation = computed(() => store.state.newRemoteLocation);
const newType = computed(() => store.state.newType);
const newDescription = computed(() => store.state.newDescription);
const newNotes = computed(() => store.state.newNotes);
const newState = computed(() => store.state.newState);
const newLastView = computed(() => store.state.newLastView);
const newUser = computed(() => store.state.newUser);
const hardwareBool = computed(() => store.state.hardwareBool);
const newModel = computed(() => store.state.newModel);
const newSerialnumber = computed(() => store.state.newSerialnumber);
const newCPU = computed(() => store.state.newCPU);
const newRAM = computed(() => store.state.newRAM);
const newStorageConfiguration = computed(() => store.state.newStorageConfiguration);
const newMiscellaneous = computed(() => store.state.newMiscellaneous);
const softwareBool = computed(() => store.state.softwareBool);
const newSoftware = computed(() => store.state.newSoftware);
const newVersion = computed(() => store.state.newVersion);
const newLicense = computed(() => store.state.newLicense);
const darkMode = ref(true)
const item = ref({});
const networkBoolean = ref(false);
const inputIPv4 = ref('');
const inputIPv6 = ref('');
const newNetworkBool = ref(true);
const newIPv4 = ref('');
const newIPv6 = ref('');
const newMAC = ref('');
const newSubnetmask = ref('');
const configItems = ref([]);
// get config item from id
const getItemById = async () => {
try {
const response = await Axios.get(
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getConfigItem/${chosenAssetId.value}`
);
item.value = response.data;
if ((item.value.networkBool == 1) && (item.value.IPv4 === (null | "")) && (item.value.IPv6 === (null | "")) && (item.value.subnetmask === (null | "")) && (item.value.MAC === (null | ""))) {
item.value.networkBool = 0;
networkBoolean.value = false;
};
if (item.value.networkBool == 1) {
networkBoolean.value = true;
};
if (item.value.IPv4 == null) {
inputIPv4.value = "";
} else {
inputIPv4.value = item.value.IPv4;
if (!addAssetBool.value) {
try {
const response = await Axios.get(
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getConfigItem/${chosenAssetId.value}`
);
item.value = response.data;
if (item.value.networkBool == 1) {
networkBoolean.value = true;
};
if (item.value.IPv4 == null) {
inputIPv4.value = "";
} else {
inputIPv4.value = item.value.IPv4;
}
if (item.value.IPv6 == null) {
inputIPv6.value = "";
} else {
inputIPv6.value = item.value.IPv6;
}
} catch (err) {
console.log(err.response.statusText);
}
if (item.value.IPv6 == null) {
inputIPv6.value = "";
} else {
inputIPv6.value = item.value.IPv6;
}
} catch (err) {
console.log(err.response.statusText);
}
}
@@ -108,6 +167,9 @@ const validateIPv6address = (ipaddress) => {
//update data
const updateConfigItem = async () => {
if (inputIPv4.value.length === 0 && inputIPv6.value.length === 0 && item.value.MAC.length === 0 && item.value.subnetmask.length === 0) {
item.value.networkBool = 0;
}
if (validateIPv4address(inputIPv4.value)) {
item.value.IPv4 = inputIPv4.value;
try {
@@ -202,10 +264,101 @@ const updateConfigItem = async () => {
}
alert("You have entered an invalid IPv6 address, please choose a valid one!");
}
await getItemById();
}
// add new config item
const addItem = async () => {
if (newIPv4.value.length === 0 && newIPv6.value.length === 0 && newMAC.value.length === 0 && newSubnetmask.value.length === 0) {
newNetworkBool.value = false;
}
// check if all input data is valid
if (newAssetName.value.trim() === "") {
alert("Please add a config item name!");
return;
} else {
var counter = 0;
// check if config item name already exists
configItems.value.forEach(ci => {
if (ci.assetName === newAssetName.value) {
counter += 1;
}
});
if (counter == 1) {
alert("This asset name already exists. Please choose an unique asset name or modify respectively delete the old one!");
return;
}
}
if (newCustomer.value.length === 0) {
alert("Please choose a client!");
return;
}
if (!validateIPv4address(newIPv4.value)) {
alert("Please enter a valid IPv4 address or leave this field empty!");
return;
}
if (!validateIPv6address(newIPv6.value)) {
alert("Please enter a valid IPv6 address or leave this field empty!");
return;
}
try {
const response = await Axios.post(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/addConfigItem`,
{
assetName: newAssetName.value,
customerID: newCustomerID.value,
customer: newCustomer.value,
location: newLocation.value,
remoteLocation: newRemoteLocation.value,
type: newType.value,
description: newDescription.value,
notes: newNotes.value,
state: newState.value,
lastView: newLastView.value,
user: newUser.value,
hardwareBool: hardwareBool.value,
model: newModel.value,
serialnumber: newSerialnumber.value,
CPU: newCPU.value,
RAM: newRAM.value,
storageConfiguration: newStorageConfiguration.value,
miscellaneous: newMiscellaneous.value,
softwareBool: softwareBool.value,
software: newSoftware.value,
version: newVersion.value,
license: newLicense.value,
networkBool: newNetworkBool.value,
IPv4: newIPv4.value,
IPv6: newIPv6.value,
MAC: newMAC.value,
subnetmask: newSubnetmask.value,
});
store.commit('resetAssetStore');
store.commit('changeToAssetlist');
} catch (err) {
console.log(err.response.statusText);
}
}
//get all config items
const getConfigItems = async () => {
try {
const response = await Axios.get(`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getAllConfigItems`);
configItems.value = response.data;
} catch (err) {
console.log(err.response.statusText);
}
}
// include delay to avoid 503 error
const triggerBackendCallsWithDelay = async (fetchDataFunc) => {
setTimeout(() => {
fetchDataFunc();
}, 1000);
}
onMounted(() => {
getItemById();
triggerBackendCallsWithDelay(getItemById);
triggerBackendCallsWithDelay(getConfigItems);
});
</script>
@@ -222,7 +375,6 @@ export default {
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
@@ -242,7 +394,7 @@ export default {
align-items: center;
justify-content: flex-start;
width: 100%;
padding: 0 1.875rem;
padding: 0.8rem 1.875rem;
gap: 1.25rem;
border-radius: 0.625rem;
}
@@ -306,6 +458,42 @@ export default {
border-radius: 0.3125rem;
}
.saveNewItem-darkmode {
background: #2c2c2c;
color: #fff;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
border: none;
padding: 1rem 1.875rem;
width: 8%;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.saveNewItem-lightmode {
background: #EBEBEB;
color: #212121;
font: 400 0.875rem/1.875rem Overpass, sans-serif;
border: none;
padding: 1rem 1.875rem;
width: 8%;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
}
.saveNewItem-darkmode:hover {
background-color: #444444;
cursor: pointer;
}
.saveNewItem-lightmode:hover {
background-color: #ACACAC;
cursor: pointer;
}
#saveNewItem {
text-align: center;
}
.input {
border: none;
}

View File

@@ -1,5 +1,6 @@
<template>
<section :class="['software-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<section v-if="softwareBoolean && !addAssetBool"
:class="['software-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']" id="software">Software specifications:
<div class="asset-data">
<table class="data-table" id="asset-table-no-client">
@@ -43,6 +44,41 @@
</div>
</div>
</section>
<section v-if="addAssetBool" :class="['software-information', darkMode ? 'section-darkmode' : 'section-lightmode']">
<div :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']" id="software">Software specifications:
<div class="asset-data">
<table class="data-table" id="asset-table-no-client">
<tbody>
<tr :class="['table-row', darkMode ? 'tr-head-darkmode' : 'tr-head-lightmode']" id="table-head">
<th
:class="['Software', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Software-darkmode' : 'Software-lightmode']">
Software</th>
<th
:class="['Version', darkMode ? 'th-darkmode' : 'th-lightmode', darkMode ? 'Version-darkmode' : 'Version-lightmode']">
Version</th>
<th :class="['License', darkMode ? 'th-darkmode' : 'th-lightmode']">License</th>
</tr>
<tr :class="['table-row', darkMode ? 'tr-darkmode' : 'tr-lightmode']" id="row-1">
<td
:class="['Software', darkMode ? 'td-darkmode' : 'td-lightmode', darkMode ? 'Software-darkmode' : 'Software-lightmode']">
<input type="text" v-model="newSoftware" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</td>
<td
:class="['Version', darkMode ? 'td-darkmode' : 'td-lightmode', darkMode ? 'Version-darkmode' : 'Version-lightmode']">
<input type="text" v-model="newVersion" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</td>
<td :class="['License', darkMode ? 'td-darkmode' : 'td-lightmode']">
<input type="text" v-model="newLicense" @change="updateAsset()"
:class="['data', 'input', darkMode ? 'data-darkmode' : 'data-lightmode']">
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</template>
<script setup>
@@ -55,6 +91,12 @@ import { computed } from 'vue';
const store = useStore();
const editable = computed(() => store.state.assetEditable);
const chosenAssetId = computed(() => store.state.chosenAssetId);
const addAssetBool = computed(() => store.state.newAsset);
const newSoftwareBool = ref(true);
const newSoftware = ref('');
const newVersion = ref('');
const newLicense = ref('');
const darkMode = ref(true)
const item = ref({});
@@ -67,10 +109,6 @@ const getItemById = async () => {
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/getConfigItem/${chosenAssetId.value}`
);
item.value = response.data;
if ((item.value.softwareBool == 1) && (item.value.version.trim() === "") && (item.value.software.trim() === "")) {
item.value.softwareBool = 0;
softwareBoolean.value = false;
};
if (item.value.softwareBool == 1) {
softwareBoolean.value = true;
};
@@ -79,8 +117,25 @@ const getItemById = async () => {
}
}
// update software fields in the store
const updateAsset = () => {
if (newSoftware.value.length == 0 && newLicense.value.length == 0 && newVersion.value.length == 0) {
newSoftwareBool.value = false
}
const asset = {
softwareBool: newSoftwareBool.value,
software: newSoftware.value,
version: newVersion.value,
license: newLicense.value,
};
store.commit('updateSoftwareComponent', asset);
}
//update data
const updateConfigItem = async () => {
if (item.value.license.length === 0 && item.value.version.length === 0 && item.value.software.length === 0) {
item.value.softwareBool = 0;
}
try {
await Axios.put(
`https://${clientsideConfig.url}:${clientsideConfig.port}/api/updateConfigItem`,
@@ -139,7 +194,6 @@ export default {
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
padding: 1.25rem 1.875rem;
border-radius: 0.625rem;
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);

39
composables/UserObject.ts Normal file
View File

@@ -0,0 +1,39 @@
export var UserObjectDefinition: {
id: string;
username: string;
password: string;
registered: string;
lastLogin: string;
fullName: string;
email: string;
phonenumber: string;
address: string;
city: string;
postcode: string;
adminBool: boolean;
technician1Bool: boolean;
technician2Bool: boolean;
technicianMonitoringBool: boolean;
merchantBool: boolean;
internBool: boolean;
}
export interface UserObject {
id: string;
username: string;
password: string;
registered: string;
lastLogin: string;
fullName: string;
email: string;
phonenumber: string;
address: string;
city: string;
postcode: string;
adminBool: boolean;
technician1Bool: boolean;
technician2Bool: boolean;
technicianMonitoringBool: boolean;
merchantBool: boolean;
internBool: boolean;
}

View File

@@ -1,25 +1,20 @@
<template>
<aside
:class="['actionbar', darkMode ? 'actionbar-darkmode' : 'actionbar-lightmode', isExpanded ? 'is-expanded' : 'is-not-expanded']">
<div :class="['icon', darkMode ? 'indicator-darkmode' : 'indicator-lightmode']" id="indicator-icon"
@click="toggleActionbar">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Indicator-Icon-Opened.svg" />
<aside :class="['actionbar', darkMode ? 'actionbar-darkmode' : 'actionbar-lightmode', isExpanded ? 'is-expanded' : 'is-not-expanded']">
<div :class="['icon', darkMode ? 'indicator-darkmode' : 'indicator-lightmode']" id="indicator-icon" @click="toggleActionbar">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Indicator-Icon-Opened.svg" />
</div>
<nav class="actions">
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="search">
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="search" @click="toggleSearched">
<div class="icon" id="search-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Search-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Search-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Search</pre>
</Transition>
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="filter">
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="filter" @click="toggleFiltered">
<div class="icon" id="filter-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Filter-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Filter-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Filter</pre>
@@ -27,8 +22,7 @@
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="instances">
<div class="icon" id="instances-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Instances-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Instances-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Instances</pre>
@@ -36,8 +30,7 @@
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="attachments">
<div class="icon" id="attachments-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Attachments-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Attachments-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Attachments</pre>
@@ -45,8 +38,7 @@
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="sell">
<div class="icon" id="sell-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Sell-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Sell-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Sell</pre>
@@ -54,17 +46,15 @@
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="archive">
<div class="icon" id="archive-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Archive-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Archive-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Archive</pre>
</Transition>
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="new">
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="new" @click="addAsset">
<div class="icon" id="new-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Add-New-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Add-New-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">New</pre>
@@ -72,8 +62,7 @@
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="edit" @click="toggleEditable">
<div class="icon" id="edit-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Edit-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Edit-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Edit</pre>
@@ -81,8 +70,7 @@
</button>
<button :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="delete" @click="deleteAsset">
<div class="icon" id="delete-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/actionbar-icons/Delete-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/actionbar-icons/Delete-Icon.svg" />
</div>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Delete</pre>
@@ -93,8 +81,12 @@
</template>
<script setup>
import { ref } from 'vue';
import { useStore } from 'vuex';
const darkMode = ref(true)
const isExpanded = ref(true)
// get accesss to the store
const store = useStore()
const toggleEditable = () => {
@@ -103,26 +95,26 @@ const toggleEditable = () => {
const deleteAsset = () => {
store.commit('doDeleteAsset');
};
const toggleActionbar = () => {
isExpanded.value = !isExpanded.value;
};
const toggleFiltered = () => {
store.commit('toggleFilteredAsset');
};
const toggleSearched = () => {
store.commit('toggleAssetSearchable');
};
const addAsset = () => {
store.commit('addNewAsset');
};
</script>
<script>
export default {
name: "Actionbar",
data() {
return {
darkMode: true,
isExpanded: true,
};
},
methods: {
toggleActionbar() {
this.isExpanded = !this.isExpanded;
},
},
}
</script>
@@ -137,7 +129,7 @@ aside.actionbar {
display: flex;
flex-direction: column;
position: sticky;
top: 5rem;
top: 3.75rem;
height: fit-content;
width: fit-content;
inline-size: fit-content;
@@ -147,37 +139,30 @@ aside.actionbar {
justify-content: center;
padding: 1.875rem 0.625rem;
gap: 1.25rem;
transition: all 10s ease-in-out 0s;
overflow: clip;
}
.actionbar-darkmode {
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
background-color: #2C2C2C;
}
.actionbar-lightmode {
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
background-color: #FFFFFF;
}
aside.is-expanded {
animation: expand 0.5s linear both;
}
aside.is-not-expanded {
animation: contract 0.5s linear both;
width: stretch;
inline-size: stretch;
}
.actions {
display: flex;
flex-direction: column;
padding: 1.25rem 0;
align-items: flex-start;
justify-content: center;
padding: 1.25rem 0;
gap: 1.25rem;
overflow: clip;
}
@@ -191,34 +176,21 @@ button {
border-radius: 0.625rem;
align-items: center;
justify-content: flex-start;
gap: 0.125rem;
border: none;
transition: 0.2s ease-in-out;
overflow: clip;
gap: 0.125rem
}
.button-darkmode { background-color: #2C2C2C; }
.button-lightmode { background-color: #FFFFFF; }
.button-darkmode {
background-color: #2C2C2C;
}
.button-darkmode:hover,
.indicator-darkmode:hover { background-color: #444444; }
.button-lightmode {
background-color: #FFFFFF;
}
.button-lightmode:hover,
.indicator-lightmode:hover { background-color: #ACACAC; }
.button-darkmode:hover,
.indicator-darkmode:hover {
background-color: #444444;
}
.button-lightmode:hover,
.indicator-lightmode:hover {
background-color: #ACACAC;
}
.is-not-expanded>button {
align-self: center;
gap: 0.5rem;
}
.is-not-expanded>button { align-self: center; }
.icon {
@@ -229,7 +201,9 @@ button {
align-items: center;
justify-content: center;
padding: 0 0.5rem;
transition: padding 0.5s linear
}
.is-not-expanded .icon {
align-self: center;
}
#indicator-icon {
@@ -237,16 +211,9 @@ button {
height: 1.875rem;
border-radius: 0.3125rem;
transition: 0.2s ease-in-out;
}
}
.is-not-expanded #indicator-icon { transform: rotate(180deg); }
.is-not-expanded .icon {
align-self: center;
padding: 0 0.375rem;
}
.is-not-expanded #indicator-icon {
transform: rotate(180deg);
}
img {
width: 0.875rem;
@@ -255,44 +222,31 @@ img {
object-position: center;
overflow: hidden;
}
#indicator-icon>img {
height: 1.25rem;
}
.img-darkmode {
filter: invert(100%);
}
.img-darkmode { filter: invert(100%); }
#indicator-icon>img { height: 1.25rem; }
.label {
display: inline-flex;
min-width: 0;
padding: 0rem 0.3125rem 0rem 0rem;
letter-spacing: 5%;
padding: 0rem 0.625rem 0rem 0rem;
align-self: center;
letter-spacing: 0.02rem;
white-space: nowrap;
font: 400 0.875rem/1.25rem Overpass, sans-serif;
}
.label-darkmode {
color: #FFFFFF;
}
.label-lightmode {
color: #000000;
}
}
.label-darkmode { color: #FFFFFF; }
.label-lightmode { color: #000000; }
.fade-enter-from,
.fade-leave-to {
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
max-width: 0;
}
.fade-enter-active {
@@ -312,8 +266,8 @@ img {
@keyframes expand {
from {
max-inline-size: 3.25rem;
max-width: 3.25rem;
max-inline-size: 3.125rem;
max-width: 3.125rem;
}
to {
@@ -329,8 +283,8 @@ img {
}
to {
max-inline-size: 3.25rem;
max-width: 3.25rem;
max-inline-size: 3.125rem;
max-width: 3.125rem;
}
}

View File

@@ -1,67 +1,64 @@
<template>
<aside
:class="['navbar', darkMode ? 'navbar-darkmode' : 'navbar-lightmode', isExpanded ? 'is-expanded' : 'is-not-expanded']">
<aside :class="['navbar', darkMode ? 'navbar-darkmode' : 'navbar-lightmode', isExpanded ? 'is-expanded' : 'is-not-expanded']">
<div class="toggleNavbar">
<div v-if="isExpanded" :class="['icon', darkMode ? 'back-darkmode' : 'back-lightmode']" id="back-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="../icons/Back-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/Back-Icon.svg" />
</div>
<div :class="['icon', darkMode ? 'indicator-darkmode' : 'indicator-lightmode']" id="indicator-icon"
@click="ToggleSidebar">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/navbar-icons/Nav-Indicator-Icon-Opened.svg" />
<div :class="['icon', darkMode ? 'indicator-darkmode' : 'indicator-lightmode']" id="indicator-icon" @click="ToggleSidebar">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/navbar-icons/Nav-Indicator-Icon-Opened.svg" />
</div>
</div>
<div class="menus">
<nav id="home-menu" :class="[darkMode ? 'menu-darkmode' : 'menu-lightmode']">
<router-link to="/home" class="button"
:class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="home-button">
<router-link to="/home" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="home-button">
<div class="icon" id="home-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/navbar-icons/Home-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/navbar-icons/Home-Icon.svg" />
</div>
<span v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Home</span>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Home</pre>
</Transition>
</router-link>
</nav>
<nav id="site-menu">
<router-link to="/maintenanceVisits" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']"
id="checklists-button">
<router-link to="/maintenanceVisits" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="checklists-button">
<div class="icon" id="checklists-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/navbar-icons/Checklists-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/navbar-icons/Checklists-Icon.svg" />
</div>
<span v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Checklists</span>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Checklists</pre>
</Transition>
</router-link>
<nuxt-link to="/assets" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" @click="defaultAssetPage()"
id="assets-button">
<nuxt-link to="/assets" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" @click="defaultAssetPage()" id="assets-button">
<div class="icon" id="assets-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/navbar-icons/Assets-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/navbar-icons/Assets-Icon.svg" />
</div>
<span v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Config Items</span>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Assets</pre>
</Transition>
</nuxt-link>
<router-link to="/solutions" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']"
id="solutions-button">
<router-link to="/solutions" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="solutions-button">
<div class="icon" id="solutions-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/navbar-icons/Solutions-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/navbar-icons/Solutions-Icon.svg" />
</div>
<span v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Solutions</span>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Solutions</pre>
</Transition>
</router-link>
<router-link to="/issueSlips" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']"
id="accounting-button">
<router-link to="/issueSlips" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="accounting-button">
<div class="icon" id="accounting-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/navbar-icons/Accounting-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/navbar-icons/Accounting-Icon.svg" />
</div>
<span v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Accounting</span>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Accounting</pre>
</Transition>
</router-link>
<router-link to="/clients" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']"
id="clients-button">
<router-link to="/clients" class="button" :class="[darkMode ? 'button-darkmode' : 'button-lightmode']" id="clients-button">
<div class="icon" id="clients-icon">
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy"
src="../icons/navbar-icons/Clients-Icon.svg" />
<img :class="[darkMode ? 'img-darkmode' : 'img-lightmode']" loading="lazy" src="/icons/navbar-icons/Clients-Icon.svg" />
</div>
<span v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Clients</span>
<Transition name="fade">
<pre v-if="isExpanded" :class="['label', darkMode ? 'label-darkmode' : 'label-lightmode']">Clients</pre>
</Transition>
</router-link>
</nav>
</div>
@@ -102,47 +99,37 @@ export default {
box-sizing: border-box;
}
aside {
aside.navbar {
display: flex;
flex-direction: column;
position: sticky;
top: 5rem;
width: 3.125rem;
transition: 0.5s ease-in-out;
}
top: 3.75rem;
height: fit-content;
width: fit-content;
inline-size: fit-content;
border-radius: 0.625rem;
flex: 0 0 0;
align-items: stretch;
justify-content: center;
padding: 0.9375rem;
gap: 0.625rem;
overflow: clip;
overflow-clip-margin: 0.625rem;
}
aside.is-expanded {
width: 12.5rem;
}
.is-expanded .label {
opacity: 1;
transition: 0.5s ease-in-out;
}
animation: expand 0.5s linear both;
}
aside.is-not-expanded {
animation: contract 0.5s linear both;
}
.is-not-expanded #indicator-icon {
transform: rotate(180deg);
transition: 0.5s ease-in-out;
}
.navbar {
display: flex;
flex-direction: column;
height: fit-content;
border-radius: 0.625rem;
align-items: stretch;
justify-content: center;
gap: 0.625rem;
padding: 0.9375rem;
margin: 0;
}
}
.navbar-darkmode {
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
background-color: #2C2C2C;
}
}
.navbar-lightmode {
box-shadow: 0.25rem 0.25rem 0.25rem 0rem rgba(0, 0, 0, 0.25);
background-color: #FFFFFF;
@@ -155,75 +142,67 @@ aside.is-expanded {
height: 2.5rem;
align-items: center;
justify-content: space-between;
overflow: clip;
overflow-clip-margin: 0.625rem;
}
.menus {
display: flex;
flex-direction: column;
position: relative;
justify-content: flex;
padding: 1.25rem 0;
align-items: flex-start;
justify-content: center;
gap: 1.875rem;
transition: 0.5s ease-in-out;
align-items: center;
overflow: clip;
overflow-clip-margin: 0.625rem;
}
nav {
display: flex;
flex-direction: column;
justify-content: center;
align-self: stretch;
padding: 0.9375rem 0;
align-items: flex-start;
justify-content: center;
gap: 1.875rem;
width: 92%;
}
overflow: clip;
overflow-clip-margin: 0.625rem;
}
.menu-darkmode {
border-bottom: 0.0625rem solid #8E8E8E;
}
.menu-lightmode {
border-bottom: 0.0625rem solid #BABABA;
}
.menu-darkmode { border-bottom: 0.0625rem solid #8E8E8E; }
.menu-lightmode { border-bottom: 0.0625rem solid #BABABA; }
button,
.button,
a {
display: flex;
flex-direction: row;
align-self: stretch;
height: 2.1875rem;
border-radius: 0.3125rem;
border-radius: 0.625rem;
align-items: center;
justify-content: flex-start;
gap: 0.3125rem;
border: none;
transition: 0.5s ease-in-out;
text-decoration: none;
width: 100%;
}
.button-darkmode {
background-color: #2C2C2C;
}
.button-lightmode {
background-color: #FFFFFF;
}
transition: 0.2s ease-in-out;
overflow: clip;
overflow-clip-margin: 0.625rem;
}
.button-darkmode { background-color: #2C2C2C; }
.button-lightmode { background-color: #FFFFFF; }
.button-darkmode:hover,
.indicator-darkmode:hover,
.back-darkmode:hover {
background-color: #444444;
}
.back-darkmode:hover { background-color: #444444; }
.button-lightmode:hover,
.indicator-lightmode:hover,
.back-lightmode:hover {
background-color: #ACACAC;
}
.back-lightmode:hover { background-color: #ACACAC; }
.is-not-expanded>button { align-self: center; }
.icon {
display: flex;
@@ -232,24 +211,18 @@ a {
height: 2.1875rem;
align-items: center;
justify-content: center;
transition: 0.2s ease-in-out;
}
}
#back-icon,
#indicator-icon {
width: 2.5rem;
height: 2.5rem;
border-radius: 0.3125rem;
transition-duration: 0.5s;
transition: 0.2s ease-out;
}
transition: 0.2s ease-in-out;
}
.back-darkmode:hover,
.back-lightmode:hover,
.indicator-darkmode:hover,
.indicator-lightmode:hover {
cursor: pointer;
}
.indicator-lightmode:hover { cursor: pointer; }
img {
@@ -258,28 +231,95 @@ img {
object-fit: contain;
object-position: center;
overflow: hidden;
transition: 0.2s ease-out;
}
}
.img-darkmode { filter: invert(100%); }
.img-darkmode {
filter: invert(100%);
}
.label {
letter-spacing: 5%;
display: inline-flex;
min-width: 0;
padding: 0rem 0.625rem 0rem 0.3125rem;
align-self: center;
letter-spacing: 0.03rem;
white-space: nowrap;
margin: auto 0;
font: 600 0.875rem/1.25rem Overpass, sans-serif;
}
.label-darkmode { color: #FFFFFF; }
.label-lightmode { color: #000000; }
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transition: opacity 0.5s ease-out;
padding: 0;
}
.label-darkmode {
color: #FFFFFF;
.fade-enter-to,
.fade-leave-from {
opacity: 1;
padding: 0rem 0.625rem 0rem 0.3125rem;
}
.label-lightmode {
color: #000000;
.fade-enter-active {
transition: all 0.75s linear;
animation: fade-in 0.5s linear forwards;
}
.fade-leave-active {
transition: all 0.75s linear;
animation: fade-out 0.5s linear forwards;
}
.fade-move {
transition: transform 5s
}
@keyframes expand {
from {
max-inline-size: 4.0625rem;
max-width: 4.0625rem;
}
to {
max-inline-size: 15rem;
max-width: 15rem;
}
}
@keyframes contract {
from {
max-inline-size: 15rem;
max-width: 15rem;
}
to {
max-inline-size: 4.0625rem;
max-width: 4.0625rem;
}
}
@keyframes fade-in {
from {
max-width: 0;
max-inline-size: 0;
}
to {
max-width: 10rem;
width: fit-content;
}
}
@keyframes fade-out {
from {
max-width: 10rem;
width: fit-content;
}
to {
max-width: 0;
max-inline-size: 0;
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<header :class="[darkMode ? 'header-darkmode' : 'header-lightmode']">
<img id="header-logo" loading="lazy" src="../tüit-logo.svg.png" />
<img id="header-logo" loading="lazy" src="/tüit-logo.svg.png" />
<div class="profile">
<div :class="['username', darkMode ? 'username-darkmode' : 'username-lightmode']">username</div>
<pre :class="['username', darkMode ? 'username-darkmode' : 'username-lightmode']" id='uname'></pre>
<div :class="['picture', darkMode ? 'picture-darkmode' : 'picture-lightmode']">
<img id="picture" loading="lazy" src="" />
</div>
@@ -12,19 +12,59 @@
<script setup>
import { useAuthStore } from '~/store/auth';
import { ref } from 'vue';
const darkMode = ref(true)
//const auth = ref();
const username = ref('username');
onMounted(() => {
//auth.value = useAuthStore();
//username.value = auth.value.username;
try {
username.value = useAuthStore().username;
} finally {
document.getElementById('uname').innerHTML = username;
}
});
onUpdated(() => {
try {
username.value = useAuthStore().username;
} finally {
document.getElementById('uname').innerHTML = username;
}
})
</script>
<script>
import { useAuthStore } from '~/store/auth';
export default {
name: "PageHeader",
/*mounted() {
try {
const auth = useAuthStore();
const username = auth.username;
document.getElementById('uname').innerHTML = username;
} catch {
document.getElementById('uname').innerHTML = 'username'
}
},
updated() {
const auth = useAuthStore();
const username = auth.username;
document.getElementById('uname').innerHTML = username;
},*/
}
</script>
<style scoped>
* {
box-sizing: border-box;
@@ -36,28 +76,28 @@ header {
position: sticky;
top: 0;
width: 100%;
height: 4.375rem;
height: 3.125rem;
align-self: stretch;
justify-content: space-between;
align-items: center;
padding: 0.625rem 1.25rem;
padding: 0.375rem 1.875rem;
}
.header-darkmode {
background-color: #212121;
border-bottom: 0.125em solid #000;
border-bottom: 0.125em solid #000000;
}
.header-lightmode {
background-color: #EBEBEB;
border-bottom: 0.125em solid #8e8e8e61;
background-color: #ebebeb;
border-bottom: 0.125em solid #8e8e8e;
}
#header-logo {
object-fit: contain;
object-position: center;
width: 5rem;
height: 2.5rem;
width: 4rem;
height: 2rem;
justify-content: center;
align-items: center;
overflow: hidden;
@@ -68,37 +108,36 @@ header {
flex-direction: row;
align-items: flex-end;
gap: 0.625rem;
padding: 0 0.625rem;
padding: 0 0.375rem;
}
.username {
align-self: center;
margin: auto 0;
text-align: right;
font: 200 0.875rem Overpass, sans-serif;
letter-spacing: 5%;
font: 200 0.75rem/1.25rem Overpass, sans-serif;
letter-spacing: 0.01rem;
}
.username-darkmode {
color: #fff;
color: #ffffff;
}
.username-lightmode {
color: #000;
color: #000000;
}
.picture {
display: flex;
width: 2.5rem;
height: 2.5rem;
width: 1.875rem;
height: 1.875rem;
border-radius: 50%;
}
.picture-darkmode {
background-color: #fff;
background-color: #ffffff;
}
.picture-lightmode {
background-color: #000;
background-color: #000000;
}
</style>

24
nuxt-app/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

75
nuxt-app/README.md Normal file
View File

@@ -0,0 +1,75 @@
# Nuxt 3 Minimal Starter
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install the dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

5
nuxt-app/app.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div>
<NuxtWelcome />
</div>
</template>

4
nuxt-app/nuxt.config.ts Normal file
View File

@@ -0,0 +1,4 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true }
})

9593
nuxt-app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

17
nuxt-app/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"nuxt": "^3.10.3",
"vue": "^3.4.19",
"vue-router": "^4.3.0"
}
}

BIN
nuxt-app/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

4
nuxt-app/tsconfig.json Normal file
View File

@@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

View File

@@ -1,7 +1,54 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
import type { NuxtPage } from 'nuxt/schema'
import clientsideConfig from './clientsideConfig'
import { UserObjectDefinition } from './composables/UserObject'
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@pinia/nuxt',
'@sidebase/nuxt-auth',
],
auth: {
//baseURL: `https://${clientsideConfig.url}:${clientsideConfig.port}/.output/server/chunks/routes/api/auth`,
computed: {
origin: `https://${clientsideConfig.url}:${clientsideConfig.port}/`,
//pathname: '/server/chunks/routes/api/auth/',
//fullBaseUrl: `https://${clientsideConfig.url}:${clientsideConfig.port}/server/chunks/routes/api/auth/`,
},
//baseUrl: `https://${clientsideConfig.url}:${clientsideConfig.port}/server/chunks/routes/api/auth/`,
provider: {
type: 'refresh',
endpoints: {
signIn: { path: '/login', method: 'post' },
signout: false,
signUp: { path: '/signup', method: 'post' },
getSession: { path: '/session', method: 'get' },
refresh: { path: '/refresh', method: 'post' }
},
token: {
signInResponseTokenPointer: '/token/authToken',
maxAgeInSeconds: 300, // 5 min
sameSiteAttribute: 'lax'
},
refreshToken: {
signInResponseRefreshTokenPointer: '/token/refreshToken',
maxAgeInSeconds: 604800, // 7 days
sameSiteAttribute: 'lax'
},
// TODO: define UserObject
//sessionDataType: UserObjectDefinition,
},
session: {
enableRefreshPeriodically: false,
enableRefreshOnWindowFocus: true,
},
globalAppMiddleware: true,
},
/*buildModules: [
//'@nuxtjs/composition-api/module',
['@pinia/nuxt', { disableVuex: false }],
],*/
devServer: {
https: {
key: './certs/privkey.pem',
@@ -10,10 +57,40 @@ export default defineNuxtConfig({
},
vite: {
server: {
cors: {
cors: {
origin: true,
optionsSuccessStatus: 204,
},
}
}
},
runtimeConfig: {
public: {
apiBase: `https://${clientsideConfig.url}:${clientsideConfig.port}/server/chunks/routes/api`,
axios: {
browserBaseURL: `https://${clientsideConfig.url}:${clientsideConfig.port}/`,
},
},
private: {
axios: {
baseURL: `https://${clientsideConfig.url}:${clientsideConfig.port}/`,
}
}
},
// hooks: {
// 'pages:extend'(pages) {
// function setMiddleware(pages: NuxtPage[]) {
// for (const page of pages) {
// if (/* some condition */ true) {
// page.meta ||= {}
// // Note that this will override any middleware set in `definePageMeta` in the page
// page.meta.middleware = ['auth']
// }
// if (page.children) {
// setMiddleware(page.children)
// }
// }
// }
// setMiddleware(pages)
// }
// }
})

4664
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,25 +7,37 @@
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
"prepare": "nuxt prepare",
"cleanup": "nuxt cleanup"
},
"devDependencies": {
"@nuxt/devtools": "latest",
"nuxt": "^3.8.0",
"@pinia/nuxt": "^0.5.1",
"@sidebase/nuxt-auth": "^0.6.7",
"nuxt": "^3.10.3",
"pinia": "^2.1.7",
"vue": "^3.3.7",
"vue-router": "^4.2.5"
},
"dependencies": {
"@nuxt/module-builder": "^0.5.5",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.11.24",
"@vueform/toggle": "^2.1.4",
"axios": "^1.6.7",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.18.2",
"h3": "^1.11.1",
"jsonwebtoken": "^9.0.2",
"leading-trim": "^1.0.2",
"mariadb": "^3.2.3",
"nuxi": "^3.10.1",
"pinia-plugin-persistedstate": "^3.2.1",
"typescript": "^5.3.3",
"uuid": "^9.0.1",
"vite": "^5.1.0",
"vue-tsc": "^2.0.5",
"vuex": "^4.1.0"
}
}

View File

@@ -40,7 +40,8 @@ const onAsset = computed(() => store.state.onAsset);
const onSolutionlistAsset = computed(() => store.state.onSolutionlistAsset);
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Assets'
})
const darkMode = ref(true)

View File

@@ -7,6 +7,11 @@
<ClientTable v-if="onCustomerlist"/>
<Client v-if="onCustomer"/>
<ClientQuickAccess v-if="onCustomer"/>
<ClientEmployeeList v-if="onEmployeelist"/>
<ClientEmployee v-if="onEmployee"/>
<ClientDepartmentList v-if="onDepartmentlist"/>
<ClientDepartment v-if="onDepartment"/>
<ClientDepartmentEmployeeList v-if="onDepartment"/>
</div>
</section>
</template>
@@ -18,18 +23,26 @@ import { ref } from 'vue';
import ClientTable from "../components/server/ClientTable.vue";
import Client from "../components/server/Client.vue";
import ClientQuickAccess from "../components/server/ClientQuickAccess.vue";
//import ClientEmployees from "../components/server/ClientEmployees.vue";
//import ClientEmployee from "../components/server/ClientEmployee.vue";
import ClientEmployeeList from "../components/server/ClientEmployeeList.vue";
import ClientEmployee from "../components/server/ClientEmployee.vue";
import ClientDepartmentList from '~/components/server/ClientDepartmentList.vue';
import ClientDepartment from '~/components/server/ClientDepartment.vue';
import ClientDepartmentEmployeeList from '~/components/server/ClientDepartmentEmployeeList.vue';
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Clients'
})
const darkMode = ref(true)
// to render the right components
const onCustomerlist = ref(true)
const onCustomer = ref(true)
const onCustomer = ref(false)
const onEmployeelist = ref(false)
const onEmployee = ref(false)
const onDepartmentlist = ref(false)
const onDepartment = ref(false)
</script>
<script>

View File

@@ -20,7 +20,8 @@ import Dashboard from "../components/Dashboard.vue";
import QuickAccess from "../components/QuickAccess.vue";
definePageMeta({
layout: 'empty'
layout: 'empty',
title: 'Home'
})
const darkMode = ref(true)

View File

@@ -14,7 +14,8 @@
import { ref } from 'vue';
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Test'
})
const darkMode = ref(true)

View File

@@ -20,8 +20,8 @@
&nbsp;
&nbsp;
&nbsp;
<router-link to="/issues" class="button" id="issues-button">
<h1 :class="[darkMode ? 'h1-darkmode' : 'h1-lightmode']" id="active-page-name">Issues</h1>
<router-link to="/issueItems" class="button" id="issues-button">
<h1 :class="[darkMode ? 'h1-darkmode' : 'h1-lightmode']" id="active-page-name">Issue Items</h1>
</router-link>
</div>
<div id="content-body">
@@ -43,7 +43,8 @@ import IssueVariants from "../components/server/IssueVariants.vue";
import IssueVariant from "../components/server/IssueVariant.vue";
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Issue Items'
})
const darkMode = ref(true)
@@ -56,7 +57,7 @@ const onIssueItemVariant = ref(false)
<script>
export default {
name: "IssuePage",
name: "IssueItemsPage",
}
</script>

View File

@@ -20,8 +20,8 @@
&nbsp;
&nbsp;
&nbsp;
<router-link to="/issues" class="button" id="issues-button">
<h1 :class="[darkMode ? 'h1-darkmode' : 'h1-lightmode']" id="page-name">Issues</h1>
<router-link to="/issueItems" class="button" id="issues-button">
<h1 :class="[darkMode ? 'h1-darkmode' : 'h1-lightmode']" id="page-name">Issue Items</h1>
</router-link>
</div>
<div id="content-body">
@@ -47,7 +47,8 @@ import OrderingInformation from "../components/server/OrderingInformation.vue";
import Accounting from "../components/server/Accounting.vue";
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Issue Slips'
})
const darkMode = ref(true)

View File

@@ -16,7 +16,9 @@ import { ref } from 'vue';
import LoginForm from "../components/LoginForm.vue";
definePageMeta({
layout: 'empty'
layout: 'empty',
title: 'Login',
auth: false,
})
const darkMode = ref(true)

View File

@@ -53,7 +53,8 @@ import MaintenanceVisitsInstance from "../components/server/MaintenanceVisitsIns
import InstanceChecklist from "../components/server/InstanceChecklist.vue";
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Maintenance Visits'
})
const darkMode = ref(true)

View File

@@ -53,7 +53,8 @@ import ProductionOrdersInstance from "../components/server/ProductionOrdersInsta
import InstanceChecklist from "../components/server/InstanceChecklist.vue";
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Production Orders'
})
const darkMode = ref(true)

View File

@@ -39,7 +39,8 @@ import UserAppearance from "../components/server/UserAppearance.vue";
import UserRightsList from "../components/server/UserRightsList.vue";
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Settings'
})
const darkMode = ref(true)

View File

@@ -24,7 +24,8 @@ import Solution from "../components/server/Solution.vue";
import SolutionChecklist from "../components/server/SolutionChecklist.vue";
definePageMeta({
layout: 'default'
layout: 'default',
title: 'Solutions'
})
const darkMode = ref(true)

0
pinia.js Normal file
View File

View File

@@ -4,18 +4,52 @@ const store = createStore({
state() {
return {
assetEditable: false,
assetFiltered: false,
assetSearchable: false,
deleteAsset: false,
onAssetlist: true,
onCustomerAssetlist: false,
onAsset: false,
onSolutionlistAsset: false,
chosenAssetId: -1
chosenAssetId: -1,
filteredAssetbyClient: '',
newAsset: false,
newAssetName: '',
newCustomerID: '',
newCustomer: '',
newLocation: '',
newRemoteLocation: '',
newType: '',
newDescription: '',
newNotes: '',
newState: '',
newLastView: '',
newUser: '',
hardwareBool: false,
newModel: '',
newSerialnumber: '',
newCPU: '',
newRAM: '',
newStorageConfiguration: '',
newMiscellaneous: '',
softwareBool: false,
newSoftware: '',
newVersion: '',
newLicense: '',
};
},
mutations: {
toggleEditableAsset(state) {
state.assetEditable = !state.assetEditable
},
toggleFilteredAsset(state) {
if (state.assetFiltered == false) {
state.assetFiltered = true
state.assetSearchable = false
} else {
state.assetFiltered = false
}
},
changeToAssetlist(state) {
state.onAssetlist = true
state.onCustomerAssetlist = false
@@ -44,14 +78,119 @@ const store = createStore({
state.chosenAssetId = id
},
resetAssetStore(state) {
state.chosenAssetId = -1
state.assetEditable = false
state.assetFiltered = false
state.assetSearchable = false
state.deleteAsset = false
state.chosenAssetId = -1
state.filteredAssetbyClient = ''
state.newAsset = false
state.newAssetName = ''
state.newCustomerID = ''
state.newCustomer = ''
state.newLocation = ''
state.newRemoteLocation = ''
state.newType = ''
state.newDescription = ''
state.newNotes = ''
state.newState = ''
state.newLastView = ''
state.newUser = ''
state.hardwareBool = false
state.newModel = ''
state.newSerialnumber = ''
state.newCPU = ''
state.newRAM = ''
state.newStorageConfiguration = ''
state.newMiscellaneous = ''
state.softwareBool = false
state.newSoftware = ''
state.newVersion = ''
state.newLicense = ''
},
// functions to change the production order and maintenance visit pages
changeToTemplatelist(state) {
state.onTemplatelist = true
state.onCustomerTemplatelist = false
state.onTemplate = false
state.onInstancelist = false
state.onInstance = false
},
changeToCustomerTemplatelist(state) {
state.onTemplatelist = false
state.onCustomerTemplatelist = true
state.onTemplate = false
state.onInstancelist = false
state.onInstance = false
},
changeToTemplate(state) {
state.onTemplatelist = false
state.onCustomerTemplatelist = false
state.onTemplate = true
state.onInstancelist = false
state.onInstance = false
},
changeToInstancelist(state) {
state.onTemplatelist = false
state.onCustomerTemplatelist = false
state.onTemplate = false
state.onInstancelist = true
state.onInstance = false
},
doDeleteAsset(state) {
state.deleteAsset = true
},
undoDeleteAsset(state) {
state.deleteAsset = false
},
updateAssetFilterbyClient(state, client) {
state.filteredAssetbyClient = client
},
toggleAssetSearchable(state) {
if (state.assetSearchable == false) {
state.assetSearchable = true
state.assetFiltered = false
} else {
state.assetSearchable = false
}
state.filteredAssetbyClient = ''
},
addNewAsset(state) {
state.newAsset = true
state.assetEditable = false
state.assetFiltered = false
state.assetSearchable = false
state.onAssetlist = false
state.onCustomerAssetlist = false
state.onAsset = true
state.onSolutionlistAsset = false
},
updateAssetComponent(state, asset) {
state.newAssetName = asset.assetName
state.newCustomerID = asset.customerId
state.newCustomer = asset.customer
state.newLocation = asset.location
state.newRemoteLocation = asset.remoteLocation
state.newType = asset.type
state.newDescription = asset.description
state.newNotes = asset.notes
state.newState = asset.state
},
updateHardwareComponent(state, asset) {
state.hardwareBool = asset.hardwareBool
state.newModel = asset.model
state.newSerialnumber = asset.serialnumber
state.newCPU = asset.CPU
state.newRAM = asset.RAM
state.newStorageConfiguration = asset.storageConfig
state.newMiscellaneous = asset.miscellaneous
},
updateSoftwareComponent(state, asset) {
state.softwareBool = asset.softwareBool
state.newSoftware = asset.software
state.newVersion = asset.version
state.newLicense = asset.license
}
},
});

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m11 5.5-4.8 4.6a4 4 0 0 1-1.9.9c-.9 0-1.6-.1-2.3-.6C1.4 9.8 1 9 1 8c.1-.7.6-1.5 1.2-2l4.7-4.5c.4-.4 1-.6 1.5-.6.6 0 1.2.2 1.6.6.4.3.6.8.6 1.4 0 .5-.2 1.2-.6 1.5L5.2 9.1c-.2.1-.7.5-1 .5-.4 0-1 0-1.3-.3-.4-.4-.5-.7-.4-1.2 0-.5.4-.9.6-1L7 3.4"/>
</svg>

After

Width:  |  Height:  |  Size: 426 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" fill="none" viewBox="0 0 12 11">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M5 7v2c0 .6-.4 1-1 1H2a1 1 0 0 1-1-1V7c0-.6.4-1 1-1h2c.6 0 1 .4 1 1Zm3-5v2c0 .6-.4 1-1 1H5a1 1 0 0 1-1-1V2c0-.6.4-1 1-1h2c.6 0 1 .4 1 1Zm3 5v2c0 .6-.4 1-1 1H8a1 1 0 0 1-1-1V7c0-.6.4-1 1-1h2c.6 0 1 .4 1 1Zm-8 .5V6m3-3.5V1m3 6.5V6"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" fill="none" viewBox="0 0 12 11">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.75" d="M7.5 5.5v-.3a1.7 1.7 0 1 1 3.5 0v.3"/>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.75" d="M9.3 3.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM6 2.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm-5 3v-.3a1.7 1.7 0 1 1 3.5 0v.3"/>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.75" d="M2.8 3.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M3 9.9v-.5a3 3 0 1 1 6 0v.5"/>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M6 6.4A1.7 1.7 0 1 0 6 3a1.7 1.7 0 0 0 0 3.4Z"/>
</svg>

After

Width:  |  Height:  |  Size: 831 B

View File

@@ -0,0 +1,21 @@
import { errorMsg } from "../middleware/configItems";
import { OutgoingMessage } from 'http';
export default defineEventHandler(async (event) => {
const headers: Record<string, Parameters<OutgoingMessage['setHeader']>[1]> = {
'Access-Control-Allow-Origin': 'https://tueitapp.tueit.de',
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'OPTIONS,GET,HEAD,PUT,PATCH,POST,DELETE',
};
setResponseHeaders(event, headers)
if (!(errorMsg === '')) {
throw createError({
statusCode: 400,
statusMessage: errorMsg,
})
}
setResponseStatus(event, 200)
})

95
server/api/auth/login.ts Normal file
View File

@@ -0,0 +1,95 @@
import axios, { AxiosError } from 'axios';
import serversideConfig from '../../../serversideConfig';
import https from 'https';
let errorMsg = 'error';
//const { data } = useAuthState()
export default eventHandler(async (event) => {
const agent = new https.Agent({
rejectUnauthorized: false,
});
const axiosInstance = axios.create({
headers: {
'Content-Type': 'application/json',
Accept: "*",
},
httpsAgent: agent
});
const body = await readBody(event)
// get user object from backend
try {
let res = await axiosInstance.post(`https://${serversideConfig.url}:${serversideConfig.port}/login`, {
username: body.username,
password: body.password,
});
const sessionToken = res.data.token;
const user = res.data.user;
console.log('sessionToken: ', sessionToken);
console.log('user: ', user);
setResponseStatus(event, 200);
const resBody = {
token: sessionToken,
message: 'Login successful'
};
console.log('resBody: ', resBody);
return resBody;
}
catch (err) {
if (axios.isAxiosError(err)) {
const axiosError = err as AxiosError;
if (axiosError.response) {
// Axios error
//console.error(axiosError.response.data.message);
//errorMsg = axiosError.response.data.message;
} else if (axiosError.request) {
// If error was caused by the request
console.error(axiosError.request);
} else {
// Other errors
console.error('Error', axiosError.message);
}
} else {
// No AxiosError
console.error('Error', err);
}
}
throw createError({
statusCode: 400,
statusMessage: errorMsg,
})
})
/*import { loginSuccessful, sessionToken, errorMsg } from "../../middleware/login";
import { OutgoingMessage } from 'http';
export default defineEventHandler(async (event) => {
if (!loginSuccessful) {
throw createError({
statusCode: 400,
statusMessage: errorMsg,
})
}
setResponseStatus(event, 200);
//setResponseHeader(event, "Set-Cookie", sessionToken);
const resBody = {
token: sessionToken,
message: 'Login successful'
};
return resBody;
})*/

View File

@@ -0,0 +1,50 @@
import { createError, eventHandler, readBody, sendRedirect } from 'h3';
import jwt from 'jsonwebtoken';
const SECRET = 'SECRETTUEITKEY'
interface User {
username: string;
id: string;
}
interface JwtPayload extends User {
exp: number;
}
export default eventHandler(async (event) => {
const body = await readBody<{ refreshToken: string }>(event);
if (!body.refreshToken) {
throw createError({
statusCode: 403,
statusMessage: 'Unauthorized, no refreshToken in payload'
});
};
const decoded = jwt.verify(body.refreshToken, SECRET) as JwtPayload | undefined;
if (!decoded) {
throw createError({
statusCode: 403,
statusMessage: 'Unauthorized, refreshToken can`t be verified'
});
};
// decoded.userId exists on JwtPayload, TS falsely wants decoded.id
const user: User = {
username: decoded.username,
id: decoded.userId,
};
const authToken = jwt.sign( user, SECRET, { expiresIn: 60 * 5 }); // expires in 5 min
const refreshToken = jwt.sign( user, SECRET, { expiresIn: 60 * 60 * 24 * 7 }); // expires in 7 days
return {
token: {
authToken,
refreshToken
}
};
})

View File

@@ -0,0 +1,36 @@
import { createError, eventHandler, getRequestHeader, H3Event } from 'h3'
import jwt from 'jsonwebtoken';
const TOKEN_TYPE = 'Bearer'
const extractToken = (authHeaderValue: string) => {
const [, token] = authHeaderValue.split(`${TOKEN_TYPE} `)
return token
}
const ensureAuth = (event: H3Event) => {
const authHeaderValue = getRequestHeader(event, 'authorization')
if (typeof authHeaderValue === 'undefined') {
throw createError({
statusCode: 403,
statusMessage:
'Need to pass valid Bearer-authorization header to access this endpoint'
})
}
const extractedToken = extractToken(authHeaderValue)
try {
return jwt.verify(extractedToken, 'SECRETTUEITKEY')
} catch (error) {
console.error("Login failed. Here's the raw error:", error)
throw createError({
statusCode: 403,
statusMessage: 'You must be logged in to access this page'
})
}
}
export default eventHandler((event) => {
const user = ensureAuth(event)
return user
})

View File

@@ -1,4 +1,4 @@
import { errorMsg } from "../middleware/signUp.js";
import { errorMsg } from "../../middleware/signUp.js";
export default defineEventHandler(async (event) => {

View File

@@ -0,0 +1,22 @@
import { selectedConfigItemsByAsset, errorMsg } from "../../middleware/configItems";
import { OutgoingMessage } from 'http';
export default defineEventHandler(async (event) => {
const headers: Record<string, Parameters<OutgoingMessage['setHeader']>[1]> = {
'Access-Control-Allow-Origin': 'https://tueitapp.tueit.de',
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'OPTIONS,GET,HEAD,PUT,PATCH,POST,DELETE',
};
setResponseHeaders(event, headers)
if (!(errorMsg === '')) {
throw createError({
statusCode: 400,
statusMessage: errorMsg,
})
}
setResponseStatus(event, 200)
return selectedConfigItemsByAsset
})

View File

@@ -0,0 +1,22 @@
import { selectedConfigItemsByClient, errorMsg } from "../../middleware/configItems";
import { OutgoingMessage } from 'http';
export default defineEventHandler(async (event) => {
const headers: Record<string, Parameters<OutgoingMessage['setHeader']>[1]> = {
'Access-Control-Allow-Origin': 'https://tueitapp.tueit.de',
'Access-Control-Allow-Headers': 'authorization, content-type',
'Access-Control-Allow-Methods': 'OPTIONS,GET,HEAD,PUT,PATCH,POST,DELETE',
};
setResponseHeaders(event, headers)
if (!(errorMsg === '')) {
throw createError({
statusCode: 400,
statusMessage: errorMsg,
})
}
setResponseStatus(event, 200)
return selectedConfigItemsByClient
})

View File

@@ -1,15 +0,0 @@
import { loginSuccessful, errorMsg } from "../middleware/login";
import { OutgoingMessage } from 'http';
export default defineEventHandler(async (event) => {
if (!loginSuccessful) {
throw createError({
statusCode: 400,
statusMessage: errorMsg,
})
}
setResponseStatus(event, 200)
return 'Successfully logged in.'
})

1
server/main.ts Normal file
View File

@@ -0,0 +1 @@
//import { pinia } from '@/store'

View File

@@ -5,6 +5,8 @@ import https from 'https';
let configItems = [];
let configItem = {};
let errorMsg = '';
let selectedConfigItemsByClient = [];
let selectedConfigItemsByAsset = [];
export default defineEventHandler(async (event) => {
@@ -108,6 +110,34 @@ export default defineEventHandler(async (event) => {
}
}
if (event.path.startsWith("/api/addConfigItem")) {
const body = await readBody(event)
// add the config item in the backend
try {
let res = await axiosInstance.post(`https://${serversideConfig.url}:${serversideConfig.port}/configItems`, body);
} catch (err) {
if (axios.isAxiosError(err)) {
const axiosError = err as AxiosError;
if (axiosError.response) {
// Axios error
console.error(axiosError.response.data.message);
errorMsg = axiosError.response.data.message;
} else if (axiosError.request) {
console.log(err)
// If error was caused by the request
console.error(axiosError.request);
} else {
// Other errors
console.error('Error', axiosError.message);
}
} else {
// No AxiosError
console.error('Error', err);
}
}
}
if (event.path.startsWith("/api/deleteConfigItem")) {
let itemId = null;
const path = event._path;
@@ -139,6 +169,70 @@ export default defineEventHandler(async (event) => {
}
}
}
if (event.path.startsWith("/api/getSelectedConfigItemsByClient")) {
// get selected config items object by client from backend
let filteredClient = null;
const path = event._path;
const pathSegments = path.split('/');
filteredClient = pathSegments[pathSegments.length - 1];
try {
let res = await axiosInstance.get(`https://${serversideConfig.url}:${serversideConfig.port}/selectedConfigItemsByCustomer/${filteredClient}`);
selectedConfigItemsByClient = res.data;
} catch (err) {
if (axios.isAxiosError(err)) {
const axiosError = err as AxiosError;
if (axiosError.response) {
// Axios error
console.error(axiosError.response.data.message);
errorMsg = axiosError.response.data.message;
} else if (axiosError.request) {
// If error was caused by the request
console.error(axiosError.request);
} else {
// Other errors
console.error('Error', axiosError.message);
}
} else {
// No AxiosError
console.error('Error', err);
}
}
}
if (event.path.startsWith("/api/getSelectedConfigItemsByAsset")) {
// get selected config items object by asset from backend
let filteredAsset = null;
const path = event._path;
const pathSegments = path.split('/');
filteredAsset = pathSegments[pathSegments.length - 1];
try {
let res = await axiosInstance.get(`https://${serversideConfig.url}:${serversideConfig.port}/selectedConfigItemsByAssetName/${filteredAsset}`);
selectedConfigItemsByAsset = res.data;
} catch (err) {
if (axios.isAxiosError(err)) {
const axiosError = err as AxiosError;
if (axiosError.response) {
// Axios error
console.error(axiosError.response.data.message);
errorMsg = axiosError.response.data.message;
} else if (axiosError.request) {
// If error was caused by the request
console.error(axiosError.request);
} else {
// Other errors
console.error('Error', axiosError.message);
}
} else {
// No AxiosError
console.error('Error', err);
}
}
}
})
export { configItems, configItem, errorMsg };
export { configItems, configItem, selectedConfigItemsByClient, selectedConfigItemsByAsset, errorMsg };

View File

@@ -1,56 +0,0 @@
import axios, { AxiosError } from 'axios';
import serversideConfig from '../../serversideConfig';
import https from 'https';
let loginSuccessful = false;
let errorMsg = '';
export default defineEventHandler(async (event) => {
loginSuccessful = false;
const agent = new https.Agent({
rejectUnauthorized: false,
});
const axiosInstance = axios.create({
headers: {
'Content-Type': 'application/json',
Accept: "*",
},
httpsAgent: agent
});
if (event.path.startsWith("/api/login")) {
const body = await readBody(event)
// get user object from backend
try {
let res = await axiosInstance.post(`https://${serversideConfig.url}:${serversideConfig.port}/login`, {
username: body.username,
password: body.password,
});
loginSuccessful = true;
} catch (err) {
if (axios.isAxiosError(err)) {
const axiosError = err as AxiosError;
if (axiosError.response) {
// Axios error
console.error(axiosError.response.data.message);
errorMsg = axiosError.response.data.message;
} else if (axiosError.request) {
// If error was caused by the request
console.error(axiosError.request);
} else {
// Other errors
console.error('Error', axiosError.message);
}
} else {
// No AxiosError
console.error('Error', err);
}
}
}
})
export { loginSuccessful, errorMsg };

6
store/index.ts Normal file
View File

@@ -0,0 +1,6 @@
import { createPinia } from "pinia";
import piniaPluginPersistedState from "pinia-plugin-persistedstate"
const pinia = createPinia().use(piniaPluginPersistedState);
useNuxtApp().vueApp.use(pinia);