Migrate to Vue, slightly redesign labels to fit more text
This commit is contained in:
parent
f2d207e540
commit
a239e2dd6e
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@ -0,0 +1,3 @@
|
||||
trailingComma: es5
|
||||
singleQuote: true
|
||||
jsxBracketSameLine: true
|
13
App.vue
Normal file
13
App.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<ToolLabels />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from 'vue-property-decorator';
|
||||
import ToolLabels from './ToolLabels.vue';
|
||||
|
||||
@Component({ components: { ToolLabels } })
|
||||
export default class App extends Vue {
|
||||
|
||||
}
|
||||
</script>
|
142
ComputerLabel.vue
Normal file
142
ComputerLabel.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<article class="label">
|
||||
<div class="content">
|
||||
<img class="qrcode" :src="qrcode_dataURL" />
|
||||
<span class="text-content">
|
||||
<div class="name">{{ name }}</div>
|
||||
<div class="hostname">{{ asset.name }}</div>
|
||||
<div class="mac">{{ mac }}</div>
|
||||
<div class="mac">{{ mac2 }}</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<span class="scanme">Scan the QR code to learn more!</span>
|
||||
<span class="barcode-tag">
|
||||
<img class="barcode" ref="barcode" />
|
||||
<div class="tag">{{ asset.asset_tag }}</div>
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from 'vue-property-decorator';
|
||||
import JsBarcode from 'jsbarcode';
|
||||
import { toWords as numberToWords } from 'number-to-words';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
import snipeit from './snipeit';
|
||||
|
||||
@Component
|
||||
export default class ComputerLabel extends Vue {
|
||||
QRCODE_BASE = 'https://inv.claremontmakerspace.org/';
|
||||
|
||||
@Prop(Object) readonly asset: snipeit.Hardware;
|
||||
|
||||
qrcode_dataURL: string | null = null;
|
||||
|
||||
async mounted() {
|
||||
this.qrcode_dataURL = await QRCode.toDataURL(
|
||||
this.QRCODE_BASE + this.asset.asset_tag,
|
||||
{
|
||||
margin: 0,
|
||||
}
|
||||
);
|
||||
|
||||
JsBarcode(this.$refs.barcode, this.asset.asset_tag, {
|
||||
displayValue: false,
|
||||
margin: 0,
|
||||
});
|
||||
}
|
||||
|
||||
get name() {
|
||||
return numberToWords(this.asset.name.match(/[0-9]+$/)[0]).toUpperCase();
|
||||
}
|
||||
|
||||
get mac() {
|
||||
return this.asset.custom_fields['MAC Address']?.value;
|
||||
}
|
||||
get mac2() {
|
||||
return this.asset.custom_fields['MAC Address 2']?.value;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@page {
|
||||
size: calc(146.64pt - 4.32pt) calc(69.12pt - 4.08pt);
|
||||
margin: 1mm;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-size: cover;
|
||||
page-break-after: always;
|
||||
clear: both;
|
||||
width: calc(146.64pt - 4.32pt);
|
||||
height: calc(69.12pt - 4.08pt);
|
||||
position: relative;
|
||||
padding-top: 1.9mm;
|
||||
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
|
||||
@media screen {
|
||||
.label {
|
||||
border: 1px solid red;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
margin-left: 1ex;
|
||||
font-family: monospace;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30;
|
||||
line-height: 0.8;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
.mac {
|
||||
font-size: 10;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin-top: 2%;
|
||||
display: flex;
|
||||
height: 26%;
|
||||
padding-right: 1.2mm;
|
||||
}
|
||||
|
||||
.scanme {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.barcode-tag {
|
||||
width: 90%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 7px;
|
||||
}
|
||||
|
||||
.barcode {
|
||||
width: 90%;
|
||||
height: 60%;
|
||||
}
|
||||
</style>
|
23
ToolLabels.vue
Normal file
23
ToolLabels.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<ComputerLabel
|
||||
v-for="asset in assets.rows.filter(t => t.name !== '')"
|
||||
:asset="asset"
|
||||
:key="asset.id"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from 'vue-property-decorator';
|
||||
|
||||
import snipeit from './snipeit';
|
||||
import ComputerLabel from './ComputerLabel.vue';
|
||||
|
||||
import assets from './computers.json';
|
||||
|
||||
@Component({ components: { ComputerLabel } })
|
||||
export default class ToolLabels extends Vue {
|
||||
assets: { rows: [snipeit.Hardware]; total: number } = assets;
|
||||
}
|
||||
</script>
|
@ -1,58 +0,0 @@
|
||||
@page {
|
||||
size: calc(146.64pt - 4.32pt) calc(69.12pt - 4.08pt);
|
||||
margin: 1mm;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-size: cover;
|
||||
page-break-after: always;
|
||||
clear: both;
|
||||
width: calc(146.64pt - 4.32pt);
|
||||
height: calc(69.12pt - 4.08pt);
|
||||
position: relative;
|
||||
padding-top: 1.9mm;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen {
|
||||
.label {
|
||||
border: 1px solid red;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
margin-left: 1ex;
|
||||
font-family: monospace;
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30;
|
||||
line-height: 0.8;
|
||||
margin-bottom: .1em;
|
||||
}
|
||||
|
||||
.mac {
|
||||
font-size: 10;
|
||||
}
|
||||
|
||||
.barcode {
|
||||
margin-top: 2%;
|
||||
bottom: 0;
|
||||
width: 90%;
|
||||
height: 24%;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<link rel="stylesheet" type="text/css" href="computerLabel.css" />
|
||||
<script src="computerLabel.js"></script>
|
||||
|
||||
<template id="labelTemplate">
|
||||
<article class="label">
|
||||
<div class="content">
|
||||
<img class="qrcode" />
|
||||
<span class="text-content">
|
||||
<div class="name"></div>
|
||||
<div class="hostname"></div>
|
||||
<div class="mac"></div>
|
||||
<div class="tag"></div>
|
||||
</span>
|
||||
</div>
|
||||
<img class="barcode" />
|
||||
</article>
|
||||
</template>
|
@ -1,39 +0,0 @@
|
||||
import JsBarcode from "jsbarcode";
|
||||
import { toWords as numberToWords } from "number-to-words";
|
||||
import QRCode from "qrcode";
|
||||
|
||||
import data from "./export-computers-assets-2020-01-30.json";
|
||||
|
||||
async function renderLabels() {
|
||||
const template = document.querySelector("#labelTemplate");
|
||||
|
||||
const URL_BASE = "https://inv.claremontmakerspace.org/";
|
||||
|
||||
for (const asset of data.data) {
|
||||
const {
|
||||
"Asset Tag": assetTag,
|
||||
"Asset Name": assetName,
|
||||
"MAC Address": mac
|
||||
} = asset;
|
||||
let clone = document.importNode(template.content, true);
|
||||
|
||||
let qrcode = await QRCode.toDataURL(URL_BASE + assetTag, { margin: 0 });
|
||||
clone.querySelector(".qrcode").src = qrcode;
|
||||
|
||||
JsBarcode(clone.querySelector(".barcode"), assetTag, {
|
||||
displayValue: false,
|
||||
margin: 0
|
||||
});
|
||||
|
||||
let name = numberToWords(assetName.match(/[0-9]+$/)[0]).toUpperCase();
|
||||
|
||||
clone.querySelector(".name").textContent = name;
|
||||
clone.querySelector(".hostname").textContent = assetName;
|
||||
clone.querySelector(".mac").textContent = mac;
|
||||
clone.querySelector(".tag").textContent = assetTag;
|
||||
|
||||
document.body.appendChild(clone);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", renderLabels);
|
31
getAssets.py
Executable file
31
getAssets.py
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import requests
|
||||
import sys
|
||||
|
||||
BASE_URL = 'https://inventory.claremontmakerspace.org/api/v1/'
|
||||
|
||||
token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjlmODA4ZDhlNGNiNTExNDJhZmZkNzc3YzVmYmFjOTAzN2U4ZGVkMmJhNWMxZDY2M2YzODhhOWQ3ODQ1MWFhMDIwN2E1NWVmNDNlNTlkZTRlIn0.eyJhdWQiOiIxIiwianRpIjoiOWY4MDhkOGU0Y2I1MTE0MmFmZmQ3NzdjNWZiYWM5MDM3ZThkZWQyYmE1YzFkNjYzZjM4OGE5ZDc4NDUxYWEwMjA3YTU1ZWY0M2U1OWRlNGUiLCJpYXQiOjE1ODM4NTg3ODIsIm5iZiI6MTU4Mzg1ODc4MiwiZXhwIjoxNjE1Mzk4MzgyLCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.RaUpwGEQOyQHKF9toXgtI2gMetCXpokux6SeLlF-6OXMpGPv64YPIWYFWfpwF8PrKTKIJiEW_gOyg15sHdEs1VLchQLYUrO7Fv9jK8_S3jj1PkXx_OQpork7nO19hEJUpi64d5RPuyyprTgR2Yu9VAi37bwTjBz2TbpggOaqWjH1gpX7BvZqpFFgTgg-A-KO9TCYurPst4GrNpR4DG0SYxqDCOlpQoMa6bnzlZ3ZbW0Xt2Z8E8JiEWjZ6FalNs3EoQAyGIyW_f66qiouR0EzFqa9-EHt0TdZvr_n6KG0Rdq4RBmKGG0-b4l3VIDbCKe9DeSVZa8oZOamfAiwBIhADOMIXrNJNov7-T25c9h2PznuaXLZrh1JydJxreFpoWwvME4G-HBQWHL9EVY3Kdi-6gB8Bh0kWdfoEkFFcio-40iE4eL5h5AnE-nyMKSFeqrhw_A1mhNaGgJixIzD0gDxnVBNT1RpjEDxBFXV1_bQd8pWOG6gWP263PY8fWf4vGE8_WPt95F-DYYrOJ7QsmZ1iTy4FKHo5CvuIgvExMXg3eLol7Rc0P6e1MA-OITi6taT7GG4T26R9_fukXJYe15vVyV-sR8_wBE9CeYIQLoW69R4vzAzKXwBJ5zrjFAog13iq5Vmyy9koY1vRGOvKuXIO5TZxlQTznZSJFy86FoANww'
|
||||
|
||||
categories = {
|
||||
"tools": "4",
|
||||
"computers": "5"
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': "application/json",
|
||||
'Content-Type': "application/json"
|
||||
}
|
||||
|
||||
r = requests.get(
|
||||
BASE_URL + "hardware/",
|
||||
params={
|
||||
"limit": "1000",
|
||||
"category_id": categories[sys.argv[1]]
|
||||
},
|
||||
headers=headers)
|
||||
|
||||
with open(sys.argv[1] + '.json', 'w') as f:
|
||||
f.write(r.text)
|
4
index.html
Normal file
4
index.html
Normal file
@ -0,0 +1,4 @@
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="index.ts"></script>
|
||||
</body>
|
7
index.ts
Normal file
7
index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import App from './App';
|
||||
|
||||
let app = new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app');
|
10
package.json
10
package.json
@ -6,6 +6,14 @@
|
||||
"dependencies": {
|
||||
"jsbarcode": "^3.11.0",
|
||||
"number-to-words": "^1.2.4",
|
||||
"qrcode": "^1.4.4"
|
||||
"qrcode": "^1.4.4",
|
||||
"vue": "^2.6.11",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-property-decorator": "^8.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/component-compiler-utils": "^3.1.1",
|
||||
"typescript": "^3.8.3",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
}
|
||||
}
|
||||
|
63
snipeit.d.ts
vendored
Normal file
63
snipeit.d.ts
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
export interface Hardware {
|
||||
id: number;
|
||||
name: string;
|
||||
asset_tag: string;
|
||||
serial: string;
|
||||
model: IdName;
|
||||
model_number: string;
|
||||
eol?: string; // TODO
|
||||
status_label: IdName & {
|
||||
status_type: 'deployable' | 'pending' | 'undeployable' | 'archived';
|
||||
status_meta: 'deployed' | 'pending'; // TODO
|
||||
};
|
||||
category: IdName;
|
||||
manufacturer: IdName;
|
||||
supplier?: IdName;
|
||||
notes?: string;
|
||||
order_number?: string;
|
||||
company?: IdName; // TODO
|
||||
location?: IdName;
|
||||
rtd_location?: IdName;
|
||||
image: string;
|
||||
assigned_to: IdName & { type: string };
|
||||
warranty_months?: string;
|
||||
warranty_expires?: string;
|
||||
created_at: DatetimeFormatted;
|
||||
updated_at: DatetimeFormatted;
|
||||
last_audit_date?: DatetimeFormatted;
|
||||
next_audit_date?: DatetimeFormatted;
|
||||
deleted_at?: DatetimeFormatted;
|
||||
purchase_date: DatetimeFormatted;
|
||||
last_checkout: DatetimeFormatted;
|
||||
expected_checkin: DatetimeFormatted;
|
||||
purchase_cost: number;
|
||||
checkin_counter: number;
|
||||
checkout_counter: number;
|
||||
requests_counter: number;
|
||||
user_can_checkout: boolean;
|
||||
custom_fields: {
|
||||
[key: string]: {
|
||||
field: string;
|
||||
value?: string;
|
||||
field_format: string;
|
||||
};
|
||||
};
|
||||
available_actions: {
|
||||
checkout: boolean;
|
||||
checkin: boolean;
|
||||
clone: boolean;
|
||||
restore: boolean;
|
||||
update: boolean;
|
||||
delete: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IdName {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DatetimeFormatted {
|
||||
datetime: string;
|
||||
formatted: string;
|
||||
}
|
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"strict": true,
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user