Initial commit

This commit is contained in:
Donny 2024-08-30 08:25:26 -06:00
commit d892b38317
37 changed files with 7395 additions and 0 deletions

13
.eslintignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

31
.eslintrc.cjs Normal file
View File

@ -0,0 +1,31 @@
/** @type { import("eslint").Linter.Config } */
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

120
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,120 @@
{
"prettier.documentSelectors": [
"**/*.svelte"
],
"tailwindCSS.classAttributes": [
"class",
"accent",
"active",
"animIndeterminate",
"aspectRatio",
"background",
"badge",
"bgBackdrop",
"bgDark",
"bgDrawer",
"bgLight",
"blur",
"border",
"button",
"buttonAction",
"buttonBack",
"buttonClasses",
"buttonComplete",
"buttonDismiss",
"buttonNeutral",
"buttonNext",
"buttonPositive",
"buttonTextCancel",
"buttonTextConfirm",
"buttonTextFirst",
"buttonTextLast",
"buttonTextNext",
"buttonTextPrevious",
"buttonTextSubmit",
"caretClosed",
"caretOpen",
"chips",
"color",
"controlSeparator",
"controlVariant",
"cursor",
"display",
"element",
"fill",
"fillDark",
"fillLight",
"flex",
"flexDirection",
"gap",
"gridColumns",
"height",
"hover",
"inactive",
"indent",
"justify",
"meter",
"padding",
"position",
"regionAnchor",
"regionBackdrop",
"regionBody",
"regionCaption",
"regionCaret",
"regionCell",
"regionChildren",
"regionChipList",
"regionChipWrapper",
"regionCone",
"regionContent",
"regionControl",
"regionDefault",
"regionDrawer",
"regionFoot",
"regionFootCell",
"regionFooter",
"regionHead",
"regionHeadCell",
"regionHeader",
"regionIcon",
"regionInput",
"regionInterface",
"regionInterfaceText",
"regionLabel",
"regionLead",
"regionLegend",
"regionList",
"regionListItem",
"regionNavigation",
"regionPage",
"regionPanel",
"regionRowHeadline",
"regionRowMain",
"regionSummary",
"regionSymbol",
"regionTab",
"regionTrail",
"ring",
"rounded",
"select",
"shadow",
"slotDefault",
"slotFooter",
"slotHeader",
"slotLead",
"slotMessage",
"slotMeta",
"slotPageContent",
"slotPageFooter",
"slotPageHeader",
"slotSidebarLeft",
"slotSidebarRight",
"slotTrail",
"spacing",
"text",
"track",
"transition",
"width",
"zIndex"
]
}

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM node:latest
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY ./Frontend .
EXPOSE 3000
CMD [ "node", "build" ]

3
README.md Normal file
View File

@ -0,0 +1,3 @@
## MPE License Tracker
This project was meant to help employees quickly see who is using software licenses. This hopefully alleviates strain on upper management so they never get the question "Who is using x software right now?

6711
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

48
package.json Normal file
View File

@ -0,0 +1,48 @@
{
"name": "",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@skeletonlabs/skeleton": "2.10.0",
"@skeletonlabs/tw-plugin": "0.4.0",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@tailwindcss/forms": "0.5.7",
"@tailwindcss/typography": "0.5.13",
"@types/eslint": "8.56.0",
"@types/node": "20.12.13",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "10.4.19",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"postcss": "8.4.38",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"tailwindcss": "3.4.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3",
"vite-plugin-tailwind-purgecss": "0.3.3"
},
"type": "module",
"dependencies": {
"@unovis/svelte": "^1.4.1",
"@unovis/ts": "^1.4.1",
"chart.js": "^4.4.3"
}
}

6
postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

9
src/app.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface PageData {}
// interface Error {}
// interface Platform {}
}

12
src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en" class="light">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" data-theme="mpe_theme">
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
</body>
</html>

9
src/app.postcss Normal file
View File

@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
html,
body {
@apply h-full overflow-hidden;
}

1
src/lib/index.ts Normal file
View File

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

101
src/mpe_theme.ts Normal file
View File

@ -0,0 +1,101 @@
// You can also use the generator at https://skeleton.dev/docs/generator to create these values for you
import type { CustomThemeConfig } from '@skeletonlabs/tw-plugin';
export const mpe_theme: CustomThemeConfig = {
name: 'mpe_theme',
properties: {
// =~= Theme Properties =~=
"--theme-font-family-base": "system-ui",
"--theme-font-family-heading": "system-ui",
"--theme-font-color-base": "0 0 0",
"--theme-font-color-dark": "255 255 255",
"--theme-rounded-base": "9999px",
"--theme-rounded-container": "8px",
"--theme-border-base": "1px",
// =~= Theme On-X Colors =~=
"--on-primary": "0 0 0",
"--on-secondary": "255 255 255",
"--on-tertiary": "0 0 0",
"--on-success": "0 0 0",
"--on-warning": "0 0 0",
"--on-error": "255 255 255",
"--on-surface": "255 255 255",
// =~= Theme Colors =~=
// primary | #0FBA81
"--color-primary-50": "219 245 236", // #dbf5ec
"--color-primary-100": "207 241 230", // #cff1e6
"--color-primary-200": "195 238 224", // #c3eee0
"--color-primary-300": "159 227 205", // #9fe3cd
"--color-primary-400": "87 207 167", // #57cfa7
"--color-primary-500": "36 88 164", // #0FBA81
"--color-primary-600": "14 167 116", // #0ea774
"--color-primary-700": "11 140 97", // #0b8c61
"--color-primary-800": "9 112 77", // #09704d
"--color-primary-900": "7 91 63", // #075b3f
// secondary | #4F46E5
"--color-secondary-50": "229 227 251", // #e5e3fb
"--color-secondary-100": "220 218 250", // #dcdafa
"--color-secondary-200": "211 209 249", // #d3d1f9
"--color-secondary-300": "185 181 245", // #b9b5f5
"--color-secondary-400": "132 126 237", // #847eed
"--color-secondary-500": "79 70 229", // #4F46E5
"--color-secondary-600": "71 63 206", // #473fce
"--color-secondary-700": "59 53 172", // #3b35ac
"--color-secondary-800": "47 42 137", // #2f2a89
"--color-secondary-900": "39 34 112", // #272270
// tertiary | #0EA5E9
"--color-tertiary-50": "219 242 252", // #dbf2fc
"--color-tertiary-100": "207 237 251", // #cfedfb
"--color-tertiary-200": "195 233 250", // #c3e9fa
"--color-tertiary-300": "159 219 246", // #9fdbf6
"--color-tertiary-400": "86 192 240", // #56c0f0
"--color-tertiary-500": "14 165 233", // #0EA5E9
"--color-tertiary-600": "13 149 210", // #0d95d2
"--color-tertiary-700": "11 124 175", // #0b7caf
"--color-tertiary-800": "8 99 140", // #08638c
"--color-tertiary-900": "7 81 114", // #075172
// success | #84cc16
"--color-success-50": "237 247 220", // #edf7dc
"--color-success-100": "230 245 208", // #e6f5d0
"--color-success-200": "224 242 197", // #e0f2c5
"--color-success-300": "206 235 162", // #ceeba2
"--color-success-400": "169 219 92", // #a9db5c
"--color-success-500": "132 204 22", // #84cc16
"--color-success-600": "119 184 20", // #77b814
"--color-success-700": "99 153 17", // #639911
"--color-success-800": "79 122 13", // #4f7a0d
"--color-success-900": "65 100 11", // #41640b
// warning | #EAB308
"--color-warning-50": "252 244 218", // #fcf4da
"--color-warning-100": "251 240 206", // #fbf0ce
"--color-warning-200": "250 236 193", // #faecc1
"--color-warning-300": "247 225 156", // #f7e19c
"--color-warning-400": "240 202 82", // #f0ca52
"--color-warning-500": "234 179 8", // #EAB308
"--color-warning-600": "211 161 7", // #d3a107
"--color-warning-700": "176 134 6", // #b08606
"--color-warning-800": "140 107 5", // #8c6b05
"--color-warning-900": "115 88 4", // #735804
// error | #D41976
"--color-error-50": "249 221 234", // #f9ddea
"--color-error-100": "246 209 228", // #f6d1e4
"--color-error-200": "244 198 221", // #f4c6dd
"--color-error-300": "238 163 200", // #eea3c8
"--color-error-400": "225 94 159", // #e15e9f
"--color-error-500": "212 25 118", // #D41976
"--color-error-600": "191 23 106", // #bf176a
"--color-error-700": "159 19 89", // #9f1359
"--color-error-800": "127 15 71", // #7f0f47
"--color-error-900": "104 12 58", // #680c3a
// surface | #495a8f
"--color-surface-50": "228 230 238", // #e4e6ee
"--color-surface-100": "36 88 164", // #dbdee9
"--color-surface-200": "210 214 227", // #d2d6e3
"--color-surface-300": "182 189 210", // #b6bdd2
"--color-surface-400": "128 140 177", // #808cb1
"--color-surface-500": "73 90 143", // #495a8f
"--color-surface-600": "66 81 129", // #425181
"--color-surface-700": "55 68 107", // #37446b
"--color-surface-800": "44 54 86", // #2c3656
"--color-surface-900": "36 44 70", // #242c46
}
}

21
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,21 @@
<script lang="ts">
import '../app.postcss';
import { AppShell, AppBar } from '@skeletonlabs/skeleton';
</script>
<!-- App Shell -->
<AppShell class="bg-white">
<svelte:fragment slot="header">
<!-- App Bar -->
<AppBar class="bg-blue-500">
<svelte:fragment slot="lead">
<img class="max-w-36 mr-5 ml-4" src="mpe_logo.png" alt="MPE Logo">
</svelte:fragment>
<svelte:fragment slot="trail">
</svelte:fragment>
</AppBar>
</svelte:fragment>
<!-- Page Route Content -->
<slot />
</AppShell>

218
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,218 @@
<!--apilicenses.mpe.ca/api/licenses-->
<script>
import { onMount } from 'svelte';
import { createEventDispatcher } from 'svelte';
let homeBase = "https://apilicenses.mpe.ca";
console.log("Made by: Donavon McDowell");
let licenses = [];
// This is how often the web interface will update
let webPollRate = 10000; // In ms
//This is for the license count function
let programName = '';
let RS2 = 0;
const RS2Seats = 1;
let Slide2 = 0;
const Slide2Seats = 3;
let Slide3 = 0;
const Slide3Seats = 1;
let error = '';
const fetchLicenses = async () => {
try {
const response = await fetch(homeBase + '/api/licenses', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
licenses = data;
} catch (err) {
error = `Failed to fetch licenses: ${err.message}`;
}
};
// Function to call the API
async function getLicenseCount(programName) {
let count = 0;
try {
// Clear previous error
error = '';
// Make the POST request to the API
const response = await fetch(homeBase + '/api/licenseCount', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ programName })
});
const data = await response.json();
if (response.ok) {
// Update the count with the response data
count = data.count;
} else {
// Handle errors
error = data.error || 'Unknown error occurred';
}
} catch (err) {
// Handle network or other errors
error = 'Failed to fetch data: ' + err.message;
}
return count;
}
// Schedule the function to run every 10 seconds
setInterval(fetchLicenses, webPollRate);
setInterval(getCounts, webPollRate);
onMount(() => {
fetchLicenses();
getCounts()
});
// Utility function to determine availability
function isAvailable(usedSeats, maxSeats) {
return usedSeats < maxSeats;
}
async function getCounts(){
RS2 = await getLicenseCount("RS2");
Slide2 = await getLicenseCount("Slide");
Slide3 = await getLicenseCount("Slide3");
}
// This function changes the images
function getImageSource(program) {
switch (program) {
case 'RS2':
return 'RS2.png';
case 'Slide':
return 'Slide2.png';
case 'Slide3':
return 'Slide3.png';
case 'chrome':
return 'Chrome.png';
case 'WINPROJ':
return 'Project.png';
case 'SewerCad':
return 'flowmaster&sewercad.png';
case 'Settle3':
return 'Settle3.png';
case 'CulvertMaster':
return 'culvertmaster.png';
case 'FlowMaster':
return 'flowmaster&sewercad.png';
case 'SewerGEMS':
return 'sewergems.png';
case 'WaterCAD':
return 'watercad.png';
case 'VirtualSurveyor':
return 'virtual_surveyor.png';
default:
return 'mpe_logo.png'; // Default image if program name doesn't match
}
}
</script>
<title>MPE | Licenses</title>
<div class="flex flex-col md:flex-col lg:flex-row justify-center p-4">
<!--Start of software card -->
<div class="card shadow-xl md:w-4/5 lg:w-1/2 mt-3 p-8 flex flex-row m-2 hover:bg-gray-400 bg-gray-300">
<div class="w-1/2 flex flex-row">
<img class="max-w-20" src="RS2.png" alt="RS2 Logo">
<div class="flex flex-col ml-5">
<h2 class="text-3xl">RS2</h2>
{#if isAvailable(RS2, RS2Seats)}
<h2 class="text-xl mt-4 text-green-500">Available</h2>
{:else}
<h2 class="text-xl mt-4 text-red-500">Not Available</h2>
{/if}
</div>
</div>
<div class="w-1/2 flex flex-row-reverse">
<div class="flex flex-col">
<h2 class="text-2xl">Seats</h2>
<h2 class="text-2xl mt-4">{RS2}/{RS2Seats}</h2>
</div>
</div>
</div>
<!-- End of software card -->
<!--Start of software card -->
<div class="card shadow-xl md:w-4/5 lg:w-1/2 mt-3 p-8 flex flex-row m-2 hover:bg-gray-400 bg-gray-300">
<div class="w-1/2 flex flex-row">
<img class="max-w-20" src="Slide2.png" alt="slide2 Logo">
<div class="flex flex-col ml-5">
<h2 class="text-3xl">Slide2</h2>
{#if isAvailable(Slide2, Slide2Seats)}
<h2 class="text-xl mt-4 text-green-500">Available</h2>
{:else}
<h2 class="text-xl mt-4 text-red-500">Not Available</h2>
{/if}
</div>
</div>
<div class="w-1/2 flex flex-row-reverse">
<div class="flex flex-col">
<h2 class="text-2xl">Seats</h2>
<h2 class="text-2xl mt-4">{Slide2}/{Slide2Seats}</h2>
</div>
</div>
</div>
<!-- End of software card -->
<!--Start of software card -->
<div class="card shadow-xl md:w-4/5 lg:w-1/2 mt-3 p-8 flex flex-row m-2 hover:bg-gray-400 bg-gray-300">
<div class="w-1/2 flex flex-row">
<img class="max-w-20" src="Slide3.png" alt="slide2 Logo">
<div class="flex flex-col ml-5">
<h2 class="text-3xl">Slide3</h2>
{#if isAvailable(Slide3, Slide3Seats)}
<h2 class="text-xl mt-4 text-green-500">Available</h2>
{:else}
<h2 class="text-xl mt-4 text-red-500">Not Available</h2>
{/if}
</div>
</div>
<div class="w-1/2 flex flex-row-reverse">
<div class="flex flex-col">
<h2 class="text-2xl">Seats</h2>
<h2 class="text-2xl mt-4">{Slide3}/{Slide3Seats}</h2>
</div>
</div>
</div>
<!-- End of software card -->
</div>
<!-- Container for the license cards -->
<div class="flex flex-col justify-center text-center">
<h2 class="text-3xl">Currently using licenses</h2>
{#each licenses as license}
<div class="card shadow-xl m-auto w-5/6 sm:w-5/6 md:w-4/5 lg:w-1/2 mt-3 p-2 flex flex-row hover:bg-gray-400 bg-gray-300">
<div class="w-1/2 flex flex-row">
<img class="max-w-7" src={getImageSource(license.program)} alt={license.machine + " Logo"}>
<div class="flex flex-col ml-5 justify-center">
<h2 class="text-lg">{license.program === 'Slide' ? 'Slide2' : (license.program === 'WINPROJ' ? 'Project' : license.program)}</h2>
</div>
</div>
<div class="w-1/2 flex flex-row-reverse">
<div class="flex flex-col justify-center">
<h3 class="mr-1">{license.machine}</h3>
</div>
</div>
</div>
{/each}
</div>

BIN
static/Chrome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
static/Project.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
static/RS2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
static/RS3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
static/Settle3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
static/Slide2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/Slide3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
static/culvertmaster.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
static/mpe_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
static/sewergems.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
static/virtual_surveyor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
static/watercad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

7
svelte.config.js Normal file
View File

@ -0,0 +1,7 @@
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter()
}
};

25
tailwind.config.ts Normal file
View File

@ -0,0 +1,25 @@
import { join } from 'path'
import type { Config } from 'tailwindcss'
import forms from '@tailwindcss/forms';
import typography from '@tailwindcss/typography';
import { skeleton } from '@skeletonlabs/tw-plugin';
import { mpe_theme } from './src/mpe_theme'
export default {
darkMode: 'class',
content: ['./src/**/*.{html,js,svelte,ts}', join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')],
theme: {
extend: {},
},
plugins: [
forms,
typography,
skeleton({
themes: {
custom: [
mpe_theme,
],
},
}),
],
} satisfies Config;

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

7
vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit(), purgeCss()]
});