Publish to git

This commit is contained in:
Cole Deck
2024-09-03 16:50:04 -05:00
commit bfebba3e9e
22 changed files with 1440 additions and 0 deletions

72
inventory/__init__.py Normal file
View File

@ -0,0 +1,72 @@
from __future__ import annotations
from pathlib import Path
from typing import * # type: ignore
import rio
from . import pages
from . import components as comps
from db_classes import *
# Define a theme for Rio to use.
#
# You can modify the colors here to adapt the appearance of your app or website.
# The most important parameters are listed, but more are available! You can find
# them all in the docs
#
# https://rio.dev/docs/api/theme
theme = rio.Theme.from_colors(
secondary_color=rio.Color.from_hex("004990ff"),
primary_color=rio.Color.from_hex("004990ff"),
neutral_color=rio.Color.from_hex("b1cad8FF"),
light=True,
)
init()
# Create a dataclass that inherits from rio.UserSettings. This indicates to
# Rio that these are settings and should be persisted.
# Create the Rio app
app = rio.App(
name='Inventory',
default_attachments=[
comps.Settings(),
],
pages=[
rio.Page(
name="Home",
page_url='',
build=pages.BrowsePage,
),
rio.Page(
name="AboutPage",
page_url='about-page',
build=pages.AboutPage,
),
rio.Page(
name="AddPage",
page_url='add',
build=pages.AddPage,
),
rio.Page(
name="ItemPage",
page_url='item',
build=pages.ItemPage,
),
],
# You can optionally provide a root component for the app. By default,
# a simple `rio.PageView` is used. By providing your own component, you
# can create components which stay put while the user navigates between
# pages, such as a navigation bar or footer.
#
# When you do this, make sure your component contains a `rio.PageView`
# so the currently active page is still visible.
build=pages.RootPage,
theme=theme,
assets_dir=Path(__file__).parent / "assets",
)

View File

@ -0,0 +1,5 @@
from .footer import Footer
from .navbar import Navbar
from .testimonial import Testimonial
from .item import Item
from .settings import Settings

View File

@ -0,0 +1,32 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
class Footer(rio.Component):
"""
A simple, static component which displays a footer with the company name and
website name.
"""
def build(self) -> rio.Component:
return rio.Card(
content=rio.Column(
rio.Icon("rio/logo:fill", width=5, height=5),
rio.Text("Buzzwordz Inc."),
rio.Text(
"Hyper Dyper Website",
style="dim",
),
spacing=1,
margin=2,
align_x=0.5,
),
color="hud",
corner_radius=0,
)

View File

@ -0,0 +1,24 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
from db_classes import *
class Item(rio.Component):
itemcode: str = ""
def build(self) -> rio.Component:
if 'current_item' in self.session:
self.itemcode = self.session['current_item']
return rio.Card(
rio.Markdown(
self.markdown,
margin=2,
)
)

View File

@ -0,0 +1,163 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
class Navbar(rio.Component):
"""
A navbar with a fixed position and responsive width.
"""
# Make sure the navbar will be rebuilt when the app navigates to a different
# page. While Rio automatically detects state changes and rebuilds
# components as needed, navigating to other pages is not considered a state
# change, since it's not stored in the component.
#
# Instead, we can use Rio's `on_page_change` event to trigger a rebuild of
# the navbar when the page changes.
@rio.event.on_page_change
async def on_page_change(self) -> None:
# Rio comes with a function specifically for this. Whenever Rio is
# unable to detect a change automatically, use this function to force a
# refresh.
self.office = self.session[comps.Settings].office
#print(self.office)
await self.force_refresh()
checkpage: str = "in"
office: str = ""
def sub_page(self, event: rio.DropdownChangeEvent):
page = event.value
self.session.navigate_to("/" + page)
self.checkpage = "n/a"
def set_office(self, event: rio.DropdownChangeEvent):
settings = self.session[comps.Settings]
settings.office = event.value
self.session.attach(self.session[comps.Settings])
#print(settings.office)
self.office = event.value
@rio.event.on_populate
def set_office_init(self):
self.office = self.session[comps.Settings].office
print(self.office)
def build(self) -> rio.Component:
# Which page is currently active? This will be used to highlight the
# correct navigation button.
#
# `active_page_instances` contains the same `rio.Page` instances that
# 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_page = self.session.active_page_instances[0]
active_page_url_segment = active_page.page_url
# The navbar should appear above all other components. This is easily
# done by using a `rio.Overlay` component.
return rio.Overlay(
rio.Row(
rio.Spacer(),
# Use a card for visual separation
rio.Rectangle(
content=rio.Row(
# Links can be used to navigate to other pages and
# external URLs. You can pass either a simple string, or
# another component as their content.
rio.Link(
rio.Button(
"Browse",
icon="material/info",
style=(
"major"
if active_page_url_segment == ""
else "plain"
),
),
"/",
),
# This spacer will take up any superfluous space,
# effectively pushing the subsequent buttons to the
# right.
rio.Spacer(),
# By sticking buttons into a `rio.Link`, we can easily
# make the buttons navigate to other pages, without
# having to write an event handler. Notice how there is
# no Python function called when the button is clicked.
rio.Dropdown(
options={
"ALL": "all",
"US-CHI": "us-chi",
"US-SC": "us-sc",
"DE-NT": "de-nt",
"CN-SHA": "cn-sha",
"IN-BAN": "in-ban"
},
on_change=self.set_office,
selected_value=self.bind().office,
),
rio.Dropdown(
options={
"Scan...": "n/a",
"Check in": "in",
"Check out": "out"
},
on_change=self.sub_page,
selected_value=self.bind().checkpage,
),
rio.Link(
rio.Button(
"Add",
icon="material/news",
style=(
"major"
if active_page_url_segment == "add"
else "plain"
),
),
"/add",
),
# Same game, different button
rio.Link(
rio.Button(
"About",
icon="material/info",
style=(
"major"
if active_page_url_segment == "about-page"
else "plain"
),
),
"/about-page",
),
spacing=1,
margin=1,
),
fill=self.session.theme.neutral_color,
corner_radius=self.session.theme.corner_radius_medium,
shadow_radius=0.8,
shadow_color=self.session.theme.shadow_color,
shadow_offset_y=0.2,
),
rio.Spacer(),
# Proportions are an easy way to make the navbar's size relative
# to the screen. This assigns 5% to the first spacer, 90% to the
# navbar, and 5% to the second spacer.
proportions=(0.5, 9, 0.5),
# Overlay assigns the entire screen to its child component.
# Since the navbar isn't supposed to take up all space, assign
# an alignment.
align_y=0,
margin=2,
)
)

View File

@ -0,0 +1,14 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
class Settings(rio.UserSettings):
language: str = "en"
office: str = "us-chi"
selected_item: str = ""

View File

@ -0,0 +1,56 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
class Testimonial(rio.Component):
"""
Displays 100% legitimate testimonials from real, totally not made-up people.
"""
# The quote somebody has definitely said about this company.
quote: str
# Who said the quote, probably Mark Twain.
name: str
# The company the person is from.
company: str
def build(self) -> rio.Component:
# Wrap everything in a card to make it stand out from the background.
return rio.Card(
# A second card, but this one is offset a bit. This allows the outer
# card to pop out a bit, displaying a nice colorful border at the
# bottom.
rio.Card(
# Combine the quote, name, and company into a column.
rio.Column(
rio.Markdown(self.quote),
rio.Text(
f"{self.name}",
justify="left",
),
rio.Text(
f"{self.company}",
# Dim text and icons are used for less important
# information and make the app more visually appealing.
style="dim",
justify="left",
),
spacing=0.4,
margin=2,
align_y=0.5,
),
margin_bottom=0.2,
),
# Important colors such as primary, secondary, neutral and
# background are available as string constants for easy access.
color="primary",
width=20,
)

View File

@ -0,0 +1,7 @@
from .root_page import RootPage
from .about_page import AboutPage
from .add_page import AddPage
from .browse_page import BrowsePage
from .login_page import LoginPage
from .item_page import ItemPage

View File

@ -0,0 +1,63 @@
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,
)

247
inventory/pages/add_page.py Normal file
View File

@ -0,0 +1,247 @@
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
class AddPage(rio.Component):
partnum: str = ""
mac: str = ""
serial: str = ""
fwver: str = ""
code: str = ""
popup_message: str = ""
popup_show: bool = False
popup_color: str = 'warning'
date: datetime.date = datetime.date.today()
tz: datetime.tzinfo = datetime.tzinfo()
time: datetime.datetime = datetime.datetime.now()
time_start: str = datetime.datetime.now().strftime(format="%H:%M")
macvendor: str = ""
manu: str = ""
manufield: str = ""
office: str = ""
@rio.event.periodic(1)
def set_office_init(self):
self.office = self.session[comps.Settings].office
#print("Populated:", self.office)
async def check_mac(self,mac):
print("Checking", mac)
try:
macvendor = await AsyncMacLookup().lookup(mac)
if self.manufield == "" or self.manufield == self.macvendor: # blank or set by MAC already
self.manu = macvendor
self.manufield = macvendor
self.macvendor = macvendor
#print(macvendor)
except:
pass
# not valid MAC?
async def check_all(self):
await self.check_mac(self.mac)
# check part number
# lookup in PL_Export_rel
async def add_part(self):
await self.check_all()
if self.code == "":
# FAIL
self.popup_message = "\n Missing barcode! \n\n"
self.popup_show = True
self.popup_color = 'danger'
else:
# 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:
self.popup_message = "\n Duplicate barcode! \n\n"
self.popup_show = True
self.popup_color = 'warning'
else:
self.popup_message = "\n Part added! \n\n"
self.popup_show = True
self.popup_color = 'success'
self.name: str = ""
self.partnum: str = ""
self.mac: str = ""
self.serial: str = ""
self.fwver: str = ""
self.code: str = ""
self.macvendor: str = ""
self.manu: str = ""
self.manufield: str = ""
async def _add_part_enter(self, event: rio.TextInputConfirmEvent):
await self.add_part()
async def _add_part_button(self):
await self.add_part()
@rio.event.periodic(1)
def update_time_view(self):
self.time_start = self.session.timezone.fromutc(datetime.datetime.now()).now().strftime(format="%m/%d/%Y %H:%M:%S")
self.time = datetime.datetime.now()
def _set_time(self, event: rio.TextInputChangeEvent):
time_str = event.text
try:
time_obj = datetime.datetime.strptime(time_str, "%H:%M").time()
dt_with_time = datetime.datetime.combine(self.date, time_obj)
dt_with_tz = dt_with_time.replace(tzinfo=self.session.timezone)
self.time = dt_with_tz
#event.text =
#print(self.time)
except ValueError:
pass
async def _update_mac(self, event: rio.TextInputChangeEvent):
await self.check_mac(event.text)
def _update_partnum(self, event: rio.TextInputChangeEvent):
def __find_hm_header(txt):
searchlist = ["RSPS", "RSPE", "RSP", "RSB", "LRS", "RS", "OS", "RED", "MSP", "MSM", "MS", "MM", "EESX", "EES", "OZD", "OBR"]
for header in searchlist:
if txt.find(header) >= 0:
return txt.find(header) + len(header) + 2
def __find_header_general(txt):
acount = 0
ncount = 0
dash = False
for char in txt:
if char == '-':
dash = True
break
if char.isdigit():
ncount += 1
if char.isalpha():
acount += 1
if dash and acount <= 5 and ncount <= 5:
return acount+ncount
return -1
def __find_hm_fwver(txt):
a = txt.find(".")
if a > 5:
return a-2
return -1
def __find_hm_fwmode(txt):
a = __find_hm_fwver(txt)
#print(a)
if txt.find("BRS") == 0:
a -= 1
if txt[a] in ['S', 'A']:
return txt[a]
a = __find_hm_fwver(txt)
if txt.find("GRS") == 0:
a -= 4
if txt[a:a+2] in ['2S', '2A', '3S', '3A']: # 1040
return txt[a:a+2]
elif txt[a+2:a+4] in ['2S', '2A', '3S', '3A']: # 1020/30
return txt[a+2:a+4]
a = __find_hm_fwver(txt)
if txt.find("RSP") == 0 or txt.find("OS") == 0 or txt.find("MSP") == 0 or txt.find("EESX") == 0 or txt.find("RED"):
a -= 2
if txt[a:a+2] in ['2S', '2A', '3S', '3A']:
return txt[a:a+2]
a = __find_hm_fwver(txt)
if txt.find("RS") == 0 or txt.find("MS") == 0:
a -= 3
print(txt,txt[a])
if txt[a] in ['P', 'E']:
return txt[a]
a = __find_hm_fwver(txt)
if txt.find("EAGLE") == 0:
a -= 2
if txt[a:a+2] in ['3F', 'MB', 'IN', 'UN', 'OP', '01', 'SU', 'NF']:
return txt[a:a+2]
print("Failed to match software level", repr(txt))
return ""
pn = event.text
self.name = ""
if __find_header_general(pn) >= 0:
# hirschmann switch detected
self.name = pn[0:__find_header_general(pn)]
if __find_hm_fwver(pn) >= 0:
self.fwver = pn[__find_hm_fwver(pn):]
if len(__find_hm_fwmode(pn)) > 0:
self.fwver += " SWL-" + __find_hm_fwmode(pn)
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="Full part number",
text=self.bind().partnum,
on_change=self._update_partnum
),
rio.TextInput(
label="Serial",
text=self.bind().serial
),
rio.TextInput(
label="MAC",
text=self.bind().mac,
on_change=self._update_mac
),
rio.TextInput(
label="Manufacturer",
text=self.bind().manufield
),
rio.TextInput(
label="FW Ver",
text=self.bind().fwver,
on_confirm=self._add_part_enter
),
rio.Row(
# rio.DateInput(
# label="Timestamp",
# value=self.bind().date
# ),
rio.TextInput(
#text=
is_sensitive=False,
text = self.bind().time_start,
#on_change=self._set_time
)
),
rio.Button(
content="Add",
on_press=self._add_part_button
),
spacing=2,
width=60,
margin_bottom=4,
align_x=0.5,
align_y=0,
)

View File

@ -0,0 +1,71 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
from db_classes import *
import functools
class BrowsePage(rio.Component):
searchtext: str = ""
items: dict = {}
office: str = ""
filters: dict = {}
@rio.event.on_populate
async def _search(self, query=searchtext):
self.office = self.session[comps.Settings].office
self.filters['office'] = self.office
self.items = search_item(query, self.filters)
await self.force_refresh()
@rio.event.periodic(1)
async def set_office_init(self):
if self.office != self.session[comps.Settings].office:
self.office = self.session[comps.Settings].office
#print(self.office)
await self._search()
async def _search_trigger(self, event: rio.TextInputChangeEvent):
await self._search(event.text)
def click_item(self, code):
self.session[comps.Settings].selected_item = code
self.session.attach(self.session[comps.Settings])
self.session.navigate_to("/item")
def build(self) -> rio.Component:
searchview: rio.ListView = rio.ListView(height='grow')
for item in self.items:
if item["loc"] is not None:
loc = item["loc"]["name"]
else:
loc = ""
if item["checkout"]:
checkout = item["checkout_user"] + " - " + loc
else:
checkout = loc
searchview.add(rio.SimpleListItem(text=item["fullname"],secondary_text=(item["manufacturer"] + " - Serial: " + item["serial"] + "\n" + checkout), on_press=functools.partial(
self.click_item,
code=item["barcode"])))
return rio.Column(
rio.Row(
rio.TextInput(
text=self.bind().searchtext,
on_change=self._search_trigger,
label="Search"
)
),
searchview,
spacing=2,
width=60,
align_x=0.5,
align_y=0,
)

View File

@ -0,0 +1,101 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
from db_classes import *
import functools
class ItemPage(rio.Component):
itm: dict = {}
barcode: str = ""
@rio.event.on_populate
async def _get(self):
self.barcode = self.session[comps.Settings].selected_item
self.itm = find_item(self.barcode)
#print(find_item(self.barcode))
#print(self.itm)
await self.force_refresh()
def build(self) -> rio.Component:
if 'barcode' in self.itm:
if self.itm["loc"] is not None:
loc = self.itm["loc"]["name"]
else:
loc = ""
if self.itm["checkout"]:
checkout = self.itm["checkout_user"] + " - " + loc
else:
checkout = loc
# searchview.add(rio.SimpleListItem(text=item["fullname"],secondary_text=(item["manufacturer"] + " - Serial: " + item["serial"] + "\n" + checkout), on_press=functools.partial(
# self.click_item,
# file=item["barcode"])))
return rio.Column(
rio.Text(
text=str(self.itm["fullname"]),
style='heading1',
align_x = 0.5
),
rio.Text(
text=str(self.itm["manufacturer"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="Serial: " + str(self.itm["serial"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="MAC: " + str(self.itm["mac"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="FW Version: " + str(self.itm["fwver"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="Location: " + str(checkout),
style='heading2',
align_x = 0.5
),
rio.Text(
text="Checked out?: " + str(self.itm["checkout"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="Checkout start/end: " + str(self.itm["checkout_start"]) + " to " + str(self.itm["checkout_end"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="Office: " + str(self.itm["office"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="Barcode: " + str(self.itm["barcode"]),
style='heading2',
align_x = 0.5
),
rio.Text(
text="Description: " + str(self.itm["description"]),
style='heading2',
align_x = 0.5,
),
spacing=2,
width=60,
align_x=0.5,
align_y=0,
)
else:
return rio.Text("This item does not exist!")

View File

@ -0,0 +1,49 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
import datetime
from .. import components as comps
class LoginPage(rio.Component):
name: str = ""
def build(self) -> rio.Component:
return rio.Column(
rio.Popup(
anchor=rio.Text(
text="Login",
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="User",
text=self.bind().code
),
rio.TextInput(
label="Password",
text=self.bind().partnum
),
rio.Button(
content="Login",
on_press=self._add_part_button
),
spacing=2,
width=60,
margin_bottom=4,
align_x=0.5,
align_y=0,
)

View File

@ -0,0 +1,39 @@
from __future__ import annotations
from dataclasses import KW_ONLY, field
from typing import * # type: ignore
import rio
from .. import components as comps
class RootPage(rio.Component):
"""
This page will be used as the root component for the app. This means, that
it will always be visible, regardless of which page is currently active.
This makes it the perfect place to put components that should be visible on
all pages, such as a navbar or a footer.
Additionally, the root page will contain a `rio.PageView`. Page views don't
have any appearance on their own, but they are used to display the content
of the currently active page. Thus, we'll always see the navbar and footer,
with the content of the current page in between.
"""
def build(self) -> rio.Component:
return rio.Column(
# The navbar contains a `rio.Overlay`, so it will always be on top
# of all other components.
comps.Navbar(),
# Add some empty space so the navbar doesn't cover the content.
rio.Spacer(height=10),
# The page view will display the content of the current page.
rio.PageView(
# Make sure the page view takes up all available space.
height="grow",
),
# The footer is also common to all pages, so place it here.
comps.Footer(),
)