Add edit page, dialog based item page. Consider switching to popups.

master
Cole Deck 1 day ago
parent 2a8de79adb
commit 27169ff8a0

@ -1,12 +1,8 @@
FROM python:3.11-slim FROM python:3.13-slim
# Get runtime dependencies
# glx for OpenCV, ghostscript for datasheet PDF rendering, zbar for barcode scanning, git for cloning repos
#RUN apt-get update && apt-get install -y libgl1-mesa-glx ghostscript libzbar0 git && apt-get clean && rm -rf /var/lib/apt/lists
COPY requirements.txt ./ COPY requirements.txt ./
#COPY config-server.yml config.yml
RUN pip3 install -r requirements.txt RUN pip3 install -r requirements.txt
COPY *.py *.txt *.toml ./ COPY *.py *.txt *.toml ./
COPY inventory ./inventory COPY inventory ./inventory
CMD ["rio", "run", "--release", "--public", "--port", "8000"] CMD ["rio", "run", "--release", "--public", "--port", "8000"]
EXPOSE 8000 EXPOSE 8000

@ -24,7 +24,7 @@ class office(Model):
class location(Model): class location(Model):
name = CharField() name = CharField()
locationid = AutoField() locationid = CharField(unique=True, primary_key=True)
description = CharField(null=True) description = CharField(null=True)
parent = ForeignKeyField('self', null=True, backref="sublocations") parent = ForeignKeyField('self', null=True, backref="sublocations")
@ -42,6 +42,7 @@ class item(Model):
description = CharField(null=True) description = CharField(null=True)
serial = CharField(null=True) serial = CharField(null=True)
checkout = BooleanField(default=False) checkout = BooleanField(default=False)
checkout_loc = ForeignKeyField(location, backref="items_checkedout_here", null=True)
checkout_user = ForeignKeyField(user, backref="items_held", null=True) checkout_user = ForeignKeyField(user, backref="items_held", null=True)
checkout_start = DateTimeField(null=True) checkout_start = DateTimeField(null=True)
checkout_end = DateTimeField(null=True) checkout_end = DateTimeField(null=True)
@ -70,7 +71,13 @@ class component(Model):
def init(): def init():
print("Connecting to database...") print("Connecting to database...")
db.connect() import time
while True:
try:
db.connect()
break
except:
time.sleep(1)
print("Checking & creating tables...") print("Checking & creating tables...")
db.create_tables([location, office, item, component, user]) db.create_tables([location, office, item, component, user])
print("Database initialized.") print("Database initialized.")
@ -81,6 +88,10 @@ def init():
#print(add) #print(add)
#print(type(add)) #print(type(add))
for itm in add: for itm in add:
try:
itm["location"] = item.select().where(item.barcode==itm["barcode"])[0].loc.name
except:
pass
print(itm) print(itm)
#print(type(itm)) #print(type(itm))
search.add_document(itm) search.add_document(itm)
@ -115,17 +126,65 @@ def search_item(query, filters: dict={}):
def find_item(barcode): def find_item(barcode):
return search.get_barcode(barcode) return search.get_barcode(barcode)
def create_item(fullname, serial, officename, barcode, location=None, description=None, manufacturer=None, mac=None, fwver=None): def find_item_location(barcode):
try:
return item.select().where(item.barcode==barcode).loc
except:
return False
def create_item(fullname, serial, officename, barcode, locationid=None, description=None, manufacturer=None, mac=None, fwver=None):
try: try:
off = office(name=officename) off = office(name=officename)
off.save(force_insert=True) off.save(force_insert=True)
except IntegrityError: except IntegrityError:
pass pass
try: try:
loc = get_location_id(locationid)
if loc == False:
loc = None
else:
print("Found location: " + loc.name)
off = office.select().where(office.name == officename)[0] off = office.select().where(office.name == officename)[0]
itm = item(office=off, barcode=barcode, fullname=fullname, description=description, loc=location, serial=serial, mac=mac, fwver=fwver, manufacturer=manufacturer) itm = item(office=off, barcode=barcode, fullname=fullname, description=description, loc=loc, serial=serial, mac=mac, fwver=fwver, manufacturer=manufacturer)
itm.save(force_insert=True) itm.save(force_insert=True)
search.add_document(item.select().where(item.barcode==barcode).dicts()[0]) itmdict= item.select().where(item.barcode==barcode).dicts()[0]
try:
itmdict["location"] = loc.name
#print(locationid)
#print(itmdict["location"])
except:
pass
search.add_document(itmdict)
print("item: " + itm.fullname)
return itm
except IntegrityError:
print("Duplicate item " + fullname)
return False
def update_item(fullname, serial, officename, barcode, locationid=None, description=None, manufacturer=None, mac=None, fwver=None):
try:
off = office(name=officename)
off.save(force_insert=True)
except IntegrityError:
pass
try:
loc = get_location_id(locationid)
if loc == False:
loc = None
else:
print("Found location: " + loc.name)
off = office.select().where(office.name == officename)[0]
itm = item(office=off, barcode=barcode, fullname=fullname, description=description, loc=loc, serial=serial, mac=mac, fwver=fwver, manufacturer=manufacturer)
itm.save()
itmdict= item.select().where(item.barcode==barcode).dicts()[0]
try:
itmdict["location"] = loc.name
#print(locationid)
#print(itmdict["location"])
except:
pass
search.add_document(itmdict)
print("item: " + itm.fullname) print("item: " + itm.fullname)
return itm return itm
except IntegrityError: except IntegrityError:
@ -196,7 +255,7 @@ def checkout(user, barcode, loc=None):
if itm: if itm:
itm.checkout = True itm.checkout = True
itm.checkout_user = user itm.checkout_user = user
itm.loc = loc itm.checkout_loc = loc
itm.save() itm.save()
return itm return itm
else: else:
@ -207,19 +266,21 @@ def checkin(user, barcode, loc=None):
if itm: if itm:
itm.checkout = False itm.checkout = False
itm.last_user = user itm.last_user = user
itm.loc = loc if loc is not None:
itm.loc = loc
itm.save() itm.save()
return itm return itm
else: else:
return False return False
def create_location(name, parent=None): def create_location(name, barcode, parent=None, description=None):
if parent is not None: try:
loc = location(name=name, parent=parent) loc = location(name=name, locationid=barcode, parent=parent, description=description)
loc.save() loc.save(force_insert=True)
print(loc.name, loc.locationid)
return loc return loc
else: except:
return False return False
def _find_parent(loc, parent): def _find_parent(loc, parent):
@ -247,6 +308,21 @@ def get_location(name, parent=None):
return False return False
except: except:
return False return False
def get_location_id(barcode):
try:
print("str" + barcode + "str")
if len(barcode) > 0:
query = location.select()
for loc in query:
print(loc.name, loc.locationid)
if loc.locationid == barcode:
return loc
return False
else:
return False
except:
return False
def get_user(name): def get_user(name):
query = user.select().where(user.username == name) query = user.select().where(user.username == name)

@ -35,26 +35,31 @@ app = rio.App(
comps.Settings(), comps.Settings(),
], ],
pages=[ pages=[
rio.Page( rio.ComponentPage(
name="Home", name="Home",
page_url='', url_segment='',
build=pages.BrowsePage, build=pages.BrowsePage,
), ),
rio.Page( rio.ComponentPage(
name="AboutPage", name="SettingsPage",
page_url='about-page', url_segment='settings-page',
build=pages.AboutPage, build=pages.SettingsPage,
), ),
rio.Page( rio.ComponentPage(
name="AddPage", name="AddPage",
page_url='add', url_segment='add',
build=pages.AddPage, build=pages.AddPage,
), ),
rio.Page( rio.ComponentPage(
name="AddLocationPage",
url_segment='addlocation',
build=pages.AddLocationPage,
),
rio.ComponentPage(
name="ItemPage", name="ItemPage",
page_url='item', url_segment='item',
build=pages.ItemPage, build=pages.ItemPage,
), ),
], ],

@ -16,7 +16,6 @@ class Footer(rio.Component):
def build(self) -> rio.Component: def build(self) -> rio.Component:
return rio.Card( return rio.Card(
content=rio.Column( content=rio.Column(
rio.Icon("rio/logo:fill", width=5, height=5),
rio.Text("Buzzwordz Inc."), rio.Text("Buzzwordz Inc."),
rio.Text( rio.Text(
"Hyper Dyper Website", "Hyper Dyper Website",

@ -60,7 +60,7 @@ class Navbar(rio.Component):
# you've passed the app during creation. Since multiple pages can be # you've passed the app during creation. Since multiple pages can be
# active at a time (e.g. /foo/bar/baz), this is a list. # active at a time (e.g. /foo/bar/baz), this is a list.
active_page = self.session.active_page_instances[0] active_page = self.session.active_page_instances[0]
active_page_url_segment = active_page.page_url active_page_url_segment = active_page.url_segment
# The navbar should appear above all other components. This is easily # The navbar should appear above all other components. This is easily
# done by using a `rio.Overlay` component. # done by using a `rio.Overlay` component.
@ -80,7 +80,7 @@ class Navbar(rio.Component):
style=( style=(
"major" "major"
if active_page_url_segment == "" if active_page_url_segment == ""
else "plain" else "plain-text"
), ),
), ),
"/", "/",
@ -121,23 +121,35 @@ class Navbar(rio.Component):
style=( style=(
"major" "major"
if active_page_url_segment == "add" if active_page_url_segment == "add"
else "plain" else "plain-text"
), ),
), ),
"/add", "/add",
), ),
rio.Link(
rio.Button(
"Locations",
icon="material/news",
style=(
"major"
if active_page_url_segment == "addlocation"
else "plain-text"
),
),
"/addlocation",
),
# Same game, different button # Same game, different button
rio.Link( rio.Link(
rio.Button( rio.Button(
"About", "Settings",
icon="material/info", icon="material/info",
style=( style=(
"major" "major"
if active_page_url_segment == "about-page" if active_page_url_segment == "settings-page"
else "plain" else "plain-text"
), ),
), ),
"/about-page", "/settings-page",
), ),
spacing=1, spacing=1,
margin=1, margin=1,

@ -1,6 +1,7 @@
from .root_page import RootPage from .root_page import RootPage
from .about_page import AboutPage from .settings_page import SettingsPage
from .add_page import AddPage from .add_page import AddPage
from .add_location import AddLocationPage
from .browse_page import BrowsePage from .browse_page import BrowsePage
from .login_page import LoginPage from .login_page import LoginPage
from .item_page import ItemPage from .item_page import ItemPage

@ -1,63 +0,0 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
class AboutPage(rio.Component):
"""
A sample page, which displays a humorous description of the company.
"""
def build(self) -> rio.Component:
return rio.Markdown(
"""
# About Us
Welcome to Buzzwordz Inc.! Unleashing Synergistic Paradigms for Unprecedented
Excellence since the day after yesterday.
## About Our Company
At buzzwordz, we are all talk and no action. Our mission is to be the vanguards
of industry-leading solutions, leveraging bleeding-edge technologies to catapult
your business into the stratosphere of success. Our unparalleled team of ninjas,
gurus, and rockstars is dedicated to disrupting the status quo and actualizing
your wildest business dreams. We live, breathe, and eat operational excellence
and groundbreaking innovation.
## Synergistic Consulting
Unlock your business's quantum potential with our bespoke, game-changing
strategies. Our consulting services synergize cross-functional paradigms to
create a holistic ecosystem of perpetual growth and exponential ROI. Did I
mention paradigm-shifts? We've got those too.
## Agile Hyper-Development
We turn moonshot ideas into reality with our agile, ninja-level development
techniques. Our team of coding wizards crafts robust, scalable, and future-proof
solutions that redefine industry standards. 24/7 Proactive Hyper-Support
Experience next-gen support that anticipates your needs before you do. Our
omnipresent customer happiness engineers ensure seamless integration,
frictionless operation, and infinite satisfaction, day and night.
Embark on a journey of transformational growth and stratospheric success. Don't
delay, give us your money today.
Phone: (123) 456-7890
Email: info@yourwebsite.com
Address: 123 Main Street, City, Country
""",
width=60,
margin_bottom=4,
align_x=0.5,
align_y=0,
)

@ -0,0 +1,118 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
import datetime
from mac_vendor_lookup import AsyncMacLookup
from db_classes import *
from .. import components as comps
import asyncio
class AddLocationPage(rio.Component):
code: str = ""
popup_message: str = ""
popup_show: bool = False
popup_color: str = 'warning'
description: str = ""
name: str = ""
parent_code: str = ""
parent: str = ""
@rio.event.periodic(1)
def set_office_init(self):
self.office = self.session[comps.Settings].office
#print("Populated:", self.office)
async def _update_location(self, event: rio.TextInputChangeEvent):
print("Checking " + self.parent)
if get_location_id(self.parent) != False:
self.parent_code = self.parent
print("Found location " + get_location_id(self.parent).name)
self.parent = get_location_id(self.parent).name
async def add_part(self):
if self.code == "":
# FAIL
self.popup_message = "\n Missing barcode! \n\n"
self.popup_show = True
self.popup_color = 'danger'
await asyncio.sleep(1)
self.popup_show = False
else:
# OK, add part
if get_location_id(self.parent_code) != False:
self.parent = get_location_id(self.parent_code)
else:
self.parent = None
if create_location(name=self.name, barcode=self.code, parent=self.parent, description=self.description) == False:
self.popup_message = "\n Duplicate barcode! \n\n"
self.popup_show = True
self.popup_color = 'warning'
await asyncio.sleep(2)
self.popup_show = False
else:
self.popup_message = "\n Part added! \n\n"
self.popup_show = True
self.popup_color = 'success'
self.name: str = ""
self.code: str = ""
self.description: str = ""
self.parent_code: str = ""
self.parent: str = ""
await asyncio.sleep(2)
self.popup_show = False
async def _add_part_enter(self, event: rio.TextInputConfirmEvent):
await self.add_part()
async def _add_part_button(self):
await self.add_part()
def build(self) -> rio.Component:
return rio.Column(
rio.Popup(
anchor=rio.Text(
text="Add a part below:",
style='heading1',
align_x = 0.5
),
color=self.bind().popup_color,
is_open=self.bind().popup_show,
content=rio.Text(
text=self.bind().popup_message,
),
),
rio.TextInput(
label="Barcode",
text=self.bind().code
),
rio.TextInput(
label="Parent Location (optional)",
text=self.bind().parent,
on_change=self._update_location
),
rio.TextInput(
label="Location Name",
text=self.bind().name
),
rio.MultiLineTextInput(
label="Description (optional)",
text=self.bind().description
),
rio.Button(
content="Add",
on_press=self._add_part_button
),
spacing=2,
min_width=60,
margin_bottom=4,
align_x=0.5,
align_y=0,
)

@ -10,7 +10,14 @@ from mac_vendor_lookup import AsyncMacLookup
from db_classes import * from db_classes import *
from .. import components as comps from .. import components as comps
import asyncio
class AddPage(rio.Component): class AddPage(rio.Component):
"""
A set of fields for adding/editing items.
"""
partnum: str = "" partnum: str = ""
mac: str = "" mac: str = ""
serial: str = "" serial: str = ""
@ -27,6 +34,9 @@ class AddPage(rio.Component):
manu: str = "" manu: str = ""
manufield: str = "" manufield: str = ""
office: str = "" office: str = ""
description: str = ""
location: str = ""
location_code: str = ""
@rio.event.periodic(1) @rio.event.periodic(1)
def set_office_init(self): def set_office_init(self):
@ -60,12 +70,16 @@ class AddPage(rio.Component):
self.popup_message = "\n Missing barcode! \n\n" self.popup_message = "\n Missing barcode! \n\n"
self.popup_show = True self.popup_show = True
self.popup_color = 'danger' self.popup_color = 'danger'
await asyncio.sleep(2)
self.popup_show = False
else: else:
# OK, add part # OK, add part
if create_item(self.partnum, self.serial, self.office, self.code, location=None, description=None, manufacturer=self.manu, mac=self.mac, fwver=self.fwver) == False: if create_item(self.partnum, self.serial, self.office, self.code, locationid=self.location_code, description=self.description, manufacturer=self.manu, mac=self.mac, fwver=self.fwver) == False:
self.popup_message = "\n Duplicate barcode! \n\n" self.popup_message = "\n Duplicate barcode! \n\n"
self.popup_show = True self.popup_show = True
self.popup_color = 'warning' self.popup_color = 'warning'
await asyncio.sleep(2)
self.popup_show = False
else: else:
self.popup_message = "\n Part added! \n\n" self.popup_message = "\n Part added! \n\n"
self.popup_show = True self.popup_show = True
@ -79,6 +93,11 @@ class AddPage(rio.Component):
self.macvendor: str = "" self.macvendor: str = ""
self.manu: str = "" self.manu: str = ""
self.manufield: str = "" self.manufield: str = ""
self.description: str = ""
self.location: str = ""
self.location_code: str = ""
await asyncio.sleep(2)
self.popup_show = False
async def _add_part_enter(self, event: rio.TextInputConfirmEvent): async def _add_part_enter(self, event: rio.TextInputConfirmEvent):
@ -107,8 +126,16 @@ class AddPage(rio.Component):
async def _update_mac(self, event: rio.TextInputChangeEvent): async def _update_mac(self, event: rio.TextInputChangeEvent):
await self.check_mac(event.text) await self.check_mac(event.text)
async def _update_location(self, event: rio.TextInputChangeEvent):
print("Checking " + self.location)
if get_location_id(self.location) != False:
self.location_code = self.location
print("Found location " + get_location_id(self.location).name)
self.location = get_location_id(self.location).name
def _update_partnum(self, event: rio.TextInputChangeEvent): def _update_partnum(self, event: rio.TextInputChangeEvent):
def __find_hm_header(txt): def __find_hm_header_static(txt):
searchlist = ["RSPS", "RSPE", "RSP", "RSB", "LRS", "RS", "OS", "RED", "MSP", "MSM", "MS", "MM", "EESX", "EES", "OZD", "OBR"] searchlist = ["RSPS", "RSPE", "RSP", "RSB", "LRS", "RS", "OS", "RED", "MSP", "MSM", "MS", "MM", "EESX", "EES", "OZD", "OBR"]
for header in searchlist: for header in searchlist:
if txt.find(header) >= 0: if txt.find(header) >= 0:
@ -129,6 +156,8 @@ class AddPage(rio.Component):
if dash and acount <= 5 and ncount <= 5: if dash and acount <= 5 and ncount <= 5:
return acount+ncount return acount+ncount
elif dash and acount >5 and acount <= 10 and ncount == 0:
return acount+ncount
return -1 return -1
def __find_hm_fwver(txt): def __find_hm_fwver(txt):
@ -143,7 +172,7 @@ class AddPage(rio.Component):
if txt.find("BRS") == 0: if txt.find("BRS") == 0:
a -= 1 a -= 1
if txt[a] in ['S', 'A']: if txt[a] in ['S', 'A']:
return txt[a] return '2' + txt[a]
a = __find_hm_fwver(txt) a = __find_hm_fwver(txt)
if txt.find("GRS") == 0: if txt.find("GRS") == 0:
a -= 4 a -= 4
@ -162,7 +191,7 @@ class AddPage(rio.Component):
print(txt,txt[a]) print(txt,txt[a])
if txt[a] in ['P', 'E']: if txt[a] in ['P', 'E']:
return txt[a] return '2' + txt[a]
a = __find_hm_fwver(txt) a = __find_hm_fwver(txt)
if txt.find("EAGLE") == 0: if txt.find("EAGLE") == 0:
a -= 2 a -= 2
@ -179,7 +208,7 @@ class AddPage(rio.Component):
if __find_hm_fwver(pn) >= 0: if __find_hm_fwver(pn) >= 0:
self.fwver = pn[__find_hm_fwver(pn):] self.fwver = pn[__find_hm_fwver(pn):]
if len(__find_hm_fwmode(pn)) > 0: if len(__find_hm_fwmode(pn)) > 0:
self.fwver += " SWL-" + __find_hm_fwmode(pn) self.fwver += " SW-L" + __find_hm_fwmode(pn)
def build(self) -> rio.Component: def build(self) -> rio.Component:
return rio.Column( return rio.Column(
@ -207,33 +236,39 @@ class AddPage(rio.Component):
), ),
rio.TextInput( rio.TextInput(
label="Serial", label="Serial",
text=self.bind().serial text=self.bind().serial,
on_confirm=self._add_part_enter
), ),
rio.TextInput( rio.TextInput(
label="MAC", label="MAC",
text=self.bind().mac, text=self.bind().mac,
on_change=self._update_mac on_change=self._update_mac,
on_confirm=self._add_part_enter
),
rio.TextInput(
label="Location (optional)",
text=self.bind().location,
on_change=self._update_location,
on_confirm=self._add_part_enter
), ),
rio.TextInput( rio.TextInput(
label="Manufacturer", label="Manufacturer",
text=self.bind().manufield text=self.bind().manufield,
on_confirm=self._add_part_enter
), ),
rio.TextInput( rio.TextInput(
label="FW Ver", label="FW Ver",
text=self.bind().fwver, text=self.bind().fwver
on_confirm=self._add_part_enter ),
rio.MultiLineTextInput(
label="Description (optional)",
text=self.bind().description
), ),
rio.Row( rio.TextInput(
# rio.DateInput( label="Timestamp",
# label="Timestamp", is_sensitive=False,
# value=self.bind().date text = self.bind().time_start,
# ), #on_change=self._set_time
rio.TextInput(
#text=
is_sensitive=False,
text = self.bind().time_start,
#on_change=self._set_time
)
), ),
rio.Button( rio.Button(
content="Add", content="Add",
@ -241,7 +276,7 @@ class AddPage(rio.Component):
), ),
spacing=2, spacing=2,
width=60, min_width=60,
margin_bottom=4, margin_bottom=4,
align_x=0.5, align_x=0.5,
align_y=0, align_y=0,

@ -6,15 +6,36 @@ from typing import * # type: ignore
import rio import rio
from .. import components as comps from .. import components as comps
from .add_page import AddPage
from db_classes import * from db_classes import *
import functools import functools
import asyncio
class BrowsePage(rio.Component): class BrowsePage(rio.Component):
searchtext: str = "" searchtext: str = ""
items: dict = {} items: list = []
office: str = "" office: str = ""
filters: dict = {} filters: dict = {}
popup_message: str = ""
popup_show: bool = False
popup_color: str = 'success'
elocation: str = ""
elocation_code: str = ""
epartnum: str = ""
emac: str = ""
eserial: str = ""
efwver: str = ""
ecode: str = ""
emacvendor: str = ""
emanu: str = ""
emanufield: str = ""
eoffice: str = ""
edescription: str = ""
@rio.event.on_populate @rio.event.on_populate
async def _search(self, query=searchtext): async def _search(self, query=searchtext):
self.office = self.session[comps.Settings].office self.office = self.session[comps.Settings].office
@ -35,24 +56,331 @@ class BrowsePage(rio.Component):
async def _search_trigger(self, event: rio.TextInputChangeEvent): async def _search_trigger(self, event: rio.TextInputChangeEvent):
await self._search(event.text) await self._search(event.text)
def click_item(self, code): async def _create_dialog_info(self, code: str) -> str | None:
async def copy_info(text: str):
await self.session.set_clipboard(text)
# TODO: show "Copied!" popup
self.popup_message = "\n Copied! \n\n"
self.popup_color = 'success'
self.popup_show = True
await asyncio.sleep(1.5)
self.popup_show = False
def build_dialog_info() -> rio.Component:
# Build the dialog
itm: dict = find_item(code)
try:
loc = itm["location"]
except:
loc = ""
if itm["checkout"]:
checkout = itm["checkout_user"] + " - " + loc
else:
checkout = loc
details: rio.ListView = rio.ListView(grow_y=True, min_width=40)
# for key, val in itm.items():
# details.add(rio.SimpleListItem(text=key,secondary_text=val))
#functools.partial(copy_info, text=item["barcode"])
name=str(itm["fullname"])
manu=str(itm["manufacturer"])
serial=str(itm["serial"])
mac=str(itm["mac"])
fw=str(itm["fwver"])
loc=str(checkout)
checkouts=str(itm["checkout"])
checkout_times=str(itm["checkout_start"]) + " to " + str(itm["checkout_end"])
#office=str(itm["office"])
barcode=str(itm["barcode"])
desc=str(itm["description"])
details.add(rio.SimpleListItem(text=name, on_press=functools.partial(copy_info, text=name)))
details.add(rio.SimpleListItem(text=manu, on_press=functools.partial(copy_info, text=manu)))
details.add(rio.SimpleListItem(text="Serial",secondary_text=serial, on_press=functools.partial(copy_info, text=serial)))
details.add(rio.SimpleListItem(text="MAC",secondary_text=mac, on_press=functools.partial(copy_info, text=mac)))
details.add(rio.SimpleListItem(text="FW Version",secondary_text=fw, on_press=functools.partial(copy_info, text=fw)))
details.add(rio.SimpleListItem(text="Location",secondary_text=loc, on_press=functools.partial(copy_info, text=loc)))
details.add(rio.SimpleListItem(text="Checked out?",secondary_text=checkouts, on_press=functools.partial(copy_info, text=checkouts)))
details.add(rio.SimpleListItem(text="Checkout start/end",secondary_text=checkout_times, on_press=functools.partial(copy_info, text=checkout_times)))
#details.add(rio.SimpleListItem(text="Office",secondary_text=office, on_press=functools.partial(copy_info, text=office)))
details.add(rio.SimpleListItem(text="Barcode",secondary_text=barcode, on_press=functools.partial(copy_info, text=barcode)))
details.add(rio.SimpleListItem(text="Description",secondary_text=desc, on_press=functools.partial(copy_info, text=desc)))
return rio.Card(
rio.Column(
details,
rio.Row(
rio.Button(
content="Close",
on_press=_close_dialog_info
),
rio.Button(
content="Edit",
on_press=functools.partial(self._create_dialog_edit, code=code)
),
spacing=2,
margin=2
),
spacing=1,
margin=2,
),
align_x=0.5,
align_y=0.5,
)
async def _close_dialog_info() -> None:
# This function will be called whenever the user selects an
# Item. It simply closes the dialog with the selected value.
await dialog.close()
dialog = await self.session.show_custom_dialog(
build=build_dialog_info,
# Prevent the user from interacting with the rest of the app
# while the dialog is open
modal=True,
# Don't close the dialog if the user clicks outside of it
user_closeable=True,
)
# Wait for the user to select an option
result = await dialog.wait_for_close()
# Return the selected value
return result
async def _create_dialog_edit(self, code: str) -> str | None:
itm: dict = find_item(code)
from mac_vendor_lookup import AsyncMacLookup
try:
self.elocation: str = itm["location"]
self.elocation_code: str = find_item_location(code).locationid
except:
self.elocation: str = ""
self.elocation_code: str = ""
self.epartnum: str = itm["fullname"]
self.emac: str = itm["mac"]
self.eserial: str = itm["serial"]
self.efwver: str = itm["fwver"]
self.ecode: str = code
self.popup_message: str = ""
self.popup_show: bool = False
self.popup_color: str = 'warning'
self.emacvendor: str = ""
self.emanu: str = itm["manufacturer"]
self.emanufield: str = itm["manufacturer"]
self.eoffice: str = itm["office"]
self.edescription: str = itm["description"]
async def check_mac(mac):
print("Checking", mac)
self.emac = mac
try:
macvendor = await AsyncMacLookup().lookup(mac)
if self.emanufield == "" or self.emanufield == self.emacvendor: # blank or set by MAC already
self.emanu = macvendor
self.emanufield = macvendor
else:
self.emanu = self.emanufield
self.emacvendor = macvendor
#print(macvendor)
except:
pass
# not valid MAC?
async def check_all():
await check_mac(self.emac)
# check part number
# lookup in PL_Export_rel
async def add_part():
await check_all()
if self.ecode == "":
# FAIL
self.popup_message = "\n Missing barcode! \n\n"
self.popup_show = True
self.popup_color = 'danger'
else:
# OK, add part
if update_item(self.epartnum, self.eserial, self.office, self.ecode, locationid=self.elocation_code, description=self.edescription, manufacturer=self.emanu, mac=self.emac, fwver=self.efwver) == False:
self.popup_message = "\n Unable to update! \n\n"
self.popup_show = True
self.popup_color = 'warning'
else:
self.popup_message = "\n Part updated! \n\n"
self.popup_show = True
self.popup_color = 'success'
#self.ename: str = ""
self.epartnum: str = ""
self.emac: str = ""
self.eserial: str = ""
self.efwver: str = ""
self.ecode: str = ""
self.emacvendor: str = ""
self.emanu: str = ""
self.emanufield: str = ""
self.edescription: str = ""
self.elocation: str = ""
self.elocation_code: str = ""
async def _add_part_enter(event: rio.TextInputConfirmEvent):
await add_part()
async def _add_part_button():
await add_part()
async def _update_mac(event: rio.TextInputChangeEvent):
await check_mac(event.text)
async def _update_location(event: rio.TextInputChangeEvent):
print("Checking " + event.text)
self.elocation = event.text
if get_location_id(event.text) != False:
self.elocation_code = event.text
print("Found location " + get_location_id(event.text).name)
self.elocation = get_location_id(event.text).name
async def _update_epartnum(event: rio.TextInputChangeEvent):
self.epartnum = event.text
async def _update_eserial(event: rio.TextInputChangeEvent):
self.eserial = event.text
async def _update_emanufield(event: rio.TextInputChangeEvent):
self.emanufield = event.text
self.emanu = event.text
async def _update_efwver(event: rio.TextInputChangeEvent):
self.efwver = event.text
async def _update_edescription(event: rio.TextInputChangeEvent):
self.edescription = event.text
def build_dialog_edit() -> rio.Component:
# Build the dialog
return rio.Card(
rio.Column(
rio.TextInput(
label="Barcode",
text=self.ecode,
is_sensitive=False
),
rio.TextInput(
label="Full part number",
text=self.epartnum,
on_change=_update_epartnum
),
rio.TextInput(
label="Serial",
text=self.eserial,
on_change=_update_eserial
),
rio.TextInput(
label="MAC",
text=self.emac,
on_change=_update_mac,
),
rio.TextInput(
label="Location (optional)",
text=self.elocation,
on_change=_update_location,
),
rio.TextInput(
label="Manufacturer",
text=self.emanufield,
on_change=_update_emanufield
),
rio.TextInput(
label="FW Ver",
text=self.efwver,
on_change=_update_efwver
),
rio.MultiLineTextInput(
label="Description (optional)",
text=self.edescription,
on_change=_update_edescription
),
rio.Row(
rio.Button(
content="Cancel",
on_press=_close_dialog_edit,
color='warning'
),
rio.Button(
content="Delete",
on_press=_delete_dialog_edit,
color='danger'
),
rio.Button(
content="Save",
on_press=_save_dialog_edit
),
spacing=2,
margin=2
),
spacing=1,
margin=2
),
align_x=0.5,
align_y=0.5
)
async def _close_dialog_edit() -> None:
await dialog.close()
async def _save_dialog_edit() -> None:
await dialog.close()
await _add_part_button()
await asyncio.sleep(2)
self.popup_show = False
async def _delete_dialog_edit() -> None:
await dialog.close()
dialog = await self.session.show_custom_dialog(
build=build_dialog_edit,
# Prevent the user from interacting with the rest of the app
# while the dialog is open
modal=True,
# Don't close the dialog if the user clicks outside of it
user_closeable=False,
)
# Wait for the user to select an option
result = await dialog.wait_for_close()
# Return the selected value
return result
def click_item_page(self, code):
self.session[comps.Settings].selected_item = code self.session[comps.Settings].selected_item = code
self.session.attach(self.session[comps.Settings]) self.session.attach(self.session[comps.Settings])
self.session.navigate_to("/item") self.session.navigate_to("/item")
async def click_item_dialog(self, code):
self.session[comps.Settings].selected_item = code
#self.session.attach(self.session[comps.Settings])
#self.session.navigate_to("/item")
ret = await self._create_dialog_info(code)
def build(self) -> rio.Component: def build(self) -> rio.Component:
searchview: rio.ListView = rio.ListView(height='grow') searchview: rio.ListView = rio.ListView(grow_y=True)
for item in self.items: for item in self.items:
if item["loc"] is not None: try:
loc = item["loc"]["name"] loc = item["location"]
else: except:
loc = "" loc = ""
if item["checkout"]: if item["checkout"]:
checkout = item["checkout_user"] + " - " + loc checkout = item["checkout_user"] + " - " + loc
else: else:
checkout = loc checkout = loc
searchview.add(rio.SimpleListItem(text=item["fullname"],secondary_text=(item["manufacturer"] + " - Serial: " + item["serial"] + "\n" + checkout), on_press=functools.partial( searchview.add(rio.SimpleListItem(text=item["fullname"],secondary_text=(item["manufacturer"] + " - Serial: " + item["serial"] + "\n" + checkout), on_press=functools.partial(
self.click_item, self.click_item_dialog,
code=item["barcode"]))) code=item["barcode"])))
return rio.Column( return rio.Column(
rio.Row( rio.Row(
@ -63,8 +391,21 @@ class BrowsePage(rio.Component):
) )
), ),
searchview, searchview,
rio.Popup(
anchor=rio.Text(
text="",
style='heading1',
align_x = 0.5
),
color=self.popup_color,
is_open=self.popup_show,
content=rio.Text(
text=self.popup_message,
),
),
spacing=2, spacing=2,
width=60, min_width=60,
align_x=0.5, align_x=0.5,
align_y=0, align_y=0,
) )

@ -92,7 +92,7 @@ class ItemPage(rio.Component):
align_x = 0.5, align_x = 0.5,
), ),
spacing=2, spacing=2,
width=60, min_width=60,
align_x=0.5, align_x=0.5,
align_y=0, align_y=0,
) )

@ -27,11 +27,11 @@ class RootPage(rio.Component):
# of all other components. # of all other components.
comps.Navbar(), comps.Navbar(),
# Add some empty space so the navbar doesn't cover the content. # Add some empty space so the navbar doesn't cover the content.
rio.Spacer(height=10), rio.Spacer(min_height=10),
# The page view will display the content of the current page. # The page view will display the content of the current page.
rio.PageView( rio.PageView(
# Make sure the page view takes up all available space. # Make sure the page view takes up all available space.
height="grow", grow_y=True
), ),
# The footer is also common to all pages, so place it here. # The footer is also common to all pages, so place it here.
comps.Footer(), comps.Footer(),

@ -0,0 +1,26 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
class SettingsPage(rio.Component):
"""
A sample page, which displays a humorous description of the company.
"""
def build(self) -> rio.Component:
return rio.Markdown(
"""
Belden Inventory manager v0.5
WIP
""",
min_width=60,
margin_bottom=4,
align_x=0.5,
align_y=0,
)

@ -1,6 +1,5 @@
peewee peewee
pymysql pymysql
flask rio-ui==0.10.4
rio-ui meilisearch #==0.31.5
meilisearch
mac-vendor-lookup mac-vendor-lookup

@ -1,6 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Interactions with the Meilisearch API for adding and searching cables.""" """Interactions with the Meilisearch API for adding and searching cables."""
import sys
from meilisearch import Client from meilisearch import Client
from meilisearch.task import TaskInfo from meilisearch.task import TaskInfo
from meilisearch.errors import MeilisearchApiError from meilisearch.errors import MeilisearchApiError
@ -45,6 +47,8 @@ class InventorySearch:
# make a variable to easily reference the index # make a variable to easily reference the index
self.idxref = self.client.index(self.index) self.idxref = self.client.index(self.index)
time.sleep(0.05) time.sleep(0.05)
# disable typos, we have serial numbers and such that should be exact match
self.idxref.update_typo_tolerance({'enabled': False})
# update filterable attributes if needed # update filterable attributes if needed
self.idxref.update_distinct_attribute('barcode') self.idxref.update_distinct_attribute('barcode')
self.update_filterables(filterable_attrs) self.update_filterables(filterable_attrs)
@ -109,4 +113,4 @@ class InventorySearch:
# entrypoint # entrypoint
if __name__ == "__main__": if __name__ == "__main__":
jbs = InventorySearch() ivs = InventorySearch()

Loading…
Cancel
Save