Initial Commit
This commit is contained in:
commit
d56291e561
29 changed files with 4407 additions and 0 deletions
.eslintignore.eslintrc.cjs.gitignore
.idea
.npmrcREADME.mdpackage-lock.jsonpackage.jsonpostcss.config.jssrc
static
svelte.config.jstailwind.config.jsvite.config.js
13
.eslintignore
Normal file
13
.eslintignore
Normal 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
|
14
.eslintrc.cjs
Normal file
14
.eslintrc.cjs
Normal file
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: ['eslint:recommended', 'plugin:svelte/recommended'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
}
|
||||
};
|
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.idea
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/printer-manager-fe.iml" filepath="$PROJECT_DIR$/.idea/printer-manager-fe.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
19
.idea/php.xml
Normal file
19
.idea/php.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
8
.idea/printer-manager-fe.iml
Normal file
8
.idea/printer-manager-fe.iml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
engine-strict=true
|
38
README.md
Normal file
38
README.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
3410
package-lock.json
generated
Normal file
3410
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
28
package.json
Normal file
28
package.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "printer-manager-fe",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-plugin-svelte": "^2.30.0",
|
||||
"flowbite": "^1.8.1",
|
||||
"flowbite-svelte": "^0.44.18",
|
||||
"postcss": "^8.4.31",
|
||||
"svelte": "^4.0.5",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"vite": "^4.4.2"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.1"
|
||||
}
|
||||
}
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
13
src/app.css
Normal file
13
src/app.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Sometype+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
body {
|
||||
background-color: #121212;
|
||||
font-family: 'Sometype Mono', monospace;
|
||||
/*font-family: 'Roboto', sans-serif;*/
|
||||
}
|
||||
|
13
src/app.html
Normal file
13
src/app.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
8
src/components/icon.svelte
Normal file
8
src/components/icon.svelte
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div>
|
||||
<i class="uil uil-{name}"/>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
export let name = 'home';
|
||||
</script>
|
16
src/components/loadable-content.svelte
Normal file
16
src/components/loadable-content.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<div>
|
||||
{#if isLoading}
|
||||
<div class="mx-auto justify-center flex text-center my-16">
|
||||
<Loader loading="{true}"/>
|
||||
</div>
|
||||
{:else}
|
||||
<slot/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
import Loader from './Loader.svelte';
|
||||
|
||||
export let isLoading = false;
|
||||
</script>
|
22
src/components/loader.svelte
Normal file
22
src/components/loader.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
<div>
|
||||
{#if loading}
|
||||
<div>
|
||||
<div role="status">
|
||||
<svg aria-hidden="true"
|
||||
class="w-8 h-8 mr-2 text-gray-400 animate-spin dark:text-gray-600 fill-primary-700"
|
||||
viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"/>
|
||||
</svg>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
export let loading = false
|
||||
</script>
|
127
src/helpers/backend.js
Normal file
127
src/helpers/backend.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
import axios from "axios";
|
||||
|
||||
axios.defaults.baseURL = "http://localhost:8000"
|
||||
axios.defaults.headers.post["Content-Type"] = "application/json";
|
||||
axios.interceptors.request.use((request) => {
|
||||
return request;
|
||||
});
|
||||
|
||||
axios.interceptors.response.use((response) => {
|
||||
return response;
|
||||
}, function (error) {
|
||||
|
||||
if (!error.response) {
|
||||
//unable to reach server
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (error.response.status >= 500 && error.response.status <= 599) {
|
||||
// server error
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
//Invalid key logout!
|
||||
if (error.response.status === 401) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (error.response.status === 403) {
|
||||
return Promise.reject(error);
|
||||
// no permission
|
||||
}
|
||||
|
||||
if (error.response.status === 422) {
|
||||
console.log('validation error', error);
|
||||
}
|
||||
|
||||
if (error.response.status === 404) {
|
||||
console.log('not found');
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
// const setAuthorization = (access_token) => {
|
||||
// if (access_token) {
|
||||
// axios.defaults.headers.common["Authorization"] = "Bearer " + access_token;
|
||||
// }
|
||||
// };
|
||||
|
||||
export function getValueByDot(obj, target) {
|
||||
const arr = target.split('.');
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (obj)
|
||||
obj = obj[arr[i]];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function buildUrl(url) {
|
||||
|
||||
return "/api"+ url;
|
||||
// setAuthorization(user?.access_token);
|
||||
}
|
||||
|
||||
|
||||
export async function getData(url, params) {
|
||||
let response;
|
||||
let paramKeys = [];
|
||||
|
||||
url = buildUrl(url);
|
||||
if (params) {
|
||||
Object.keys(params).map(key => {
|
||||
if (params[key] !== undefined && params[key] !== null) {
|
||||
paramKeys.push(key + '=' + params[key])
|
||||
}
|
||||
return paramKeys;
|
||||
});
|
||||
|
||||
const queryString = paramKeys && paramKeys.length ? paramKeys.join('&') : "";
|
||||
url = url + '?' + queryString;
|
||||
}
|
||||
|
||||
// const cachedData = cache.get(url);
|
||||
// if(cachedData){
|
||||
// return cachedData;
|
||||
// }
|
||||
|
||||
response = axios.get(url);
|
||||
// cache.rememberResponse(url, response, 1);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function postData(url, data, params) {
|
||||
let paramKeys = [];
|
||||
url = buildUrl(url)
|
||||
if (params) {
|
||||
Object.keys(params).map(key => {
|
||||
if (params[key]) {
|
||||
paramKeys.push(key + '=' + params[key])
|
||||
}
|
||||
return paramKeys;
|
||||
});
|
||||
|
||||
const queryString = paramKeys && paramKeys.length ? paramKeys.join('&') : "";
|
||||
url = url + '?' + queryString;
|
||||
}
|
||||
console.log({url, data})
|
||||
return await axios.post(url, data);
|
||||
}
|
||||
|
||||
|
||||
export async function putData(url, data) {
|
||||
return axios.put(buildUrl(url), data);
|
||||
}
|
||||
|
||||
export async function deleteData(url) {
|
||||
return axios.delete(buildUrl(url));
|
||||
}
|
||||
|
||||
|
||||
export function handleResponse(response) {
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
return response.data?.data || response.data;
|
||||
}
|
||||
return response.data?.data || response.data;
|
||||
}
|
1
src/lib/index.js
Normal file
1
src/lib/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
21
src/routes/+layout.svelte
Normal file
21
src/routes/+layout.svelte
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!-- src/routes/__layout.svelte -->
|
||||
<script>
|
||||
import "../app.css";
|
||||
|
||||
let isDarkMode = false;
|
||||
|
||||
function toggleDarkMode() {
|
||||
isDarkMode = !isDarkMode;
|
||||
|
||||
// Add or remove the 'dark-mode' class from the <html> or <body> element
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark-mode');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark-mode');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<slot/>
|
||||
</main>
|
139
src/routes/+page.svelte
Normal file
139
src/routes/+page.svelte
Normal file
|
@ -0,0 +1,139 @@
|
|||
<div class="max-w-7xl mx-auto px-8">
|
||||
|
||||
<h4 class="text-primary-700 text-xl font-bold mt-16 ">
|
||||
Welcome {user.name}
|
||||
</h4>
|
||||
|
||||
<h4 class="text-primary-700 font-bold mt-4 ">
|
||||
<span>Select a printer to continue</span>
|
||||
</h4>
|
||||
|
||||
<LoadableContent isLoading="{loadingPrinters}">
|
||||
<div class="mx-auto">
|
||||
<div class="mt-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
{#each printers as printer}
|
||||
<div class="bg-dark-light p-4 rounded-md w-full">
|
||||
<div class="text-lg font-bold text-center text-white">
|
||||
<div class="flex justify-center items-center flex-col">
|
||||
<p>{printer.name}</p>
|
||||
<div class="mt-1">
|
||||
<div class="flex flex-col gap-1 text-stone-300 items-center text-sm">
|
||||
<Badge color="{getPrinterLogs(printer).color}" rounded
|
||||
class="px-2.5 py-0.5">
|
||||
<Indicator color="{getPrinterLogs(printer).color}" size="xs"
|
||||
class="mr-1"/> {printer?.logs?.state?.text ?? 'unkown'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{#if printer?.logs?.temperature}
|
||||
<div class="flex gap-2 justify-center mt-3">
|
||||
{#each Object.entries(printer?.logs?.temperature) as [key, temperature]}
|
||||
<div class="flex justify-center text-xs bg-blue-200 text-blue-500 py-0.5 px-2 rounded-full">
|
||||
{key}
|
||||
{temperature?.actual}°C
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if printer?.logs?.display_logs}
|
||||
<div class="mt-4 text-left text-xs">
|
||||
<pre class="text-gray-400">{JSON.stringify(printer.logs, null, 2)}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center gap-4 mt-4">
|
||||
<Button color="primary" size="xs" href="/printers/{printer.id}">Select</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LoadableContent>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import {Button, Indicator, Badge} from 'flowbite-svelte';
|
||||
import LoadableContent from "../components/loadable-content.svelte";
|
||||
import Icon from "../components/icon.svelte";
|
||||
import {onDestroy, onMount} from "svelte";
|
||||
import printerService from "../services/printer/printer.service.js";
|
||||
import printerDriverService from "../services/printer/printer-driver.service.js";
|
||||
|
||||
onMount(() => {
|
||||
fetchPrinters()
|
||||
})
|
||||
|
||||
let printers = []
|
||||
let loadingPrinters = true
|
||||
|
||||
const user = {name: 'John Doe'}
|
||||
|
||||
let clear
|
||||
$: {
|
||||
clearInterval(clear)
|
||||
clear = setInterval(getAllPrinterStatus, 2000)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(clear);
|
||||
});
|
||||
async function fetchPrinters() {
|
||||
loadingPrinters = true
|
||||
await printerService.index().then((response) => {
|
||||
printers = response.data.data
|
||||
getAllPrinterStatus()
|
||||
loadingPrinters = false
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
loadingPrinters = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async function getAllPrinterStatus() {
|
||||
console.log('FOMR HIEROOO')
|
||||
printers.forEach((printer) => {
|
||||
getPrinterStatus(printer)
|
||||
})
|
||||
}
|
||||
|
||||
async function getPrinterStatus(printer) {
|
||||
await printerDriverService.getStatus(printer).then(res => {
|
||||
printer = {...printer, logs: res, display_logs: false}; // Using spread operator to create a new object
|
||||
const index = printers.findIndex(p => p.name === printer.name);
|
||||
if (index !== -1) {
|
||||
printers[index] = printer;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getPrinterColor(state) {
|
||||
if (state?.paused || state?.printing || state?.pausing) {
|
||||
return 'yellow'
|
||||
}
|
||||
|
||||
if (state?.operational || state?.ready) {
|
||||
return 'green'
|
||||
}
|
||||
|
||||
if (state?.cancelling || state?.error) {
|
||||
return 'red'
|
||||
}
|
||||
|
||||
|
||||
return 'gray'
|
||||
}
|
||||
|
||||
function getPrinterLogs(printer) {
|
||||
const color = getPrinterColor(printer?.logs?.state?.flags)
|
||||
|
||||
return {
|
||||
color: color,
|
||||
}
|
||||
}
|
||||
</script>
|
326
src/routes/printers/[id]/+page.svelte
Normal file
326
src/routes/printers/[id]/+page.svelte
Normal file
|
@ -0,0 +1,326 @@
|
|||
<div class="max-w-7xl mx-auto px-8">
|
||||
<div class="flex justify-between items-center mt-16">
|
||||
<h4 class="text-primary-700 text-xl font-bold">
|
||||
Printing as <span class="font-bold">{user.name}</span>
|
||||
<p class="text-xs text-gray-500">{user.user_id}</p>
|
||||
</h4>
|
||||
<Button color="primary" size="xs" href="/">
|
||||
<Icon name="arrow-left"/>
|
||||
All Printers
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<LoadableContent isLoading="{loading}">
|
||||
<div class="mx-auto">
|
||||
<div class="mt-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 h-full gap-4">
|
||||
<div class="col-span-1 space-y-4">
|
||||
<div class="bg-dark-light p-4 rounded-md w-full">
|
||||
<div class="text-lg font-bold text-center text-white">
|
||||
<div class="flex justify-center items-center flex-col">
|
||||
<p>{printer.name}</p>
|
||||
<div class="mt-1">
|
||||
<div class="flex flex-col gap-1 text-stone-300 items-center text-sm">
|
||||
<Badge color="{getPrinterLogs(printer).color}" rounded
|
||||
class="px-2.5 py-0.5">
|
||||
<Indicator color="{getPrinterLogs(printer).color}" size="xs"
|
||||
class="mr-1"/> {printer?.logs?.state?.text ?? 'unkown'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{#if printer?.logs?.temperature}
|
||||
<div class="flex gap-2 justify-center mt-3">
|
||||
{#each Object.entries(printer?.logs?.temperature) as [key, temperature]}
|
||||
<div class="flex justify-center text-xs bg-blue-200 text-blue-500 py-0.5 px-2 rounded-full">
|
||||
{key}
|
||||
{temperature?.actual}°C
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if printer?.logs?.display_logs}
|
||||
<div class="mt-4 text-left text-xs">
|
||||
<pre class="text-gray-400">{JSON.stringify(printer.logs, null, 2)}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-dark-light p-4 rounded-md w-full">
|
||||
<div class="text-lg font-bold text-center text-white">
|
||||
<div class="flex justify-center items-center flex-col">
|
||||
<p>Queue Status</p>
|
||||
<div class="mt-1">
|
||||
<div class="flex flex-col gap-1 text-stone-300 items-center text-sm">
|
||||
<!-- <Badge color="{getPrinterLogs(printer).color}" rounded-->
|
||||
<!-- class="px-2.5 py-0.5">-->
|
||||
<!-- <Indicator color="{getPrinterLogs(printer).color}" size="xs"-->
|
||||
<!-- class="mr-1"/> {printer?.logs?.state?.text ?? 'unkown'}-->
|
||||
<!-- </Badge>-->
|
||||
{printerData?.queueData?.queue_state}
|
||||
</div>
|
||||
|
||||
{#if printerData?.queueData?.queued_jobs?.length}
|
||||
<div class="flex gap-2 flex-col mt-3">
|
||||
{#each printerData?.queueData?.queued_jobs as queuedJob}
|
||||
<div class="flex justify-center flex-col text-xs bg-gray-700 p-2 rounded-lg">
|
||||
<p>{queuedJob.filename}</p>
|
||||
<p class="text-[9px]">{queuedJob.job_id}</p>
|
||||
<div class="text-[9px]">
|
||||
<p>Added At: {formatDate(queuedJob.time_added)}</p>
|
||||
<p>In queue: {formatTimeAgo(queuedJob.time_added)}</p>
|
||||
</div>
|
||||
<button on:click={deleteJobFromQueue(queuedJob.job_id)}>
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center gap-4 mt-8">
|
||||
<Button color="red" size="xs" on:click={clearPrinterQueue}>
|
||||
<Icon name="times"/>
|
||||
<span class="ml-1">Clear queue</span>
|
||||
</Button>
|
||||
<Button color="green" size="xs" href="/">
|
||||
<Icon name="play"/>
|
||||
<span class="ml-1">Start queue</span>
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 h-auto">
|
||||
<div class="bg-dark-light p-4 rounded-md w-full">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-primary-600">
|
||||
Files
|
||||
</div>
|
||||
<div>
|
||||
<Button color="primary" size="xs" href="/">
|
||||
<Icon name="upload"/>
|
||||
<span class="ml-1">Upload file</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-">
|
||||
{#if printerData?.files?.length}
|
||||
<TableSearch color="gray" placeholder="Search by file name" hoverable={true}
|
||||
bind:inputValue={searchTerm}>
|
||||
<TableHead>
|
||||
<TableHeadCell>Filename</TableHeadCell>
|
||||
<TableHeadCell></TableHeadCell>
|
||||
</TableHead>
|
||||
<TableBody class="divide-y">
|
||||
{#each filteredItems as file}
|
||||
<TableBodyRow>
|
||||
<TableBodyCell class="w-[20px]">
|
||||
<div>
|
||||
{file.path}
|
||||
<p class="text-gray-400 text-xs">{formatBytes(file.size)}
|
||||
- {formatDate(file.modified)}</p>
|
||||
</div>
|
||||
</TableBodyCell>
|
||||
<TableBodyCell>
|
||||
<Button color="primary" size="xs"
|
||||
on:click={putInQueue(file.path)}>
|
||||
<Icon name="print"/>
|
||||
</Button>
|
||||
</TableBodyCell>
|
||||
</TableBodyRow>
|
||||
{/each}
|
||||
|
||||
</TableBody>
|
||||
</TableSearch>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LoadableContent>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableBodyCell,
|
||||
TableBodyRow,
|
||||
TableHead,
|
||||
TableHeadCell,
|
||||
Checkbox,
|
||||
TableSearch
|
||||
} from 'flowbite-svelte';
|
||||
import {Button, Indicator, Badge} from 'flowbite-svelte';
|
||||
import LoadableContent from "../../../components/loadable-content.svelte";
|
||||
import Icon from "../../../components/icon.svelte";
|
||||
import {onDestroy, onMount} from "svelte";
|
||||
import printerService from "../../../services/printer/printer.service.js";
|
||||
import printerDriverService from "../../../services/printer/printer-driver.service.js";
|
||||
|
||||
import {page} from '$app/stores';
|
||||
|
||||
const id = $page.params.id;
|
||||
|
||||
onMount(() => {
|
||||
fetchPrinter(id)
|
||||
})
|
||||
|
||||
let printer = {}
|
||||
let printerData = {}
|
||||
let loading = true
|
||||
let searchTerm = '';
|
||||
$: filteredItems = printerData?.files?.filter((item) => item.path.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1);
|
||||
|
||||
const user = {name: 'John Doe', user_id: 'dnh-jabir-from-pmanager'}
|
||||
|
||||
let clear
|
||||
$: {
|
||||
clearInterval(clear)
|
||||
clear = setInterval(getPrinterStatus, 10000)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(clear);
|
||||
});
|
||||
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
if (!+bytes) return '0 Bytes'
|
||||
|
||||
const k = 1024
|
||||
const dm = decimals < 0 ? 0 : decimals
|
||||
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
async function putInQueue(file) {
|
||||
await printerDriverService.postPrinterQueue(printer, file).then((response) => {
|
||||
getPrinterQueue()
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
loading = false
|
||||
})
|
||||
}
|
||||
|
||||
async function clearPrinterQueue(){
|
||||
await printerDriverService.clearQueue(printer).then((response) => {
|
||||
getPrinterQueue()
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
loading = false
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteJobFromQueue(jobId){
|
||||
await printerDriverService.deleteJob(printer, jobId).then((response) => {
|
||||
getPrinterQueue()
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
loading = false
|
||||
})
|
||||
}
|
||||
function formatDate(date) {
|
||||
return new Date(date * 1000).toLocaleDateString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function formatTimeAgo(date) {
|
||||
const currentTime = new Date();
|
||||
const previousTime = new Date(date * 1000);
|
||||
const timeDifference = Math.abs(currentTime - previousTime);
|
||||
|
||||
const minutes = Math.floor(timeDifference / 60000);
|
||||
const hours = Math.floor(timeDifference / 3600000);
|
||||
const days = Math.floor(timeDifference / 86400000);
|
||||
|
||||
if (minutes < 1) {
|
||||
return 'just now';
|
||||
} else if (minutes < 60) {
|
||||
return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`;
|
||||
} else if (hours < 24) {
|
||||
return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
|
||||
} else {
|
||||
return `${days} ${days === 1 ? 'day' : 'days'} ago`;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPrinter(id) {
|
||||
loading = true
|
||||
await printerService.fetch(id).then((response) => {
|
||||
printer = response.data.data
|
||||
getPrinterStatus()
|
||||
loading = false
|
||||
}).then(() => {
|
||||
getPrinterFiles()
|
||||
getPrinterQueue()
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
loading = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async function getPrinterStatus() {
|
||||
console.log('nu from hier XD')
|
||||
await printerDriverService.getStatus(printer).then(res => {
|
||||
printer = {...printer, logs: res, display_logs: false};
|
||||
})
|
||||
}
|
||||
|
||||
async function getPrinterFiles() {
|
||||
await printerDriverService.getFiles(printer).then(res => {
|
||||
printerData = {...printerData, files: res?.result};
|
||||
})
|
||||
}
|
||||
|
||||
async function getPrinterQueue() {
|
||||
await printerDriverService.getPrinterQueue(printer).then(res => {
|
||||
printerData = {...printerData, queueData: res?.result};
|
||||
console.log(printerData)
|
||||
})
|
||||
}
|
||||
|
||||
function getPrinterColor(state) {
|
||||
if (state?.paused || state?.printing || state?.pausing) {
|
||||
return 'yellow'
|
||||
}
|
||||
|
||||
if (state?.operational || state?.ready) {
|
||||
return 'green'
|
||||
}
|
||||
|
||||
if (state?.cancelling || state?.error) {
|
||||
return 'red'
|
||||
}
|
||||
|
||||
|
||||
return 'gray'
|
||||
}
|
||||
|
||||
function getPrinterLogs(printer) {
|
||||
const color = getPrinterColor(printer?.logs?.state?.flags)
|
||||
|
||||
return {
|
||||
color: color,
|
||||
}
|
||||
}
|
||||
</script>
|
93
src/services/printer/printer-driver.service.js
Normal file
93
src/services/printer/printer-driver.service.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
import {deleteData} from "../../helpers/backend.js";
|
||||
|
||||
export default {
|
||||
async getStatus(printer) {
|
||||
if (printer.driver === 'moonraker') {
|
||||
return request(`http://${printer.config.host}:${printer.config.port}/api/printer`).then(res => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
async getFiles(printer) {
|
||||
if (printer.driver === 'moonraker') {
|
||||
return request(`http://${printer.config.host}:${printer.config.port}/server/files/list`).then(res => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
},
|
||||
async getPrinterQueue(printer) {
|
||||
if (printer.driver === 'moonraker') {
|
||||
return request(`http://${printer.config.host}:${printer.config.port}/server/job_queue/status`).then(res => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
},
|
||||
async postPrinterQueue(printer, filename) {
|
||||
if (printer.driver === 'moonraker') {
|
||||
//post
|
||||
|
||||
return post(`http://${printer.config.host}:${printer.config.port}/server/job_queue/job?filenames=${filename}`).then(res => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
},
|
||||
async deleteJob(printer, jobId) {
|
||||
if (printer.driver === 'moonraker') {
|
||||
//post
|
||||
|
||||
return deleteMethod(`http://${printer.config.host}:${printer.config.port}/server/job_queue/job?job_ids=${jobId}`).then(res => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
},
|
||||
async clearQueue(printer) {
|
||||
if (printer.driver === 'moonraker') {
|
||||
//post
|
||||
|
||||
return deleteMethod(`http://${printer.config.host}:${printer.config.port}/server/job_queue/job`).then(res => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function post(url, data) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
// body: JSON.stringify(data),
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => data)
|
||||
.catch(error => {
|
||||
console.log(error, 'xddddaaa');
|
||||
});
|
||||
}
|
||||
|
||||
function deleteMethod(url, data) {
|
||||
return fetch(url, {
|
||||
method: 'detele',
|
||||
// body: JSON.stringify(data),
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => data)
|
||||
.catch(error => {
|
||||
console.log(error, 'xddddaaa');
|
||||
});
|
||||
}
|
||||
|
||||
function request(url, options) {
|
||||
return fetch(url, options)
|
||||
.then(response => response.json())
|
||||
.then(data => data)
|
||||
.catch(error => {
|
||||
console.log(error, 'xddddaaa');
|
||||
});
|
||||
}
|
10
src/services/printer/printer.service.js
Normal file
10
src/services/printer/printer.service.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import {getData} from "../../helpers/backend.js";
|
||||
|
||||
export default {
|
||||
async index(params) {
|
||||
return getData(`/printers`, params);
|
||||
},
|
||||
async fetch(id) {
|
||||
return getData(`/printers/${id}`);
|
||||
}
|
||||
}
|
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.5 KiB |
10
svelte.config.js
Normal file
10
svelte.config.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
},
|
||||
preprocess: vitePreprocess()
|
||||
};
|
||||
export default config;
|
32
tailwind.config.js
Normal file
32
tailwind.config.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
const colors = require('tailwindcss/colors')
|
||||
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
export default {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}', './node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}'],
|
||||
plugins: [require('flowbite/plugin')],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'dark-primary': '#121212',
|
||||
'dark-light': '#282828',
|
||||
// 'primary': '#0284c7',
|
||||
gray: colors.gray,
|
||||
primary: {
|
||||
'50': '#f0fafb',
|
||||
'100': '#daf1f3',
|
||||
'200': '#b9e4e8',
|
||||
'300': '#89cfd7',
|
||||
'400': '#5bb6c2',
|
||||
'500': '#3696a4',
|
||||
'600': '#307a8a',
|
||||
'700': '#2c6472',
|
||||
'800': '#2b535f',
|
||||
'900': '#284751',
|
||||
'950': '#162e36',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
6
vite.config.js
Normal file
6
vite.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
Loading…
Reference in a new issue