Migrate to Vue, slightly redesign labels to fit more text

This commit is contained in:
Adam Goldsmith 2020-03-10 17:15:33 -04:00
parent f2d207e540
commit a239e2dd6e
14 changed files with 312 additions and 115 deletions

9
.editorconfig Normal file
View 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
View File

@ -0,0 +1,3 @@
trailingComma: es5
singleQuote: true
jsxBracketSameLine: true

13
App.vue Normal file
View 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
View 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
View 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>

View File

@ -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%;
}

View File

@ -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>

View File

@ -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
View 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
View File

@ -0,0 +1,4 @@
<body>
<div id="app"></div>
<script src="index.ts"></script>
</body>

7
index.ts Normal file
View File

@ -0,0 +1,7 @@
import Vue from 'vue';
import App from './App';
let app = new Vue({
render: h => h(App),
}).$mount('#app');

View File

@ -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
View 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
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es6",
"strict": true,
"module": "es2015",
"moduleResolution": "node"
}
}