1
0
Fork 1
mirror of https://github.com/thatmattlove/hyperglass.git synced 2026-01-17 08:48:05 +00:00

Implement device description and avatar

This commit is contained in:
thatmattlove 2021-09-24 01:04:28 -07:00
parent 89568dc8e5
commit 22ae6a97e8
7 changed files with 55 additions and 12 deletions

View file

@ -21,7 +21,7 @@ per-file-ignores=
hyperglass/**/test_*.py:S101,D103
hyperglass/api/*.py:B008
hyperglass/state/hooks.py:F811
ignore=W503,C0330,R504,D202,S403,S301,S404,E731,D402
ignore=W503,C0330,R504,D202,S403,S301,S404,E731,D402,IF100
select=B, BLK, C, D, E, F, I, II, N, P, PIE, S, R, W
disable-noqa=False
hang-closing=False

View file

@ -75,7 +75,6 @@ def setup_lib_logging(log_level: str) -> None:
"""
intercept_handler = LibIntercentHandler()
logging.root.setLevel(log_level)
seen = set()
for name in [

View file

@ -7,7 +7,7 @@ from pathlib import Path
from ipaddress import IPv4Address, IPv6Address
# Third Party
from pydantic import StrictInt, StrictStr, StrictBool, validator
from pydantic import FilePath, StrictInt, StrictStr, StrictBool, validator
# Project
from hyperglass.log import log
@ -43,6 +43,8 @@ class Device(HyperglassModelWithId, extra="allow"):
id: StrictStr
name: StrictStr
description: Optional[StrictStr]
avatar: Optional[FilePath]
address: Union[IPv4Address, IPv6Address, StrictStr]
group: Optional[StrictStr]
credential: Credential
@ -157,6 +159,28 @@ class Device(HyperglassModelWithId, extra="allow"):
)
return value
@validator("avatar")
def validate_avatar(
cls, value: Union[FilePath, None], values: Dict[str, Any]
) -> Union[FilePath, None]:
"""Migrate avatar to static directory."""
if value is not None:
# Standard Library
import shutil
# Third Party
from PIL import Image
target = Settings.static_path / "images" / value.name
copied = shutil.copy2(value, target)
log.debug("Copied {} avatar from {!r} to {!r}", values["name"], str(value), str(target))
with Image.open(copied) as src:
if src.width > 512:
src.thumbnail((512, 512 * src.height / src.width))
src.save(target)
return value
@validator("structured_output", pre=True, always=True)
def validate_structured_output(cls, value: bool, values: Dict) -> bool:
"""Validate structured output is supported on the device & set a default."""
@ -300,9 +324,13 @@ class Devices(MultiModel, model=Device, unique_by="id"):
"group": group,
"locations": [
{
"group": group,
"id": device.id,
"name": device.name,
"group": group,
"avatar": f"/images/{device.avatar.name}"
if device.avatar is not None
else None,
"description": device.description,
"directives": [d.frontend(params) for d in device.directives],
}
for device in self

View file

@ -43,6 +43,8 @@ class UILocation(HyperglassModel):
id: StrictStr
name: StrictStr
group: StrictStr
avatar: Optional[StrictStr]
description: Optional[StrictStr]
directives: List[UIDirective] = []

View file

@ -14,11 +14,18 @@ function buildOptions(devices: DeviceGroup[]): OptionGroup[] {
.map(group => {
const label = group.group;
const options = group.locations
.map(loc => ({
label: loc.name,
value: loc.id,
group: loc.group,
}))
.map(
loc =>
({
label: loc.name,
value: loc.id,
group: loc.group,
data: {
avatar: loc.avatar,
description: loc.description,
},
} as SingleOption),
)
.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
return { label, options };
})
@ -67,6 +74,7 @@ const LocationCard = (props: LocationCardProps): JSX.Element => {
px={6}
bg={bg}
w="100%"
minW="xs"
maxW="sm"
mx="auto"
shadow="lg"
@ -102,12 +110,15 @@ const LocationCard = (props: LocationCardProps): JSX.Element => {
bg="whiteAlpha.300"
borderStyle="solid"
borderColor={imageBorder}
src={(option.data?.avatar as string) ?? undefined}
/>
</Flex>
<chakra.p mt={2} color={fg} opacity={0.6} fontSize="sm">
To do: add details field (and location image field)
</chakra.p>
{option?.data?.description && (
<chakra.p mt={2} color={fg} opacity={0.6} fontSize="sm">
{option.data.description as string}
</chakra.p>
)}
</MotionBox>
);
};

View file

@ -6,6 +6,7 @@ export type SingleOption = AnyOption & {
value: string;
group?: string;
tags?: string[];
data?: Record<string, unknown>;
};
export type OptionGroup = AnyOption & {

View file

@ -122,7 +122,9 @@ interface _Device {
id: string;
name: string;
group: string;
avatar: string | null;
directives: _Directive[];
description: string | null;
}
interface _QueryContent {