Save
This commit is contained in:
parent
d411978b67
commit
ff284125db
8
.env
8
.env
|
|
@ -1,6 +1,7 @@
|
|||
UID=1000
|
||||
GID=1004
|
||||
DIR=/srv/docker
|
||||
DIR_LOCAL=/opt/docker_data
|
||||
DOMAIN=milvert.com
|
||||
DOMAIN_PEEK=peekskog.se
|
||||
|
||||
|
|
@ -51,3 +52,10 @@ VW_DB_NAME='vwfriend'
|
|||
VW_DB_USER='vwfriend'
|
||||
VW_DB_PASSWORD='icCJ8iwKJBeRBg'
|
||||
ADDITIONAL_PARAMETERS=-vv --mqttbroker mqtt --mqttport 1883 -mu simon -mp bajsa123
|
||||
|
||||
|
||||
DEBUG_ABOVE=--mqttbroker mqtt --mqttport 1883 -mu simon -mp bajsa123
|
||||
|
||||
UPTIME_KUMA_PASSWORD=bajsa123
|
||||
UPTIME_KUMA_USER=simon@milvert.com
|
||||
|
||||
|
|
|
|||
|
|
@ -18,19 +18,23 @@ dns:
|
|||
port: 53
|
||||
anonymize_client_ip: false
|
||||
ratelimit: 20
|
||||
ratelimit_subnet_len_ipv4: 24
|
||||
ratelimit_subnet_len_ipv6: 56
|
||||
ratelimit_whitelist: []
|
||||
refuse_any: true
|
||||
upstream_dns:
|
||||
- https://dns10.quad9.net/dns-query
|
||||
- https://dns.cloudflare.com/dns-query
|
||||
- https://dns.google/dns-query
|
||||
- https://security.cloudflare-dns.com/dns-query
|
||||
upstream_dns_file: ""
|
||||
bootstrap_dns:
|
||||
- 9.9.9.10
|
||||
- 149.112.112.10
|
||||
- 2620:fe::10
|
||||
- 2620:fe::fe:10
|
||||
fallback_dns: []
|
||||
all_servers: false
|
||||
fastest_addr: false
|
||||
fallback_dns:
|
||||
- 1.1.1.2
|
||||
upstream_mode: load_balance
|
||||
fastest_timeout: 1s
|
||||
allowed_clients: []
|
||||
disallowed_clients: []
|
||||
|
|
@ -59,12 +63,14 @@ dns:
|
|||
bootstrap_prefer_ipv6: false
|
||||
upstream_timeout: 10s
|
||||
private_networks: []
|
||||
use_private_ptr_resolvers: true
|
||||
use_private_ptr_resolvers: false
|
||||
local_ptr_upstreams: []
|
||||
use_dns64: false
|
||||
dns64_prefixes: []
|
||||
serve_http3: false
|
||||
use_http3_upstreams: false
|
||||
serve_plain_dns: true
|
||||
hostsfile_enabled: true
|
||||
tls:
|
||||
enabled: false
|
||||
server_name: ""
|
||||
|
|
@ -81,12 +87,14 @@ tls:
|
|||
private_key_path: ""
|
||||
strict_sni_check: false
|
||||
querylog:
|
||||
dir_path: ""
|
||||
ignored: []
|
||||
interval: 2160h
|
||||
size_memory: 1000
|
||||
enabled: true
|
||||
file_enabled: true
|
||||
statistics:
|
||||
dir_path: ""
|
||||
ignored: []
|
||||
interval: 24h
|
||||
enabled: true
|
||||
|
|
@ -161,6 +169,7 @@ clients:
|
|||
hosts: true
|
||||
persistent: []
|
||||
log:
|
||||
enabled: true
|
||||
file: ""
|
||||
max_backups: 0
|
||||
max_size: 100
|
||||
|
|
@ -172,4 +181,4 @@ os:
|
|||
group: ""
|
||||
user: ""
|
||||
rlimit_nofile: 0
|
||||
schema_version: 27
|
||||
schema_version: 28
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
###############################################################
|
||||
# Authelia configuration #
|
||||
###############################################################
|
||||
server:
|
||||
address: 'tcp://:9091'
|
||||
|
||||
server.host: 0.0.0.0
|
||||
server.port: 9091
|
||||
log:
|
||||
level: info
|
||||
|
||||
|
|
@ -14,6 +14,13 @@ log:
|
|||
# https://docs.authelia.com/configuration/miscellaneous.html#default-redirection-url
|
||||
default_redirection_url: https://authelia.milvert.com
|
||||
|
||||
webauthn: #FIDO2 Authentication
|
||||
disable: false
|
||||
display_name: Authelia
|
||||
attestation_conveyance_preference: direct
|
||||
user_verification: required
|
||||
timeout: 60s
|
||||
|
||||
totp:
|
||||
issuer: authelia.com
|
||||
period: 30
|
||||
|
|
@ -56,6 +63,10 @@ access_control:
|
|||
policy: two_factor
|
||||
- domain: "milvert.com"
|
||||
policy: two_factor
|
||||
- domain:
|
||||
- 'uptime.example.com'
|
||||
subject: 'oauth2:client:uptime-kuma'
|
||||
policy: 'one_factor'
|
||||
|
||||
session:
|
||||
name: authelia_session
|
||||
|
|
@ -85,10 +96,11 @@ storage:
|
|||
#path: /config/db.sqlite3
|
||||
postgres:
|
||||
# MySQL allows running multiple authelia instances. Create database and enter details below.
|
||||
host: postgres
|
||||
port: 5432
|
||||
database: authelia
|
||||
username: authelia
|
||||
address: 'tcp://postgres:5432'
|
||||
#host: postgres
|
||||
#port: 5432
|
||||
database: 'authelia'
|
||||
username: 'authelia'
|
||||
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
# password: use docker secret file instead AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE
|
||||
|
||||
|
|
@ -104,3 +116,118 @@ notifier:
|
|||
# For testing purpose, notifications can be sent in a file. Be sure map the volume in docker-compose.
|
||||
filesystem:
|
||||
filename: /tmp/authelia/notification.txt
|
||||
|
||||
|
||||
identity_providers:
|
||||
oidc:
|
||||
hmac_secret: 'akVs2Tr510MpfECDciJhpSI6SiHKhdiGefG2wMzPSuUhRlWNB0VNwDTxsFNZrRrw'
|
||||
jwks:
|
||||
- key_id: 'milvert_authelia_oidc_key'
|
||||
algorithm: 'RS256'
|
||||
use: 'sig'
|
||||
key: |
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKQIBAAKCAgEA1Yxr3WL300u245uZrBOgZtX63IwtFT9NDighnIz/PcFiYbUw
|
||||
lsrXi5HBJXuIEbxJDcdSdvPhusx08wizPuEnTLVphOgwQ8Xhab3qKLfmwW8yHGsX
|
||||
9+osJNhAzmXJXAMbjgz2Rqd7tuOT2PkyYF707FQBRlYNhcMfi06WVhlo4WFPab95
|
||||
seKmj3bSIHlmbPnrL9GqOhAtV018COCbMXM2pu6yQOTkdSltZyg5L1+QkSf2MAUN
|
||||
VjjTzWbjI8en9vQfZZjA1h7O0bpR/WOmPv9S+SdmnHE9FewOXux3aljC0qrTHeAh
|
||||
GIjq+8fzREN0Xvvqox5ZWwBKmPax4ed448Vm/8U3rZQ02Ftpr97w2inL/MT0UTmS
|
||||
wKudIlzmkuejqy0jiZ/aAX6JpE5OLsm1zhJSLJFx/vNxByh1kd2EFon22bUa5ZLF
|
||||
buVU1WMkhr1Nc8vTCgnr0Y3XKbB1koGJFwK6lg9L0Tstrt+SY34K2iRtFce/Lt2k
|
||||
KFkO+hfx3J8hRg2DcazR9bZTsjsK+OHw9sNaFMrkAf4Rd8Z27yRtSRdZXefgz88G
|
||||
1xjqgdZmjupKgRPJzCro4hbvmH6x1L8Q3ZzR5fstP8rui8m9UIsSCwLdzGlc7x7L
|
||||
toQckn+EFlZ0kLl1e3nlMDUpOaezx7TXt1OxlJtiX7MmGfhUcY+8k3+JS+0CAwEA
|
||||
AQKCAgALpHU5A61JOsaE0DXnmMZU9PORVJ8cmm/kBrIWa+V6g3GOrCczAWoQ9OhX
|
||||
181KUj6VXrmTPKag3Ya+vUmwcuwGlZlkIpiHCWHJtw3uHGCbSSmtPPV4ka7Ib3XR
|
||||
CuGYf57f9rryjS0EgpHL8YIamPK3c7kCEaz5DvNIUAeIOChsqTaAKG1FEntMNQkt
|
||||
thCsfk+hMsgaFEm0icfqX/x2DLb9EORs/02pSZHqXtoHSCmEkG4ungflHIIHn8Vg
|
||||
bQEuSI7xpgtVYSabbpILw4QLyTXH2asRemb/K/h4mmHETYSJocCSz2ZehRBym6sa
|
||||
nKbaitd2/y+V84Udo8186HbBzEBaNekr8IVDfY1NDi99ZgSUJBS0jDCUb84A6Ucf
|
||||
CRDZofvjNKV90x7wlZPz5T8N+lpDSH+ThwU0T1k8aydRA6DL/otFNfOS6vaqBdg3
|
||||
Gvpd7SQUT88u1l7rVZwsJ+uGBq9Sx+ScCnjl04jc62hDUy51hR+mOfrWNCJGqfJ9
|
||||
YJlaH2bZJuzKAyXAEYjJJuYfYPpTDVZ2glzSM72ytmPZseB6KwDJ8gZFtbdHUi0V
|
||||
eol49mOCKwBsaLjUh7rqix2WkO6yjcch17HrvpBEUZw+B0FIOPUdC2iH26lpOk0e
|
||||
QiuAPXZXcch00ta9UMBUfr8O8LVznm6L751UdaYnpNbw+2VHrQKCAQEA9soiuZYt
|
||||
jJmteohGKe2YOCOfzxirE2XqW8g5RWtWb84pEt+oZLEzrKFL4fvKYCEumfnLIrTe
|
||||
E6XrwVNbmRLxhRzPJi2ZgAqImy138BqeY9ygorUDKJP343JMBOKQvxCXF/ZvYYqn
|
||||
AXN6xt+1X2nlgBxWUJr7oqp2DJ/X7rBH2xRB8UITPInZCv9gxgRTWe5j8GToZJ2b
|
||||
S8VxgETl2IyBRE9H6knRZibD8uZKksLCPFIQdf/dkneiPTVW/PhvLzbASY3jOLJT
|
||||
O62xTkeykGEsdgAVYtBYuBrP86ujHHaqO1nGVMAYXVINNukrqXuF3n8XXCCmgFue
|
||||
Ibdus2UDct/7qwKCAQEA3YSyEVg8aSiF+cGqv1CpJFzdM169zDNxFooD7b7oj/FX
|
||||
oMZMKQ1EGcqWJ8ivseM1OYoc2FlVZF+bem1VNEDcswTEuqI7YnRJcua5vbBMBQP+
|
||||
FO0a8rbI3poctY/1hPt/oZ0twX7mBMLzljJ4kQLaC4LLQDlhhP4SriqWoXx6BfFV
|
||||
AZEbcNlzyOHGIUdA9ahyVB5isYC3ru6lZltAg+2+zHucLvNZ0H/jVAgjH8qOxoZh
|
||||
m6XILdQVdFMhZqmdLWPfFgZGqL/zc3qHrIWWvunawcIEnUZvVkTnCTSfIFvfsErJ
|
||||
jlT7hVUxNLQqed/DIsX9bz/Vj0Uj/7IOCcwBFiv+xwKCAQEAgUo4kv3IcyZ3qgAG
|
||||
siSVYKPZL0f/KDR2aQsmZeXr7LsW7ZpawBXNupzuAkBFL8NyhJxG/bG/vo9oSWoA
|
||||
TNuNyGzlYVz2VAwwsQtLEHEBldOaHGO0eee7MF9K0CxaJJ7uaVFj3Lgk8i+rnNDK
|
||||
VmhGIa220pe/SOMA4XBEUfnsSyv7uAcjyM129boA2vydJjosBV74GO4w06tm4Qo3
|
||||
WBGUD1Nxm558o4WflntriiOaWrurgAZB8F/YkTSGlBUbOqL2bhJ1fdh+nn9KqnYJ
|
||||
aHZgMpmsmo4ITLtPQpsi4uCQInPP4cqZeRppbeEOTMY3xe7TMCKy2AAnggZ1amp7
|
||||
Og157QKCAQBvfoyJwlmVrDnSvbGNXbHkhYEoi/UHxJSU/Z6AmWhAmv66A1TuDKlM
|
||||
OfVdzNrxtCRj86oVEtGyFZUSB9ms1UDAMhQ6w9ET+ufFF3BBk2yP0bSfH8BCjdGI
|
||||
iRUOJYk0B8nztEMFczOfDejAnmKkykSpKonWp4r3/1Gzq+fpG9fnCdL5WOnw4OIw
|
||||
J8MrmMuPWdtBj5GpOdo6CA/j9uYAATfZgBXaY82+7b+j2fyj0bYPIjAawVSCDI9H
|
||||
31eebpyX7f6o/TuvT/3fD7seEJcRPG9IurjL2FnNmByZO40kIlnyR5IvO4LlVz3P
|
||||
Ayel9AQpinHG/uAknm5CEoKSV8XsPPSdAoIBAQDweDT7RGHYHQp0INcQ3JxjrfN3
|
||||
PcaeVths+7KA+pAq+id1grv48Yg+bo/vfoA25SSV6UrtWBW8JUUtFcRIH+UFnQ7c
|
||||
rZkmI/l6lzdyJ3akzIJRAKvo7JGmT4NqTCjmug0Oo6feTjwuBisGRA7UFB/7gjJa
|
||||
v9IhIt51N7Dl+SHK+drYGoErbzurxCOmuE0+GCnZ2qvdafVbk6zh4U2pZ2feOfqu
|
||||
mPM3FMJdnSrXYtVWAY7hfSIsF/Ndh+kdkQi/s6TsZHqZ3PLTKWUk5ETwFpTqEXaM
|
||||
DsGaWut89Ik9YrcAQVKXez5jVJRsYXeMCznEXed6fLssXgmJT2OlrEgSQhEj
|
||||
-----END RSA PRIVATE KEY-----
|
||||
enable_client_debug_messages: true
|
||||
minimum_parameter_entropy: 8
|
||||
enforce_pkce: 'public_clients_only'
|
||||
enable_pkce_plain_challenge: false
|
||||
enable_jwt_access_token_stateless_introspection: false
|
||||
discovery_signed_response_alg: 'none'
|
||||
discovery_signed_response_key_id: ''
|
||||
require_pushed_authorization_requests: false
|
||||
authorization_policies:
|
||||
policy_name:
|
||||
default_policy: 'one_factor'
|
||||
rules:
|
||||
- policy: 'deny'
|
||||
subject: 'group:services'
|
||||
lifespans:
|
||||
access_token: '1h'
|
||||
authorize_code: '1m'
|
||||
id_token: '1h'
|
||||
refresh_token: '90m'
|
||||
cors:
|
||||
endpoints:
|
||||
- 'authorization'
|
||||
- 'token'
|
||||
- 'revocation'
|
||||
- 'introspection'
|
||||
allowed_origins:
|
||||
- 'https://authelia.milvert.com'
|
||||
allowed_origins_from_client_redirect_uris: false
|
||||
clients:
|
||||
- client_id: 'aN0VgMKamGZvleUplkT3W7kvXJmvsWmy4C9Obd6u5XGqL7A9B7CP2xxdSIv4ljIA'
|
||||
client_name: 'Grafana'
|
||||
client_secret: '$pbkdf2-sha512$310000$X53J.7eRdnYPuVSG6Uc0vw$y/PP9Wt5sHUrovp5hnXcJe6gias2t9h.PYj6iP0cMS1F2pDd98tzamSuoaU2b89vGONWdX0MaLKVs.6MFzCLEg'
|
||||
public: false
|
||||
authorization_policy: 'one_factor'
|
||||
redirect_uris:
|
||||
- 'https://data.milvert.com/login/generic_oauth'
|
||||
scopes:
|
||||
- 'openid'
|
||||
- 'profile'
|
||||
- 'groups'
|
||||
- 'email'
|
||||
userinfo_signed_response_alg: 'none'
|
||||
- client_id: 'MlMNM1K1vGR3wHBPNsZZ7J66u1cGkMGlzBoZoYJwuc80quRsjrlV9jEZlMLTTGmT'
|
||||
client_name: 'Gitea'
|
||||
client_secret: '$pbkdf2-sha512$310000$E2hUgSHeRFIhAr5bQsDAFg$1qPDiXvtmvwVhwWb./gie6F2CCI80oQQkXln.xd.q.HNVI00kn1D5esj0faJrJhHgNjV0udqrBD5SdIVD8vXow'
|
||||
public: false
|
||||
authorization_policy: 'one_factor'
|
||||
redirect_uris:
|
||||
- 'https://gitea.milvert.com/user/oauth2/Authelia/callback'
|
||||
scopes:
|
||||
- 'openid'
|
||||
- 'email'
|
||||
- 'profile'
|
||||
userinfo_signed_response_alg: 'none'
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ services:
|
|||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.middlewares.webdb-mid.ipwhitelist.sourcerange=127.0.0.1/32, 10.0.0.1/24"
|
||||
- "traefik.http.middlewares.webdb-mid.ipallowlist.sourcerange=127.0.0.1/32, 10.0.0.1/24"
|
||||
- "traefik.http.routers.webdb-secure.middlewares=webdb-mid"
|
||||
- "traefik.http.routers.webdb-secure.entrypoints=web-secure"
|
||||
- "traefik.http.routers.webdb-secure.rule=Host(`webdb.${DOMAIN}`)"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ version: '3'
|
|||
|
||||
services:
|
||||
node-red:
|
||||
image: nodered/node-red:3.0.2
|
||||
image: nodered/node-red:3.1.6
|
||||
|
||||
# image: nodered/node-red-dev:3.0.0-beta.4-14
|
||||
container_name: "node-red"
|
||||
|
|
@ -57,7 +57,7 @@ services:
|
|||
- "traefik.enable=false"
|
||||
- "traefik.http.services.landet_domo-service.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.landet_domo-secure.entrypoints=web-secure"
|
||||
- "traefik.http.routers.landet_domo-secure.rule=Host(`landet.${DOMAIN}`)"
|
||||
- "traefik.http.routers.landet_domo-secure.rule=Host(`landet_old.${DOMAIN}`)"
|
||||
- "traefik.http.routers.landet_domo-secure.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.landet_domo-secure.tls=true"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ version: '3'
|
|||
services:
|
||||
nextcloud:
|
||||
container_name: nextcloud
|
||||
image: nextcloud:25
|
||||
image: nextcloud:27
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
|
|
@ -49,9 +49,9 @@ services:
|
|||
- NEXTCLOUD_TRUSTED_DOMAINS=moln.milvert.com
|
||||
- TRUSTED_PROXIES=172.19.0.0/16
|
||||
volumes:
|
||||
- ${DIR}/nextcloud-www:/var/www/html
|
||||
# - ${DIR}/nextcloud-www:/var/www/html
|
||||
- ./nextcloud/cronjob:/var/spool/cron/crontabs/www-data
|
||||
- /srv/owncloud:/var/www/html/data
|
||||
#- /srv/owncloud:/var/www/html/data
|
||||
- /media/NAS:/media/NAS
|
||||
|
||||
networks:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ services:
|
|||
max-file: "3"
|
||||
networks:
|
||||
- backend
|
||||
image: koenkk/zigbee2mqtt:1.33.2
|
||||
ports:
|
||||
- "8088:8080"
|
||||
image: koenkk/zigbee2mqtt:2.1
|
||||
restart: always
|
||||
volumes:
|
||||
- ./zigbee_home_2:/app/data
|
||||
|
|
@ -41,7 +43,7 @@ services:
|
|||
max-file: "5"
|
||||
networks:
|
||||
- backend
|
||||
image: koenkk/zigbee2mqtt:1.33.2
|
||||
image: koenkk/zigbee2mqtt:2.1
|
||||
restart: always
|
||||
volumes:
|
||||
- ${DIR}/zigbee2matt:/app/data
|
||||
|
|
@ -65,7 +67,7 @@ services:
|
|||
|
||||
|
||||
influx:
|
||||
image: influxdb:2.6
|
||||
image: influxdb:2.7
|
||||
container_name: influxdb
|
||||
logging:
|
||||
driver: "json-file"
|
||||
|
|
@ -136,7 +138,7 @@ services:
|
|||
#- "traefik.http.routers.influx-secure.middlewares=localNetwork@file"
|
||||
|
||||
gitea:
|
||||
image: gitea/gitea:1.17
|
||||
image: gitea/gitea:1.21
|
||||
container_name: gitea
|
||||
logging:
|
||||
driver: "json-file"
|
||||
|
|
@ -171,7 +173,7 @@ services:
|
|||
|
||||
mqtt:
|
||||
# image: eclipse-mosquitto:1.6.13
|
||||
image: eclipse-mosquitto:2.0.17
|
||||
image: eclipse-mosquitto:2.0.18
|
||||
container_name: mqtt
|
||||
logging:
|
||||
driver: "json-file"
|
||||
|
|
@ -248,71 +250,43 @@ services:
|
|||
labels:
|
||||
- diun.enable=true
|
||||
|
||||
ddns-updater:
|
||||
image: qmcgaw/ddns-updater:256
|
||||
container_name: ddns-updater
|
||||
restart: always
|
||||
networks:
|
||||
- backend
|
||||
ports:
|
||||
- 8000:8000/tcp
|
||||
volumes:
|
||||
- ${DIR}/ddns-updater:/updater/data
|
||||
environment:
|
||||
PUID: 1000
|
||||
PGID: 1004
|
||||
TZ: ${TZ}
|
||||
PERIOD: 1h
|
||||
UPDATE_COOLDOWN_PERIOD: 5m
|
||||
PUBLICIP_DNS_TIMEOUT: 3s
|
||||
HTTP_TIMEOUT: 10s
|
||||
# Web UI
|
||||
LISTENING_PORT: 8000
|
||||
# Backup
|
||||
BACKUP_PERIOD: 96h # 0 to disable
|
||||
BACKUP_DIRECTORY: /updater/data/backups
|
||||
# Other
|
||||
LOG_LEVEL: info
|
||||
SHOUTRRR_ADDRESSES: $TGRAM_SHOUTRRR_ADDRESS
|
||||
labels:
|
||||
- diun.enable=true
|
||||
|
||||
pihole:
|
||||
image: pihole/pihole:2023.03.1
|
||||
container_name: pihole
|
||||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
- "8001:80"
|
||||
dns:
|
||||
- 127.0.0.1
|
||||
- 9.9.9.9
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- WEBPASSWORD=${PIHOLE_PW}
|
||||
- ServerIP=10.0.201
|
||||
- PIHOLE_DNS_=127.0.0.1;9.9.9.9;149.112.112.112;1.1.1.1
|
||||
- DNSSEC='true'
|
||||
- VIRTUAL_HOST=pihole.milvert.com # Same as port traefik config
|
||||
- WEBTHEME=default-dark
|
||||
- PIHOLE_DOMAIN=milvert.com
|
||||
volumes:
|
||||
- ${DIR}/pihole/etc:/etc/pihole
|
||||
- ${DIR}/pihole/dns:/etc/dnsmasq.d
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
backend:
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.middlewares.pihole-admin.addprefix.prefix=/admin"
|
||||
- "traefik.http.services.pihole.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.pihole-secure.middlewares=localNetwork@file"
|
||||
- "traefik.http.routers.pihole-secure.entrypoints=web-secure"
|
||||
- "traefik.http.routers.pihole-secure.rule=Host(`pihole.${DOMAIN}`)"
|
||||
- "traefik.http.routers.pihole-secure.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.pihole-secure.tls=true"
|
||||
- "traefik.http.routers.pihole-secure.middlewares=chain-no-auth@file"
|
||||
#pihole:
|
||||
#image: pihole/pihole:2023.03.1
|
||||
#container_name: pihole
|
||||
#ports:
|
||||
#- "53:53/tcp"
|
||||
#- "53:53/udp"
|
||||
#- "8001:80"
|
||||
#dns:
|
||||
#- 127.0.0.1
|
||||
#- 9.9.9.9
|
||||
#environment:
|
||||
#- TZ=${TZ}
|
||||
#- WEBPASSWORD=${PIHOLE_PW}
|
||||
#- ServerIP=10.0.201
|
||||
#- PIHOLE_DNS_=127.0.0.1;9.9.9.9;149.112.112.112;1.1.1.1
|
||||
#- DNSSEC='true'
|
||||
##- VIRTUAL_HOST=pihole.milvert.com # Same as port traefik config
|
||||
#- WEBTHEME=default-dark
|
||||
#- PIHOLE_DOMAIN=milvert.com
|
||||
#volumes:
|
||||
#- ${DIR}/pihole/etc:/etc/pihole
|
||||
#- ${DIR}/pihole/dns:/etc/dnsmasq.d
|
||||
#restart: unless-stopped
|
||||
#networks:
|
||||
#backend:
|
||||
#labels:
|
||||
#- diun.enable=true
|
||||
#- "traefik.enable=true"
|
||||
#- "traefik.http.middlewares.pihole-admin.addprefix.prefix=/admin"
|
||||
#- "traefik.http.services.pihole.loadbalancer.server.port=80"
|
||||
#- "traefik.http.routers.pihole-secure.middlewares=localNetwork@file"
|
||||
#- "traefik.http.routers.pihole-secure.entrypoints=web-secure"
|
||||
#- "traefik.http.routers.pihole-secure.rule=Host(`pihole.${DOMAIN}`)"
|
||||
#- "traefik.http.routers.pihole-secure.tls.certresolver=milvert_dns"
|
||||
#- "traefik.http.routers.pihole-secure.tls=true"
|
||||
#- "traefik.http.routers.pihole-secure.middlewares=chain-no-auth@file"
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ services:
|
|||
image: containous/whoami
|
||||
networks:
|
||||
- backend
|
||||
ports:
|
||||
- 8005:80
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
version: "3.9"
|
||||
|
||||
secrets:
|
||||
authelia_jwt_secret:
|
||||
file: $SECRETSDIR/authelia_jwt_secret
|
||||
authelia_oidc_key_secret:
|
||||
file: $SECRETSDIR/authelia_oidc_key
|
||||
authelia_oidc_hamc_secret:
|
||||
file: $SECRETSDIR/authelia_oidc_hamc
|
||||
authelia_oidc_pem_secret:
|
||||
file: $SECRETSDIR/authelia_oidc_pem
|
||||
authelia_session_secret:
|
||||
file: $SECRETSDIR/authelia_session_secret
|
||||
authelia_storage_postgres_password:
|
||||
|
|
@ -32,15 +37,13 @@ x-common-keys-monitoring: &common-keys-monitoring
|
|||
networks:
|
||||
- backend
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: always
|
||||
|
||||
- no-new-privileges:true restart: always
|
||||
services:
|
||||
|
||||
|
||||
reverse-proxy:
|
||||
# The official v2.0 Traefik docker image
|
||||
image: traefik:v2.10
|
||||
#image: traefik:v2.11
|
||||
image: traefik:v3.1
|
||||
container_name: "traefik"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
|
|
@ -55,6 +58,8 @@ services:
|
|||
# The HTTP port
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# Insecure port
|
||||
- "8080:8080"
|
||||
# Influx
|
||||
- "8086:8086"
|
||||
# Mqtt
|
||||
|
|
@ -63,23 +68,23 @@ services:
|
|||
volumes:
|
||||
# So that Traefik can listen to the Docker events
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${DIR_LOCAL}/traefik/log:/log:rw
|
||||
- ./traefik.yml:/etc/traefik/traefik.yml
|
||||
- ./traefik:/rules
|
||||
- ./static_config.yml:/etc/traefik/static_config.yml
|
||||
# - "./log.json:/etc/traefik/log.json"
|
||||
#- ./acme.json:/acme.json
|
||||
- ./letsencrypt/acme.json:/letsencrypt/acme.json
|
||||
- ${DIR}/traefik/log:/log
|
||||
# - "./log.json:/etc/traefik/log.json"
|
||||
# - ./acme.json:/acme.json
|
||||
- ./letsencrypt/:/letsencrypt:rw
|
||||
# - ./letsencrypt/acme_peek_staged.json:/letsencrypt/acme_peek_staged.json
|
||||
environment:
|
||||
- CF_API_EMAIL=simon@milvert.com
|
||||
- CF_API_KEY=48d9ae3752afb6e73d99d23c432ba8e38b24c
|
||||
#- CF_DNS_API_TOKEN=48d9ae3752afb6e73d99d23c432ba8e38b24c
|
||||
- CF_DNS_API_TOKEN=m-X93yWXyvQ2vDhfNLURcQTWOqle13aBbw7g2Zxg
|
||||
- CLOUDFLARE_IPS
|
||||
- LOCAL_IPS
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.http.routers.zighome-secure.middlewares=chain-authelia@file"
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
|
||||
authelia:
|
||||
image: authelia/authelia:4
|
||||
|
|
@ -96,12 +101,16 @@ services:
|
|||
- TZ=$TZ
|
||||
- PUID=$PUID
|
||||
- PGID=$PGID
|
||||
- AUTHELIA_JWT_SECRET_FILE=/run/secrets/authelia_jwt_secret
|
||||
- AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE=/run/secrets/authelia_jwt_secret
|
||||
- AUTHELIA_SESSION_SECRET_FILE=/run/secrets/authelia_session_secret
|
||||
- AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE=/run/secrets/authelia_storage_postgres_password
|
||||
#- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/run/secrets/authelia_notifier_smtp_password
|
||||
- AUTHELIA_DUO_API_SECRET_KEY_FILE=/run/secrets/authelia_duo_api_secret_key
|
||||
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/run/secrets/authelia_storage_encryption_key_file
|
||||
# - AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE=/run/secrets/authelia_oidc_hamc_secret
|
||||
#- AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER=/run/secrets/authelia_oidc_pem_secret
|
||||
- AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_KEY=/run/secrets/authelia_oidc_key_secret
|
||||
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
|
|
@ -115,17 +124,55 @@ services:
|
|||
- "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email" # yamllint disable-line rule:line-length
|
||||
secrets:
|
||||
- authelia_jwt_secret
|
||||
- authelia_oidc_pem_secret
|
||||
- authelia_oidc_hamc_secret
|
||||
- authelia_oidc_key_secret
|
||||
- authelia_session_secret
|
||||
- authelia_storage_postgres_password
|
||||
- authelia_notifier_smtp_password
|
||||
- authelia_duo_api_secret_key
|
||||
- authelia_storage_encryption_key_file
|
||||
|
||||
ddns-updater:
|
||||
image: qmcgaw/ddns-updater:2.7
|
||||
container_name: ddns-updater
|
||||
restart: always
|
||||
networks:
|
||||
- backend
|
||||
ports:
|
||||
- 8000:8000/tcp
|
||||
volumes:
|
||||
- ${DIR_LOCAL}/ddns-updater:/updater/data
|
||||
environment:
|
||||
PUID: 1000
|
||||
PGID: 1004
|
||||
TZ: ${TZ}
|
||||
PERIOD: 1h
|
||||
UPDATE_COOLDOWN_PERIOD: 5m
|
||||
PUBLICIP_DNS_TIMEOUT: 3s
|
||||
HTTP_TIMEOUT: 10s
|
||||
# Web UI
|
||||
LISTENING_PORT: 8000
|
||||
# Backup
|
||||
BACKUP_PERIOD: 96h # 0 to disable
|
||||
BACKUP_DIRECTORY: /updater/data/backups
|
||||
# Other
|
||||
LOG_LEVEL: info
|
||||
SHOUTRRR_ADDRESSES: $TGRAM_SHOUTRRR_ADDRESS
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.ddnsupdater.rule=Host(`ddns.${DOMAIN}`)"
|
||||
- "traefik.http.routers.ddnsupdater.entrypoints=web-secure"
|
||||
- "traefik.http.services.ddnsupdater.loadbalancer.server.port=8000"
|
||||
- "traefik.http.routers.ddnsupdater.middlewares=chain-authelia@file"
|
||||
- "traefik.http.routers.ddnsupdater.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.ddnsupdater.tls=true"
|
||||
|
||||
|
||||
adguard:
|
||||
container_name: adguard
|
||||
image: adguard/adguardhome:v0.107.40
|
||||
image: adguard/adguardhome:v0.107.52
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
docker_vlan:
|
||||
|
|
@ -145,9 +192,9 @@ services:
|
|||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.adguard.loadbalancer.server.port=3000"
|
||||
- "traefik.http.services.adguard.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.adguard.entrypoints=web-secure"
|
||||
- "traefik.http.routers.adguard.rule=Host(`vwgrafana.${DOMAIN}`)"
|
||||
- "traefik.http.routers.adguard.rule=Host(`adguard.${DOMAIN}`)"
|
||||
- "traefik.http.routers.adguard.middlewares=chain-authelia@file"
|
||||
- "traefik.http.routers.adguard.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.adguard.tls=true"
|
||||
|
|
@ -171,7 +218,7 @@ services:
|
|||
networks:
|
||||
- backend
|
||||
volumes:
|
||||
- ${DIR}/database:/var/lib/mysql:rw
|
||||
- ${DIR_LOCAL}/database:/var/lib/mysql:rw
|
||||
ports:
|
||||
- "3307:3306"
|
||||
labels:
|
||||
|
|
@ -199,7 +246,7 @@ services:
|
|||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.middlewares.webdb-mid.ipwhitelist.sourcerange=127.0.0.1/32, 10.0.0.1/24"
|
||||
- "traefik.http.middlewares.webdb-mid.ipallowlist.sourcerange=127.0.0.1/32, 10.0.0.1/24"
|
||||
- "traefik.http.routers.webdb-secure.middlewares=webdb-mid"
|
||||
- "traefik.http.routers.webdb-secure.entrypoints=web-secure"
|
||||
- "traefik.http.routers.webdb-secure.rule=Host(`webdb.${DOMAIN}`)"
|
||||
|
|
@ -226,7 +273,7 @@ services:
|
|||
networks:
|
||||
- backend
|
||||
volumes:
|
||||
- ${DIR}/database_pg/data:/var/lib/postgresql/data
|
||||
- ${DIR_LOCAL}/database_pg/data:/var/lib/postgresql/data
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=false"
|
||||
|
|
@ -282,7 +329,7 @@ services:
|
|||
networks:
|
||||
- backend
|
||||
volumes:
|
||||
- ${DIR}/redis:/var/lib/redis
|
||||
- ${DIR_LOCAL}/redis:/var/lib/redis
|
||||
#entrypoint: redis-server --appendonly yes --requirepass $REDIS_PASSWORD --maxmemory 512mb --maxmemory-policy allkeys-lru
|
||||
labels:
|
||||
- diun.enable=true
|
||||
|
|
@ -295,7 +342,7 @@ services:
|
|||
|
||||
ha:
|
||||
container_name: ha
|
||||
image: homeassistant/home-assistant:2023.11
|
||||
image: homeassistant/home-assistant:2025.2
|
||||
restart: always
|
||||
privileged: true
|
||||
networks:
|
||||
|
|
@ -395,11 +442,9 @@ services:
|
|||
command:
|
||||
- evcc
|
||||
container_name: evcc
|
||||
image: evcc/evcc:0.123.7
|
||||
image: evcc/evcc:0.200.5
|
||||
ports:
|
||||
- 7070:7070/tcp
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
volumes:
|
||||
- "./evcc/evcc.yaml:/etc/evcc.yaml:ro"
|
||||
- ./evcc/evcc:/root/.evcc
|
||||
|
|
@ -417,7 +462,7 @@ services:
|
|||
- "traefik.http.routers.evcc.tls=true"
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:10.0.0
|
||||
image: grafana/grafana:10.3.1
|
||||
container_name: grafana
|
||||
logging:
|
||||
driver: "json-file"
|
||||
|
|
@ -431,7 +476,7 @@ services:
|
|||
- ./grafana/grafana.ini:/etc/grafana/grafana.ini
|
||||
# Data persistency
|
||||
# sudo mkdir -p /srv/docker/grafana/data; chown 472:472 /srv/docker/grafana/data
|
||||
- "${DIR}/grafana:/var/lib/grafana"
|
||||
- "${DIR_LOCAL}/grafana:/var/lib/grafana"
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
|
|
@ -441,6 +486,149 @@ services:
|
|||
- "traefik.http.routers.grafana-secure.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.grafana-secure.tls=true"
|
||||
|
||||
node-red:
|
||||
image: nodered/node-red:3.1.6
|
||||
|
||||
# image: nodered/node-red-dev:3.0.0-beta.4-14
|
||||
container_name: "node-red"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "5m"
|
||||
max-file: "3"
|
||||
networks:
|
||||
- backend
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
ports:
|
||||
- "1881:1880"
|
||||
#devices:
|
||||
#- /dev/ttyAMA0
|
||||
restart: unless-stopped
|
||||
user: ${UID}
|
||||
volumes:
|
||||
- ${DIR_LOCAL}/nodered:/data
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.node-red-service.loadbalancer.server.port=1880"
|
||||
- "traefik.http.routers.node-red-secure.entrypoints=web-secure"
|
||||
- "traefik.http.routers.node-red-secure.rule=Host(`nodered.${DOMAIN}`)"
|
||||
- "traefik.http.routers.node-red-secure.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.node-red-secure.tls=true"
|
||||
|
||||
nextcloud:
|
||||
container_name: nextcloud
|
||||
image: nextcloud:28
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "5m"
|
||||
max-file: "3"
|
||||
restart: always
|
||||
ports:
|
||||
- "8009:80"
|
||||
networks:
|
||||
- backend
|
||||
depends_on:
|
||||
- redis
|
||||
- postgres
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.nextcloud.entrypoints=web-secure"
|
||||
- "traefik.http.routers.nextcloud.middlewares=nextcloud,nextcloud_redirect"
|
||||
- "traefik.http.routers.nextcloud.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.nextcloud.rule=Host(`moln.${DOMAIN}`)"
|
||||
- "traefik.http.middlewares.nextcloud.headers.customFrameOptionsValue=ALLOW-FROM https://milvert.com"
|
||||
- "traefik.http.middlewares.nextcloud.headers.contentSecurityPolicy=frame-ancestors 'self' milvert.com"
|
||||
- "traefik.http.middlewares.nextcloud.headers.stsSeconds=155520011"
|
||||
- "traefik.http.middlewares.nextcloud.headers.stsIncludeSubdomains=true"
|
||||
- "traefik.http.middlewares.nextcloud.headers.stsPreload=true"
|
||||
- "traefik.http.middlewares.nextcloud.headers.customFrameOptionsValue=SAMEORIGIN"
|
||||
- "traefik.http.middlewares.nextcloud.headers.referrerPolicy=no-referrer"
|
||||
- "traefik.http.middlewares.nextcloud.headers.browserXSSFilter=true"
|
||||
- "traefik.http.middlewares.nextcloud.headers.contentTypeNosniff=true"
|
||||
- "traefik.http.middlewares.nextcloud_redirect.redirectregex.regex=/.well-known/(card|cal)dav"
|
||||
- "traefik.http.middlewares.nextcloud_redirect.redirectregex.replacement=/remote.php/dav/"
|
||||
environment:
|
||||
- POSTGRES_DB=nextcloud
|
||||
- POSTGRES_USER=nextcloud
|
||||
- POSTGRES_PASSWORD=bajsa123
|
||||
- POSTGRES_HOST=postgres
|
||||
- NEXTCLOUD_ADMIN_USER=admin
|
||||
- NEXTCLOUD_ADMIN_PASSWORD=bajsa123
|
||||
- NEXTCLOUD_TRUSTED_DOMAINS=moln.milvert.com
|
||||
- REDIS_HOST=redis
|
||||
- TRUSTED_PROXIES=172.19.0.0/16
|
||||
- OVERWRITECLIURL=https://moln.milvert.com
|
||||
- OVERWRITEPROTOCOL=https
|
||||
- OVERWRITEHOST=moln.milvert.com
|
||||
volumes:
|
||||
- ${DIR_LOCAL}/nextcloud:/var/www/html
|
||||
- ./nextcloud/cronjob:/var/spool/cron/crontabs/www-data
|
||||
- /srv/owncloud:/var/www/html/data
|
||||
- /media/NAS:/media/NAS
|
||||
- /mnt/gunnar:/media/gunnar
|
||||
|
||||
gitea:
|
||||
image: gitea/gitea:1.21
|
||||
container_name: gitea
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
networks:
|
||||
- backend
|
||||
restart: always
|
||||
environment:
|
||||
- USER_UID=1001
|
||||
- USER_GID=1005
|
||||
volumes:
|
||||
#- /var/lib/gitea:/data
|
||||
- ${DIR}/gitea:/data
|
||||
- ./gitea/app.ini:/data/gitea/conf/app.ini
|
||||
- /home/git/.ssh:/data/git/.ssh
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "127.0.0.1:2222:22"
|
||||
- "3000:3000"
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.gitea-service.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.gitea-secure.entrypoints=web-secure"
|
||||
- "traefik.http.routers.gitea-secure.rule=Host(`gitea.${DOMAIN}`)"
|
||||
- "traefik.http.routers.gitea-secure.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.gitea-secure.tls=true"
|
||||
- "traefik.http.routers.gitea-secure.middlewares=chain-no-auth@file"
|
||||
|
||||
|
||||
uptime_kuma:
|
||||
image: louislam/uptime-kuma
|
||||
container_name: uptime_kuma
|
||||
networks:
|
||||
- backend
|
||||
environment:
|
||||
- ADMIN_PASSWORD=${UPTIME_KUMA_PASSWORD}
|
||||
- ADMIN_EMAIL=${UPTIME_KUMA_USER}
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${DIR_LOCAL}/uptime_kuma:/data
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.uptime-service.loadbalancer.server.port=3001"
|
||||
- "traefik.http.routers.uptime-secure.entrypoints=web-secure"
|
||||
- "traefik.http.routers.uptime-secure.rule=Host(`uptime.${DOMAIN}`)"
|
||||
- "traefik.http.routers.uptime-secure.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.uptime-secure.tls=true"
|
||||
- "traefik.http.routers.uptime-secure.middlewares=chain-authelia@file"
|
||||
healthcheck:
|
||||
disable: true
|
||||
######################### WEB ############################
|
||||
#
|
||||
# WEB
|
||||
|
|
@ -472,8 +660,38 @@ services:
|
|||
- "traefik.http.routers.librespeed.tls=true"
|
||||
|
||||
|
||||
jelu:
|
||||
image: wabayang/jelu
|
||||
container_name: jelu
|
||||
environment:
|
||||
- PUID=${UUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
ports:
|
||||
# The HTTP port
|
||||
- 11111:11111
|
||||
networks:
|
||||
- backend
|
||||
volumes:
|
||||
- ./jelu/config:/config
|
||||
- ${DIR_LOCAL}/jelu/database:/database
|
||||
- ${DIR_LOCAL}/jelu/files/images:/files/images
|
||||
- ${DIR_LOCAL}/jelu/files/imports:/files/imports
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- diun.enable=true
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.jelu-service.loadbalancer.server.port=11111"
|
||||
- "traefik.http.routers.jelu.entrypoints=web-secure"
|
||||
- "traefik.http.routers.jelu.rule=Host(`jelu.${DOMAIN}`)"
|
||||
- "traefik.http.routers.jelu.middlewares=chain-no-auth@file"
|
||||
- "traefik.http.routers.jelu.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.jelu.tls=true"
|
||||
|
||||
|
||||
vwsfriend:
|
||||
image: tillsteinbach/vwsfriend:0.24.2
|
||||
image: tillsteinbach/vwsfriend:0.24.7
|
||||
container_name: vwfriend
|
||||
ports:
|
||||
- ${VWSFRIEND_PORT-4000}:${VWSFRIEND_PORT-4000}
|
||||
|
|
@ -515,7 +733,7 @@ services:
|
|||
- "traefik.http.routers.vwsfriend.tls=true"
|
||||
|
||||
vwgrafana:
|
||||
image: tillsteinbach/vwsfriend-grafana:0.24.2
|
||||
image: tillsteinbach/vwsfriend-grafana:0.24.5
|
||||
container_name: vwgrafana
|
||||
ports:
|
||||
- ${GF_SERVER_HTTP_PORT-3001}:${GF_SERVER_HTTP_PORT-3000}
|
||||
|
|
@ -556,6 +774,22 @@ services:
|
|||
- "traefik.http.routers.vwgrafana.tls.certresolver=milvert_dns"
|
||||
- "traefik.http.routers.vwgrafana.tls=true"
|
||||
|
||||
# weconnect_mqtt:
|
||||
#image: "tillsteinbach/weconnect-mqtt:0.49.1"
|
||||
#container_name: weconnect_mqtt
|
||||
#restart: unless-stopped
|
||||
#networks:
|
||||
#backend:
|
||||
#labels:
|
||||
#- diun.enable=true
|
||||
#environment:
|
||||
#- TZ=$TZ
|
||||
#- LC_ALL=sv_SE
|
||||
#- USER=${WECONNECT_USER}
|
||||
#- PASSWORD=${WECONNECT_PASSWORD}
|
||||
#- BROKER_ADDRESS=mqtt
|
||||
#- ADDITIONAL_PARAMETERS=--mqtt-username simon --mqtt-password bajsa123 --spin 9331 -vv
|
||||
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
sponsortoken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODEzODIyNTYsImlhdCI6MTY4Njc3NDI1NiwiaXNzIjoiZXZjYy5pbyIsInN1YiI6Im1pbHZlcnQifQ.HUEmc0NSPt9x5MbOHUAGU6bp3H3E3qwu6O6BAHH9FvE
|
||||
sponsortoken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJldmNjLmlvIiwic3ViIjoibWlsdmVydCIsImV4cCI6MTgzMDE5MzIwMCwiaWF0IjoxNzM1NTg1MjAwLCJzcmMiOiJnaCJ9._K23QsA15DIHRjujwH8rnFZyloSw1RPIeIS4W5WLFGE
|
||||
|
||||
log: error
|
||||
#levels:
|
||||
# car: trace
|
||||
levels:
|
||||
tariff: trace
|
||||
|
||||
interval: 30s
|
||||
|
||||
|
|
@ -40,14 +40,20 @@ chargers:
|
|||
password: X7#aEzjlEysBgl
|
||||
charger: EHCNF485
|
||||
|
||||
circuits:
|
||||
- name: main # if there is only one circuit defined the name needs to be 'main'
|
||||
title: 'main circuit' # name for the UI (not implemented in UI yet)
|
||||
maxCurrent: 20
|
||||
maxPower: 10000
|
||||
meter: my_grid # optiona
|
||||
|
||||
loadpoints:
|
||||
- title: Garage
|
||||
charger: wallbox
|
||||
vehicle: car
|
||||
circuit: main
|
||||
mode: pv
|
||||
phases: 3
|
||||
mincurrent: 6
|
||||
maxcurrent: 16
|
||||
enable:
|
||||
threshold: 0
|
||||
delay: 15s
|
||||
|
|
@ -73,18 +79,19 @@ meters:
|
|||
energy:
|
||||
source: mqtt
|
||||
topic: inverter/calculated/accumulated_yield_energy
|
||||
# jq: .value
|
||||
timeout: 1h
|
||||
timeout: 60s
|
||||
currents:
|
||||
- source: mqtt
|
||||
topic: inverter/measure/phase_A_current
|
||||
timeout: 60s
|
||||
# jq: .value
|
||||
- source: mqtt
|
||||
topic: inverter/measure/phase_B_current
|
||||
timeout: 60s
|
||||
# jq: .value
|
||||
- source: mqtt
|
||||
topic: inverter/measure/phase_C_current
|
||||
# jq: .value
|
||||
timeout: 60s
|
||||
|
||||
- name: my_grid
|
||||
type: custom
|
||||
|
|
@ -94,41 +101,34 @@ meters:
|
|||
- source: mqtt
|
||||
topic: dsmr/reading/electricity_currently_returned
|
||||
scale: -1000
|
||||
timeout: 30s
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/electricity_currently_delivered
|
||||
scale: 1000
|
||||
timeout: 30s
|
||||
energy:
|
||||
source: calc
|
||||
add:
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/electricity_returned_1
|
||||
scale: 0.001
|
||||
timeout: 30s
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/electricity_delivered_1
|
||||
scale: -0.001
|
||||
timeout: 30s
|
||||
|
||||
currents:
|
||||
- source: calc
|
||||
add:
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_currently_delivered_l1
|
||||
scale: -1
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_currently_returned_l1
|
||||
- source: calc
|
||||
add:
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_currently_delivered_l2
|
||||
scale: -1
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_currently_returned_l2
|
||||
- source: calc
|
||||
add:
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_currently_delivered_l3
|
||||
scale: -1
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_currently_returned_l3
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_power_current_l1
|
||||
timeout: 30s
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_power_current_l2
|
||||
timeout: 30s
|
||||
- source: mqtt
|
||||
topic: dsmr/reading/phase_power_current_l1
|
||||
timeout: 30s
|
||||
|
||||
|
||||
influx:
|
||||
url: http://influx:8086
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1118,3 +1118,22 @@ allow_embedding = true
|
|||
|
||||
# Enable or disable loading other base map layers
|
||||
;enable_custom_baselayers = true
|
||||
[auth]
|
||||
enabled = true
|
||||
|
||||
[auth.generic_oauth]
|
||||
enabled = true
|
||||
name = Authelia
|
||||
icon = signin
|
||||
client_id = aN0VgMKamGZvleUplkT3W7kvXJmvsWmy4C9Obd6u5XGqL7A9B7CP2xxdSIv4ljIA
|
||||
client_secret = QqJ520ad7V9jX93J3NFqfWnCyklDH1UO
|
||||
scopes = openid profile email groups
|
||||
empty_scopes = false
|
||||
auth_url = https://authelia.milvert.com/api/oidc/authorization # Replace with your own URL
|
||||
token_url = https://authelia.milvert.com/api/oidc/token # Replace with your own URL
|
||||
api_url = https://authelia.milvert.com/api/oidc/userinfo # Replace with your own URL
|
||||
login_attribute_path = preferred_username
|
||||
groups_attribute_path = groups
|
||||
name_attribute_path = name
|
||||
use_pkce = true
|
||||
auto_login = true
|
||||
|
|
|
|||
|
|
@ -31,21 +31,27 @@
|
|||
- id: '1700687177645'
|
||||
alias: Handle_motorvärmare
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: time
|
||||
at: input_datetime.motorvarmare_start
|
||||
condition:
|
||||
triggers:
|
||||
- at: input_datetime.motorvarmare_start
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: input_boolean.motorvarmare_toogle
|
||||
state: 'on'
|
||||
action:
|
||||
- service: switch.turn_on
|
||||
data: {}
|
||||
actions:
|
||||
- data: {}
|
||||
action: switch.turn_on
|
||||
target:
|
||||
entity_id: switch.nodeid_22_switch
|
||||
entity_id: switch.nodeid_22_nodeid_22_switch
|
||||
- delay: 02:00:00
|
||||
- data: {}
|
||||
action: switch.turn_off
|
||||
target:
|
||||
entity_id: switch.nodeid_22_nodeid_22_switch
|
||||
mode: single
|
||||
- id: '1700693056778'
|
||||
description: ''
|
||||
alias: motorvärmare 2h
|
||||
description: motorvärmare i 2h
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id:
|
||||
|
|
@ -62,7 +68,7 @@
|
|||
entity_id: switch.nodeid_22_switch
|
||||
mode: single
|
||||
- id: '1703971688590'
|
||||
alias: Lampa trappa
|
||||
alias: Lampa trappa dag
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: state
|
||||
|
|
@ -82,6 +88,53 @@
|
|||
- service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 52
|
||||
kelvin: 2900
|
||||
target:
|
||||
device_id: ad8c90d56d6753ae960fe61560f1de66
|
||||
- delay:
|
||||
hours: 0
|
||||
minutes: 20
|
||||
seconds: 0
|
||||
milliseconds: 0
|
||||
- service: light.turn_off
|
||||
data: {}
|
||||
target:
|
||||
device_id: ad8c90d56d6753ae960fe61560f1de66
|
||||
mode: single
|
||||
- id: '1704919949019'
|
||||
alias: Update Jaffa location as MQTT location updates
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: mqtt
|
||||
topic: weconnect/0/vehicles/WVGZZZE2ZPE051949/parking/parkingPosition/latitude
|
||||
- platform: mqtt
|
||||
topic: weconnect/0/vehicles/WVGZZZE2ZPE051949/parking/parkingPosition/longitude
|
||||
condition: []
|
||||
action:
|
||||
- service: device_tracker.see
|
||||
data:
|
||||
dev_id: jaffa_location
|
||||
source_type: gps
|
||||
gps:
|
||||
- '{{ states.sensor.none_jaffa_lat.state }}'
|
||||
- '{{ states.sensor.none_jaffa_long.state }}'
|
||||
initial_state: 'on'
|
||||
- id: '1706989035065'
|
||||
alias: Lampa trappa natt
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id:
|
||||
- binary_sensor.h007m_occupancy
|
||||
to: 'on'
|
||||
condition:
|
||||
- condition: time
|
||||
after: '22:30:00'
|
||||
before: 08:00:00
|
||||
action:
|
||||
- service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 7
|
||||
target:
|
||||
device_id: ad8c90d56d6753ae960fe61560f1de66
|
||||
- delay:
|
||||
|
|
@ -94,3 +147,470 @@
|
|||
target:
|
||||
device_id: ad8c90d56d6753ae960fe61560f1de66
|
||||
mode: single
|
||||
- id: '1707166664479'
|
||||
alias: 'Oscar tts '
|
||||
description: 'Spelar upp en text på Oscars högtalare '
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id:
|
||||
- input_text.tts_syntesiser
|
||||
condition: []
|
||||
action:
|
||||
- service: tts.edge_tts_say
|
||||
metadata: {}
|
||||
data:
|
||||
cache: false
|
||||
entity_id: media_player.oscar
|
||||
language: sv_SE
|
||||
message: '{{ states(''input_text.tts_syntesiser'') }}'
|
||||
enabled: false
|
||||
- service: tts.edge_tts_say
|
||||
metadata: {}
|
||||
data:
|
||||
entity_id: media_player.oscar
|
||||
message: 'Hej '
|
||||
mode: single
|
||||
- id: '1709494545609'
|
||||
alias: växtlampa schema
|
||||
trigger:
|
||||
- platform: template
|
||||
value_template: '{{ now().hour == 7 }}'
|
||||
id: 'on'
|
||||
- platform: template
|
||||
value_template: '{{ (now().hour, now().minute) == (21,30) }}'
|
||||
id: 'off'
|
||||
action:
|
||||
- service: light.turn_{{ trigger.id }}
|
||||
target:
|
||||
entity_id: light.vaxtlampa
|
||||
- id: '1713552723716'
|
||||
alias: Automation_oscar_skrivbord
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: button_pressed
|
||||
event_data:
|
||||
entity_id: switch.oscar_skrivbord
|
||||
state: 'off'
|
||||
id: 'off'
|
||||
- platform: event
|
||||
event_type: button_pressed
|
||||
event_data:
|
||||
entity_id: switch.oscar_skrivbord
|
||||
state: 'on'
|
||||
id: 'on'
|
||||
condition: []
|
||||
action:
|
||||
- service: light.turn_{{ trigger.id }}
|
||||
target:
|
||||
entity_id: light.oscar_skrivbord
|
||||
data: {}
|
||||
- service: switch.turn_{{ trigger.id }}
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: switch.nodeid_13_nodeid_13_switch
|
||||
mode: single
|
||||
- id: '1713555224562'
|
||||
alias: Automation_oscar_moln
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: button_pressed
|
||||
event_data:
|
||||
entity_id: switch.oscar_skrivbord
|
||||
state: 'off'
|
||||
id: 'off'
|
||||
- platform: event
|
||||
event_type: button_pressed
|
||||
event_data:
|
||||
entity_id: switch.oscar_skrivbord
|
||||
state: 'on'
|
||||
id: 'on'
|
||||
condition: []
|
||||
action:
|
||||
- service: switch.turn_{{ trigger.id }}
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: switch.nodeid_7_nodeid_7_switch
|
||||
mode: single
|
||||
- id: '1715369564640'
|
||||
alias: Bevattning 20 min
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: sun
|
||||
event: sunrise
|
||||
offset: 0
|
||||
condition: []
|
||||
action:
|
||||
- type: turn_on
|
||||
device_id: ee19c0deed59d2a266b59c30dbf7ccaa
|
||||
entity_id: 068c418ccf17f83fe236590673ce7c1f
|
||||
domain: switch
|
||||
- delay:
|
||||
hours: 0
|
||||
minutes: 30
|
||||
seconds: 0
|
||||
milliseconds: 0
|
||||
- type: turn_off
|
||||
device_id: ee19c0deed59d2a266b59c30dbf7ccaa
|
||||
entity_id: 068c418ccf17f83fe236590673ce7c1f
|
||||
domain: switch
|
||||
mode: single
|
||||
- id: '1715979338256'
|
||||
alias: Nattnotis
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id:
|
||||
- scene.natt
|
||||
condition:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: state
|
||||
entity_id: binary_sensor.jaffa_locked
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: lock.h014s
|
||||
state: unlocked
|
||||
- condition: state
|
||||
entity_id: binary_sensor.sensor_inne_tvattstuga_dt002_contact
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: binary_sensor.sensor_inne_arum_dt003_contact
|
||||
state: 'on'
|
||||
action:
|
||||
- service: notify.mobile_app_simon_mobil
|
||||
metadata: {}
|
||||
data:
|
||||
title: Kolla lås.
|
||||
message: "**Natt-scen aktiverad!** * Jaffadörren: {{ states('binary_sensor.jaffa_locked').state
|
||||
}} * Tvättstuga: {{ states('binary_sensor.sensor_inne_tvattstuga_dt002_contact').state
|
||||
}} * Arbetsrum: {{ states('binary_sensor.sensor_inne_arum_dt003_contact').state
|
||||
}} \n"
|
||||
mode: single
|
||||
- id: '1723749734599'
|
||||
alias: Kväll_on_off
|
||||
description: ''
|
||||
triggers:
|
||||
- event: sunset
|
||||
offset: 00:30:00
|
||||
trigger: sun
|
||||
- at: '19:30:00'
|
||||
id: time_on
|
||||
trigger: time
|
||||
- at: '23:15:00'
|
||||
id: time_off
|
||||
trigger: time
|
||||
conditions: []
|
||||
actions:
|
||||
- data:
|
||||
entity_id: '{{ ''scene.kvalls_belysning'' if now().hour < 23 else ''scene.natt''
|
||||
}}
|
||||
|
||||
'
|
||||
action: scene.turn_on
|
||||
- id: '1723751411352'
|
||||
alias: ute_av_på
|
||||
description: ''
|
||||
triggers:
|
||||
- event: sunrise
|
||||
offset: -00:15:00
|
||||
trigger: sun
|
||||
- event: sunset
|
||||
offset: 00:15:00
|
||||
trigger: sun
|
||||
- at: 07:55:00
|
||||
trigger: time
|
||||
conditions: []
|
||||
actions:
|
||||
- data:
|
||||
entity_id: '{{ ''scene.ute'' if trigger.event == ''sunset'' else ''scene.ute_av''
|
||||
}}
|
||||
|
||||
'
|
||||
action: scene.turn_on
|
||||
mode: single
|
||||
- id: '1725646259613'
|
||||
alias: Kök på 5 min
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id:
|
||||
- light.kok_ct
|
||||
to: 'on'
|
||||
condition: []
|
||||
action:
|
||||
- entity_id: script.kok_ct_timer_2
|
||||
action: script.turn_on
|
||||
mode: restart
|
||||
- id: '1729969302596'
|
||||
alias: Tänd och släck vrum upp vid helgmorgon
|
||||
description: Tänder lampan kl 06:00 och släcker den kl 07:30 på lördagar och söndagar
|
||||
triggers:
|
||||
- at: 06:00:00
|
||||
trigger: time
|
||||
- at: 07:30:00
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: time
|
||||
weekday:
|
||||
- sat
|
||||
- sun
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.platform == ''time'' and trigger.now.strftime(''%H:%M:%S'')
|
||||
== ''06:00:00'' }}'
|
||||
sequence:
|
||||
- data:
|
||||
brightness_pct: 9
|
||||
target:
|
||||
device_id: 79ba72943d4ed67fa5dc4fdbfe4fa54d
|
||||
action: light.turn_on
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.platform == ''time'' and trigger.now.strftime(''%H:%M:%S'')
|
||||
== ''07:30:00'' }}'
|
||||
sequence:
|
||||
- target:
|
||||
device_id: 79ba72943d4ed67fa5dc4fdbfe4fa54d
|
||||
action: light.turn_off
|
||||
data: {}
|
||||
- action: switch.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: d93a6c62e11bec4c4d480497363d4512
|
||||
mode: single
|
||||
- id: '1731354729078'
|
||||
alias: Tänd garage
|
||||
description: ''
|
||||
triggers:
|
||||
- type: occupied
|
||||
device_id: 0c06d34c097db550f6339bdf16b8b408
|
||||
entity_id: c03b9e6b324f34b4ff4dc523b49ed991
|
||||
domain: binary_sensor
|
||||
trigger: device
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.sensor_inne_garage_m001_occupancy
|
||||
to: 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: light.turn_on
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: f5781cfa34b2e0a238f6f6333e7d7fa2
|
||||
- action: switch.turn_on
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: switch.nodeid_16_nodeid_16_switch
|
||||
- delay:
|
||||
minutes: 5
|
||||
- action: light.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: f5781cfa34b2e0a238f6f6333e7d7fa2
|
||||
- action: switch.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: switch.nodeid_16_nodeid_16_switch
|
||||
mode: single
|
||||
- id: '1731870335988'
|
||||
alias: Lampa, byt slinga mot fasad
|
||||
description: ''
|
||||
triggers:
|
||||
- at: '23:15:00'
|
||||
trigger: time
|
||||
- at: 06:00:00
|
||||
trigger: time
|
||||
conditions: []
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: time
|
||||
before: 06:00:00
|
||||
sequence:
|
||||
- action: switch.turn_off
|
||||
data: {}
|
||||
target:
|
||||
entity_id: switch.h024s
|
||||
- target:
|
||||
entity_id: switch.h015l
|
||||
action: switch.turn_on
|
||||
data: {}
|
||||
- conditions:
|
||||
- condition: time
|
||||
after: 05:59:59
|
||||
sequence:
|
||||
- action: switch.toggle
|
||||
data: {}
|
||||
target:
|
||||
entity_id: switch.h024s
|
||||
- target:
|
||||
entity_id: switch.h015l
|
||||
action: switch.toggle
|
||||
data: {}
|
||||
mode: single
|
||||
- id: '1733170240917'
|
||||
alias: 'Jul belysning '
|
||||
description: ''
|
||||
triggers:
|
||||
- value_template: '{{ now().hour == 16 }}'
|
||||
id: 'on'
|
||||
trigger: template
|
||||
- value_template: '{{ (now().hour) == 8 }}'
|
||||
id: 'off'
|
||||
trigger: template
|
||||
actions:
|
||||
- target:
|
||||
entity_id: group.jul_group
|
||||
action: homeassistant.turn_{{ trigger.id }}
|
||||
- id: '1735463627194'
|
||||
alias: Ada släck 0800
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: 08:00:00
|
||||
conditions: []
|
||||
actions:
|
||||
- action: light.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: light.ada_jordglob2
|
||||
- action: switch.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
entity_id: switch.ada_slnga
|
||||
- action: light.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: 069299a3de0a369de40dd512292a2828
|
||||
mode: single
|
||||
- id: '1740430707464'
|
||||
alias: Sov gott Oscar när sleep timer är 0
|
||||
description: ''
|
||||
triggers:
|
||||
- entity_id: sensor.sonos_sleep_timer
|
||||
to: '0'
|
||||
trigger: state
|
||||
actions:
|
||||
- action: script.talk_on_oscar
|
||||
data:
|
||||
message: Sov Gott Oscar! Dags att sova
|
||||
mode: single
|
||||
- id: '1741036830396'
|
||||
alias: 'Reser temp '
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: time
|
||||
at: 06:00:00
|
||||
conditions: []
|
||||
actions:
|
||||
- action: climate.set_fan_mode
|
||||
metadata: {}
|
||||
data:
|
||||
fan_mode: '3'
|
||||
target:
|
||||
device_id: ced8502d2ee4ac70dbb9929329ec3ae2
|
||||
- action: climate.set_temperature
|
||||
metadata: {}
|
||||
data:
|
||||
temperature: 22
|
||||
target:
|
||||
device_id: ced8502d2ee4ac70dbb9929329ec3ae2
|
||||
mode: single
|
||||
- id: '1743450529687'
|
||||
alias: Styr temperaturen vrum
|
||||
description: ''
|
||||
triggers:
|
||||
- type: opened
|
||||
device_id: c57486d3c2297020f9b19f7128bf867e
|
||||
entity_id: 19d29462d89383fd11e32861269de77d
|
||||
domain: binary_sensor
|
||||
metadata:
|
||||
secondary: false
|
||||
trigger: device
|
||||
for:
|
||||
hours: 0
|
||||
minutes: 0
|
||||
seconds: 30
|
||||
- type: not_opened
|
||||
device_id: c57486d3c2297020f9b19f7128bf867e
|
||||
entity_id: 19d29462d89383fd11e32861269de77d
|
||||
domain: binary_sensor
|
||||
trigger: device
|
||||
conditions: []
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- type: is_open
|
||||
condition: device
|
||||
device_id: c57486d3c2297020f9b19f7128bf867e
|
||||
entity_id: 19d29462d89383fd11e32861269de77d
|
||||
domain: binary_sensor
|
||||
for:
|
||||
hours: 0
|
||||
minutes: 0
|
||||
seconds: 30
|
||||
sequence:
|
||||
- data:
|
||||
value: '{{ state_attr(''climate.vardagsrum_2'', ''temperature'') }}'
|
||||
target:
|
||||
entity_id: input_number.vardagsrum_temperatur_sparad
|
||||
action: input_number.set_value
|
||||
- metadata: {}
|
||||
data:
|
||||
temperature: 6
|
||||
target:
|
||||
entity_id: climate.vardagsrum_2
|
||||
action: climate.set_temperature
|
||||
- conditions:
|
||||
- type: is_not_open
|
||||
condition: device
|
||||
device_id: c57486d3c2297020f9b19f7128bf867e
|
||||
entity_id: 19d29462d89383fd11e32861269de77d
|
||||
domain: binary_sensor
|
||||
sequence:
|
||||
- metadata: {}
|
||||
data:
|
||||
temperature: '{{ states(''input_number.vardagsrum_temperatur_sparad'') }}'
|
||||
target:
|
||||
entity_id: climate.vardagsrum_2
|
||||
action: climate.set_temperature
|
||||
mode: single
|
||||
- id: '1744573491574'
|
||||
alias: Möjlig frost - baserat på trend eller prognos
|
||||
triggers:
|
||||
- at: '20:00:00'
|
||||
trigger: time
|
||||
conditions:
|
||||
- condition: or
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: "{% set forecast = state_attr('weather.forecast_home', 'forecast')
|
||||
%} {% if forecast %}\n {{ forecast[0].templow | float < 0 }}\n{% else %}\n
|
||||
\ false\n{% endif %}\n"
|
||||
- condition: and
|
||||
conditions:
|
||||
- condition: numeric_state
|
||||
entity_id: sensor.h017s_temperature
|
||||
below: 4
|
||||
- condition: numeric_state
|
||||
entity_id: sensor.h017s_derivata
|
||||
below: -0.5
|
||||
actions:
|
||||
- data:
|
||||
message: Varning! Frost kan vara på gång ❄️ (enligt prognos eller temptrend)
|
||||
action: notify.mobile_app_simon_mobil
|
||||
mode: single
|
||||
|
|
|
|||
|
|
@ -5,23 +5,67 @@ default_config:
|
|||
# Load frontend themes from the themes folder
|
||||
frontend:
|
||||
themes: !include_dir_merge_named themes
|
||||
extra_module_url:
|
||||
- /config/www/community/lovelace-card-mod/card-mod.js
|
||||
|
||||
|
||||
automation: !include automations.yaml
|
||||
script: !include scripts.yaml
|
||||
scene: !include scenes.yaml
|
||||
command_line: !include sensor.yaml
|
||||
|
||||
battery_notes:
|
||||
|
||||
|
||||
#lovelace:
|
||||
# mode: storage
|
||||
# resources:
|
||||
# - url: /local/week-planner-card_2.js
|
||||
# type: module
|
||||
# dashboards:
|
||||
# dash-general:
|
||||
# mode: yaml
|
||||
# filename: dashboards/default.yaml
|
||||
# title: Overview
|
||||
# icon: mdi:tools
|
||||
# show_in_sidebar: true
|
||||
# require_admin: false
|
||||
|
||||
tts:
|
||||
- platform: edge_tts
|
||||
service-name: edge-say
|
||||
language: sv-SE
|
||||
|
||||
#This is to synthesise TTS for the Google Home Mini
|
||||
input_text:
|
||||
tts_syntesiser:
|
||||
name: TTS-til-Google
|
||||
|
||||
proximity:
|
||||
home_jaffa:
|
||||
zone: home
|
||||
devices:
|
||||
- device_tracker.jaffa_location
|
||||
tolerance: 5
|
||||
unit_of_measurement: km
|
||||
|
||||
homeassistant:
|
||||
internal_url: http://10.0.0.203:8123
|
||||
external_url: https://ha.milvert.com
|
||||
auth_providers:
|
||||
- type: homeassistant
|
||||
- type: legacy_api_password
|
||||
api_password: !secret http_password
|
||||
|
||||
packages: !include_dir_named packages
|
||||
#evcc: !include packages/evcc.yaml
|
||||
|
||||
allowlist_external_dirs:
|
||||
- "/config/files"
|
||||
customize:
|
||||
# Add an entry for each entity that you want to overwrite.
|
||||
thermostat.family_room:
|
||||
entity_picture: https://example.com/images/nest.jpg
|
||||
friendly_name: Nest
|
||||
|
||||
|
||||
sonos:
|
||||
media_player:
|
||||
advertise_addr: 10.0.0.203
|
||||
|
|
@ -29,6 +73,19 @@ sonos:
|
|||
- 10.0.3.33
|
||||
- 10.0.3.32
|
||||
|
||||
|
||||
logger:
|
||||
default: error
|
||||
|
||||
#logs:
|
||||
|
||||
#homeassistant.components.command_line: debug
|
||||
#adax: debug
|
||||
#custom_components.adax: debug
|
||||
#homeassistant.components.adax: debug
|
||||
# rflink: error
|
||||
# homeassistant.components.rflink: debug
|
||||
|
||||
|
||||
http:
|
||||
use_x_forwarded_for: true
|
||||
|
|
@ -37,19 +94,20 @@ http:
|
|||
trusted_proxies:
|
||||
- 10.0.0.223
|
||||
- 172.19.0.0/24
|
||||
|
||||
panel_iframe:
|
||||
configurator:
|
||||
title: Configurator
|
||||
icon: mdi:wrench
|
||||
url: http://10.0.0.3:3218
|
||||
require_admin: true
|
||||
|
||||
zwave:
|
||||
title: zwave
|
||||
icon: mdi:wrench
|
||||
url: http://10.0.0.3:8091
|
||||
require_admin: true
|
||||
|
||||
template:
|
||||
- sensor:
|
||||
- name: "Vardagsrum Source"
|
||||
state: >
|
||||
{% set source = state_attr('media_player.vardagsrum', 'source') | trim %}
|
||||
{% if source == "TV" %}
|
||||
TV
|
||||
{% else %}
|
||||
{{ source if source else "none" }}
|
||||
{% endif %}
|
||||
attributes:
|
||||
original_source: "{{ state_attr('media_player.vardagsrum', 'source') }}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,80 +1,58 @@
|
|||
"""
|
||||
HACS gives you a powerful UI to handle downloads of all your custom needs.
|
||||
"""HACS gives you a powerful UI to handle downloads of all your custom needs.
|
||||
|
||||
For more details about this integration, please refer to the documentation at
|
||||
https://hacs.xyz/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
from __future__ import annotations
|
||||
|
||||
from aiogithubapi import AIOGitHubAPIException, GitHub, GitHubAPI
|
||||
from aiogithubapi.const import ACCEPT_HEADERS
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant.components.frontend import async_remove_panel
|
||||
from homeassistant.components.lovelace.system_health import system_health_info
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import Platform, __version__ as HAVERSION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.loader import async_get_integration
|
||||
import voluptuous as vol
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import DOMAIN, MINIMUM_HA_VERSION, STARTUP
|
||||
from .const import DOMAIN, HACS_SYSTEM_ID, MINIMUM_HA_VERSION, STARTUP
|
||||
from .data_client import HacsDataClient
|
||||
from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMode
|
||||
from .enums import HacsDisabledReason, HacsStage, LovelaceMode
|
||||
from .frontend import async_register_frontend
|
||||
from .utils.configuration_schema import hacs_config_combined
|
||||
from .utils.data import HacsData
|
||||
from .utils.logger import LOGGER
|
||||
from .utils.queue_manager import QueueManager
|
||||
from .utils.version import version_left_higher_or_equal_then_right
|
||||
from .websocket import async_register_websocket_commands
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: hacs_config_combined()}, extra=vol.ALLOW_EXTRA)
|
||||
PLATFORMS = [Platform.SWITCH, Platform.UPDATE]
|
||||
|
||||
|
||||
async def async_initialize_integration(
|
||||
async def _async_initialize_integration(
|
||||
hass: HomeAssistant,
|
||||
*,
|
||||
config_entry: ConfigEntry | None = None,
|
||||
config: dict[str, Any] | None = None,
|
||||
config_entry: ConfigEntry,
|
||||
) -> bool:
|
||||
"""Initialize the integration"""
|
||||
hass.data[DOMAIN] = hacs = HacsBase()
|
||||
hacs.enable_hacs()
|
||||
|
||||
if config is not None:
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
if hacs.configuration.config_type == ConfigurationType.CONFIG_ENTRY:
|
||||
return True
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_type": ConfigurationType.YAML,
|
||||
**config[DOMAIN],
|
||||
"config": config[DOMAIN],
|
||||
}
|
||||
)
|
||||
if config_entry.source == SOURCE_IMPORT:
|
||||
# Import is not supported
|
||||
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
|
||||
return False
|
||||
|
||||
if config_entry is not None:
|
||||
if config_entry.source == SOURCE_IMPORT:
|
||||
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
|
||||
return False
|
||||
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_entry": config_entry,
|
||||
"config_type": ConfigurationType.CONFIG_ENTRY,
|
||||
**config_entry.data,
|
||||
**config_entry.options,
|
||||
}
|
||||
)
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_entry": config_entry,
|
||||
**config_entry.data,
|
||||
**config_entry.options,
|
||||
},
|
||||
)
|
||||
|
||||
integration = await async_get_integration(hass, DOMAIN)
|
||||
|
||||
|
|
@ -104,7 +82,6 @@ async def async_initialize_integration(
|
|||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# If this happens, the users YAML is not valid, we assume YAML mode
|
||||
pass
|
||||
hacs.log.debug("Configuration type: %s", hacs.configuration.config_type)
|
||||
hacs.core.config_path = hacs.hass.config.path()
|
||||
|
||||
if hacs.core.ha_version is None:
|
||||
|
|
@ -131,19 +108,18 @@ async def async_initialize_integration(
|
|||
"""HACS startup tasks."""
|
||||
hacs.enable_hacs()
|
||||
|
||||
for location in (
|
||||
hass.config.path("custom_components/custom_updater.py"),
|
||||
hass.config.path("custom_components/custom_updater/__init__.py"),
|
||||
):
|
||||
if os.path.exists(location):
|
||||
hacs.log.critical(
|
||||
"This cannot be used with custom_updater. "
|
||||
"To use this you need to remove custom_updater form %s",
|
||||
location,
|
||||
)
|
||||
try:
|
||||
import custom_components.custom_updater
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
hacs.log.critical(
|
||||
"HACS cannot be used with custom_updater. "
|
||||
"To use HACS you need to remove custom_updater from `custom_components`",
|
||||
)
|
||||
|
||||
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
return False
|
||||
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
return False
|
||||
|
||||
if not version_left_higher_or_equal_then_right(
|
||||
hacs.core.ha_version.string,
|
||||
|
|
@ -160,39 +136,23 @@ async def async_initialize_integration(
|
|||
hacs.disable_hacs(HacsDisabledReason.RESTORE)
|
||||
return False
|
||||
|
||||
if not hacs.configuration.experimental:
|
||||
can_update = await hacs.async_can_update()
|
||||
hacs.log.debug("Can update %s repositories", can_update)
|
||||
|
||||
hacs.set_active_categories()
|
||||
|
||||
async_register_websocket_commands(hass)
|
||||
async_register_frontend(hass, hacs)
|
||||
await async_register_frontend(hass, hacs)
|
||||
|
||||
if hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, hacs.configuration.config)
|
||||
)
|
||||
hacs.log.info("Update entities are only supported when using UI configuration")
|
||||
|
||||
else:
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
config_entry,
|
||||
[Platform.SENSOR, Platform.UPDATE]
|
||||
if hacs.configuration.experimental
|
||||
else [Platform.SENSOR],
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
hacs.set_stage(HacsStage.SETUP)
|
||||
if hacs.system.disabled:
|
||||
return False
|
||||
|
||||
# Schedule startup tasks
|
||||
async_at_start(hass=hass, at_start_cb=hacs.startup_tasks)
|
||||
|
||||
hacs.set_stage(HacsStage.WAITING)
|
||||
hacs.log.info("Setup complete, waiting for Home Assistant before startup tasks starts")
|
||||
|
||||
# Schedule startup tasks
|
||||
async_at_start(hass=hass, at_start_cb=hacs.startup_tasks)
|
||||
|
||||
return not hacs.system.disabled
|
||||
|
||||
async def async_try_startup(_=None):
|
||||
|
|
@ -202,10 +162,7 @@ async def async_initialize_integration(
|
|||
except AIOGitHubAPIException:
|
||||
startup_result = False
|
||||
if not startup_result:
|
||||
if (
|
||||
hacs.configuration.config_type == ConfigurationType.YAML
|
||||
or hacs.system.disabled_reason != HacsDisabledReason.INVALID_TOKEN
|
||||
):
|
||||
if hacs.system.disabled_reason != HacsDisabledReason.INVALID_TOKEN:
|
||||
hacs.log.info("Could not setup HACS, trying again in 15 min")
|
||||
async_call_later(hass, 900, async_try_startup)
|
||||
return
|
||||
|
|
@ -213,37 +170,19 @@ async def async_initialize_integration(
|
|||
|
||||
await async_try_startup()
|
||||
|
||||
# Remove old (v0-v1) sensor if it exists, can be removed in v3
|
||||
er = async_get_entity_registry(hass)
|
||||
if old_sensor := er.async_get_entity_id("sensor", DOMAIN, HACS_SYSTEM_ID):
|
||||
er.async_remove(old_sensor)
|
||||
|
||||
# Mischief managed!
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
|
||||
"""Set up this integration using yaml."""
|
||||
if DOMAIN in config:
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_configuration",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml_configuration",
|
||||
learn_more_url="https://hacs.xyz/docs/configuration/options",
|
||||
)
|
||||
LOGGER.warning(
|
||||
"YAML configuration of HACS is deprecated and will be "
|
||||
"removed in version 2.0.0, there will be no automatic "
|
||||
"import of this. "
|
||||
"Please remove it from your configuration, "
|
||||
"restart Home Assistant and use the UI to configure it instead."
|
||||
)
|
||||
return await async_initialize_integration(hass=hass, config=config)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up this integration using UI."""
|
||||
config_entry.async_on_unload(config_entry.add_update_listener(async_reload_entry))
|
||||
setup_result = await async_initialize_integration(hass=hass, config_entry=config_entry)
|
||||
setup_result = await _async_initialize_integration(hass=hass, config_entry=config_entry)
|
||||
hacs: HacsBase = hass.data[DOMAIN]
|
||||
return setup_result and not hacs.system.disabled
|
||||
|
||||
|
|
@ -259,7 +198,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
# Clear out pending queue
|
||||
hacs.queue.clear()
|
||||
|
||||
for task in hacs.recuring_tasks:
|
||||
for task in hacs.recurring_tasks:
|
||||
# Cancel all pending tasks
|
||||
task()
|
||||
|
||||
|
|
@ -269,15 +208,11 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
try:
|
||||
if hass.data.get("frontend_panels", {}).get("hacs"):
|
||||
hacs.log.info("Removing sidepanel")
|
||||
hass.components.frontend.async_remove_panel("hacs")
|
||||
async_remove_panel(hass, "hacs")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
platforms = ["sensor"]
|
||||
if hacs.configuration.experimental:
|
||||
platforms.append("update")
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, platforms)
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||
|
||||
hacs.set_stage(None)
|
||||
hacs.disable_hacs(HacsDisabledReason.REMOVED)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,16 +1,17 @@
|
|||
"""Base HACS class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import timedelta
|
||||
import gzip
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from aiogithubapi import (
|
||||
AIOGitHubAPIException,
|
||||
|
|
@ -24,23 +25,22 @@ from aiogithubapi import (
|
|||
from aiogithubapi.objects.repository import AIOGitHubAPIRepository
|
||||
from aiohttp.client import ClientSession, ClientTimeout
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.components.persistent_notification import (
|
||||
async_create as async_create_persistent_notification,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.loader import Integration
|
||||
from homeassistant.util import dt
|
||||
|
||||
from custom_components.hacs.repositories.base import (
|
||||
HACS_MANIFEST_KEYS_TO_EXPORT,
|
||||
REPOSITORY_KEYS_TO_EXPORT,
|
||||
)
|
||||
|
||||
from .const import DOMAIN, TV, URL_BASE
|
||||
from .coordinator import HacsUpdateCoordinator
|
||||
from .data_client import HacsDataClient
|
||||
from .enums import (
|
||||
ConfigurationType,
|
||||
HacsCategory,
|
||||
HacsDisabledReason,
|
||||
HacsDispatchEvent,
|
||||
|
|
@ -58,12 +58,14 @@ from .exceptions import (
|
|||
HacsRepositoryExistException,
|
||||
HomeAssistantCoreRepositoryException,
|
||||
)
|
||||
from .repositories import RERPOSITORY_CLASSES
|
||||
from .utils.decode import decode_content
|
||||
from .repositories import REPOSITORY_CLASSES
|
||||
from .repositories.base import HACS_MANIFEST_KEYS_TO_EXPORT, REPOSITORY_KEYS_TO_EXPORT
|
||||
from .utils.file_system import async_exists
|
||||
from .utils.json import json_loads
|
||||
from .utils.logger import LOGGER
|
||||
from .utils.queue_manager import QueueManager
|
||||
from .utils.store import async_load_from_store, async_save_to_store
|
||||
from .utils.workarounds import async_register_static_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .repositories.base import HacsRepository
|
||||
|
|
@ -113,15 +115,11 @@ class HacsConfiguration:
|
|||
appdaemon: bool = False
|
||||
config: dict[str, Any] = field(default_factory=dict)
|
||||
config_entry: ConfigEntry | None = None
|
||||
config_type: ConfigurationType | None = None
|
||||
country: str = "ALL"
|
||||
debug: bool = False
|
||||
dev: bool = False
|
||||
experimental: bool = False
|
||||
frontend_repo_url: str = ""
|
||||
frontend_repo: str = ""
|
||||
netdaemon_path: str = "netdaemon/apps/"
|
||||
netdaemon: bool = False
|
||||
plugin_path: str = "www/community/"
|
||||
python_script_path: str = "python_scripts/"
|
||||
python_script: bool = False
|
||||
|
|
@ -142,6 +140,8 @@ class HacsConfiguration:
|
|||
raise HacsException("Configuration is not valid.")
|
||||
|
||||
for key in data:
|
||||
if key in {"experimental", "netdaemon", "release_limit", "debug"}:
|
||||
continue
|
||||
self.__setattr__(key, data[key])
|
||||
|
||||
|
||||
|
|
@ -217,6 +217,13 @@ class HacsRepositories:
|
|||
"""Return a list of downloaded repositories."""
|
||||
return [repo for repo in self._repositories if repo.data.installed]
|
||||
|
||||
def category_downloaded(self, category: HacsCategory) -> bool:
|
||||
"""Check if a given category has been downloaded."""
|
||||
for repository in self.list_downloaded:
|
||||
if repository.data.category == category:
|
||||
return True
|
||||
return False
|
||||
|
||||
def register(self, repository: HacsRepository, default: bool = False) -> None:
|
||||
"""Register a repository."""
|
||||
repo_id = str(repository.data.id)
|
||||
|
|
@ -348,9 +355,6 @@ class HacsRepositories:
|
|||
class HacsBase:
|
||||
"""Base HACS class."""
|
||||
|
||||
common = HacsCommon()
|
||||
configuration = HacsConfiguration()
|
||||
core = HacsCore()
|
||||
data: HacsData | None = None
|
||||
data_client: HacsDataClient | None = None
|
||||
frontend_version: str | None = None
|
||||
|
|
@ -358,17 +362,24 @@ class HacsBase:
|
|||
githubapi: GitHubAPI | None = None
|
||||
hass: HomeAssistant | None = None
|
||||
integration: Integration | None = None
|
||||
log: logging.Logger = LOGGER
|
||||
queue: QueueManager | None = None
|
||||
recuring_tasks = []
|
||||
repositories: HacsRepositories = HacsRepositories()
|
||||
repository: AIOGitHubAPIRepository | None = None
|
||||
session: ClientSession | None = None
|
||||
stage: HacsStage | None = None
|
||||
status = HacsStatus()
|
||||
system = HacsSystem()
|
||||
validation: ValidationManager | None = None
|
||||
version: str | None = None
|
||||
version: AwesomeVersion | None = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize."""
|
||||
self.common = HacsCommon()
|
||||
self.configuration = HacsConfiguration()
|
||||
self.coordinators: dict[HacsCategory, HacsUpdateCoordinator] = {}
|
||||
self.core = HacsCore()
|
||||
self.log = LOGGER
|
||||
self.recurring_tasks: list[Callable[[], None]] = []
|
||||
self.repositories = HacsRepositories()
|
||||
self.status = HacsStatus()
|
||||
self.system = HacsSystem()
|
||||
|
||||
@property
|
||||
def integration_dir(self) -> pathlib.Path:
|
||||
|
|
@ -394,12 +405,7 @@ class HacsBase:
|
|||
if reason != HacsDisabledReason.REMOVED:
|
||||
self.log.error("HACS is disabled - %s", reason)
|
||||
|
||||
if (
|
||||
reason == HacsDisabledReason.INVALID_TOKEN
|
||||
and self.configuration.config_type == ConfigurationType.CONFIG_ENTRY
|
||||
):
|
||||
self.configuration.config_entry.state = ConfigEntryState.SETUP_ERROR
|
||||
self.configuration.config_entry.reason = "Authentication failed"
|
||||
if reason == HacsDisabledReason.INVALID_TOKEN:
|
||||
self.hass.add_job(self.configuration.config_entry.async_start_reauth, self.hass)
|
||||
|
||||
def enable_hacs(self) -> None:
|
||||
|
|
@ -413,12 +419,14 @@ class HacsBase:
|
|||
if category not in self.common.categories:
|
||||
self.log.info("Enable category: %s", category)
|
||||
self.common.categories.add(category)
|
||||
self.coordinators[category] = HacsUpdateCoordinator()
|
||||
|
||||
def disable_hacs_category(self, category: HacsCategory) -> None:
|
||||
"""Disable HACS category."""
|
||||
if category in self.common.categories:
|
||||
self.log.info("Disabling category: %s", category)
|
||||
self.common.categories.pop(category)
|
||||
self.coordinators.pop(category)
|
||||
|
||||
async def async_save_file(self, file_path: str, content: Any) -> bool:
|
||||
"""Save a file."""
|
||||
|
|
@ -451,12 +459,13 @@ class HacsBase:
|
|||
try:
|
||||
await self.hass.async_add_executor_job(_write_file)
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as error:
|
||||
self.log.error("Could not write data to %s - %s", file_path, error)
|
||||
return False
|
||||
|
||||
return os.path.exists(file_path)
|
||||
return await async_exists(self.hass, file_path)
|
||||
|
||||
async def async_can_update(self) -> int:
|
||||
"""Helper to calculate the number of repositories we can fetch data for."""
|
||||
|
|
@ -472,24 +481,13 @@ class HacsBase:
|
|||
)
|
||||
self.disable_hacs(HacsDisabledReason.RATE_LIMIT)
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.log.exception(exception)
|
||||
|
||||
return 0
|
||||
|
||||
async def async_github_get_hacs_default_file(self, filename: str) -> list:
|
||||
"""Get the content of a default file."""
|
||||
response = await self.async_github_api_method(
|
||||
method=self.githubapi.repos.contents.get,
|
||||
repository=HacsGitHubRepo.DEFAULT,
|
||||
path=filename,
|
||||
)
|
||||
if response is None:
|
||||
return []
|
||||
|
||||
return json_loads(decode_content(response.data.content))
|
||||
|
||||
async def async_github_api_method(
|
||||
self,
|
||||
method: Callable[[], Awaitable[TV]],
|
||||
|
|
@ -513,7 +511,8 @@ class HacsBase:
|
|||
except GitHubException as exception:
|
||||
_exception = exception
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.log.exception(exception)
|
||||
_exception = exception
|
||||
|
|
@ -545,7 +544,7 @@ class HacsBase:
|
|||
):
|
||||
raise AddonRepositoryException()
|
||||
|
||||
if category not in RERPOSITORY_CLASSES:
|
||||
if category not in REPOSITORY_CLASSES:
|
||||
self.log.warning(
|
||||
"%s is not a valid repository category, %s will not be registered.",
|
||||
category,
|
||||
|
|
@ -556,7 +555,7 @@ class HacsBase:
|
|||
if (renamed := self.common.renamed_repositories.get(repository_full_name)) is not None:
|
||||
repository_full_name = renamed
|
||||
|
||||
repository: HacsRepository = RERPOSITORY_CLASSES[category](self, repository_full_name)
|
||||
repository: HacsRepository = REPOSITORY_CLASSES[category](self, repository_full_name)
|
||||
if check:
|
||||
try:
|
||||
await repository.async_registration(ref)
|
||||
|
|
@ -566,7 +565,8 @@ class HacsBase:
|
|||
self.log.error("Validation for %s failed.", repository_full_name)
|
||||
if self.system.action:
|
||||
raise HacsException(
|
||||
f"::error:: Validation for {repository_full_name} failed."
|
||||
f"::error:: Validation for {
|
||||
repository_full_name} failed."
|
||||
)
|
||||
return repository.validate.errors
|
||||
if self.system.action:
|
||||
|
|
@ -582,7 +582,8 @@ class HacsBase:
|
|||
except AIOGitHubAPIException as exception:
|
||||
self.common.skip.add(repository.data.full_name)
|
||||
raise HacsException(
|
||||
f"Validation for {repository_full_name} failed with {exception}."
|
||||
f"Validation for {
|
||||
repository_full_name} failed with {exception}."
|
||||
) from exception
|
||||
|
||||
if self.status.new:
|
||||
|
|
@ -592,7 +593,7 @@ class HacsBase:
|
|||
repository.data.id = repository_id
|
||||
|
||||
else:
|
||||
if self.hass is not None and ((check and repository.data.new) or self.status.new):
|
||||
if self.hass is not None and check and repository.data.new:
|
||||
self.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
{
|
||||
|
|
@ -613,91 +614,84 @@ class HacsBase:
|
|||
for repo in critical:
|
||||
if not repo["acknowledged"]:
|
||||
self.log.critical("URGENT!: Check the HACS panel!")
|
||||
self.hass.components.persistent_notification.create(
|
||||
title="URGENT!", message="**Check the HACS panel!**"
|
||||
async_create_persistent_notification(
|
||||
self.hass, title="URGENT!", message="**Check the HACS panel!**"
|
||||
)
|
||||
break
|
||||
|
||||
if not self.configuration.experimental:
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_update_downloaded_repositories, timedelta(hours=48)
|
||||
)
|
||||
)
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_update_all_repositories,
|
||||
timedelta(hours=96),
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_load_hacs_from_github,
|
||||
timedelta(hours=48),
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_update_downloaded_custom_repositories, timedelta(hours=48)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass,
|
||||
self.async_load_hacs_from_github,
|
||||
timedelta(hours=48),
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_get_all_category_repositories, timedelta(hours=6)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.async_update_downloaded_custom_repositories, timedelta(hours=48)
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_check_rate_limit, timedelta(minutes=5)
|
||||
)
|
||||
)
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_prosess_queue, timedelta(minutes=10)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.async_get_all_category_repositories, timedelta(hours=6)
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_handle_critical_repositories, timedelta(hours=6)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(self.hass, self.async_check_rate_limit, timedelta(minutes=5))
|
||||
)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(self.hass, self.async_process_queue, timedelta(minutes=10))
|
||||
)
|
||||
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.async_handle_critical_repositories, timedelta(hours=6)
|
||||
)
|
||||
)
|
||||
|
||||
self.hass.bus.async_listen_once(
|
||||
unsub = self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_FINAL_WRITE, self.data.async_force_write
|
||||
)
|
||||
if config_entry := self.configuration.config_entry:
|
||||
config_entry.async_on_unload(unsub)
|
||||
|
||||
self.log.debug("There are %s scheduled recurring tasks", len(self.recuring_tasks))
|
||||
self.log.debug("There are %s scheduled recurring tasks", len(self.recurring_tasks))
|
||||
|
||||
self.status.startup = False
|
||||
self.async_dispatch(HacsDispatchEvent.STATUS, {})
|
||||
|
||||
await self.async_handle_removed_repositories()
|
||||
await self.async_get_all_category_repositories()
|
||||
await self.async_update_downloaded_repositories()
|
||||
|
||||
self.set_stage(HacsStage.RUNNING)
|
||||
|
||||
self.async_dispatch(HacsDispatchEvent.RELOAD, {"force": True})
|
||||
|
||||
await self.async_handle_critical_repositories()
|
||||
await self.async_prosess_queue()
|
||||
await self.async_process_queue()
|
||||
|
||||
self.async_dispatch(HacsDispatchEvent.STATUS, {})
|
||||
|
||||
async def async_download_file(self, url: str, *, headers: dict | None = None) -> bytes | None:
|
||||
async def async_download_file(
|
||||
self,
|
||||
url: str,
|
||||
*,
|
||||
headers: dict | None = None,
|
||||
keep_url: bool = False,
|
||||
nolog: bool = False,
|
||||
**_,
|
||||
) -> bytes | None:
|
||||
"""Download files, and return the content."""
|
||||
if url is None:
|
||||
return None
|
||||
|
||||
if "tags/" in url:
|
||||
if not keep_url and "tags/" in url:
|
||||
url = url.replace("tags/", "")
|
||||
|
||||
self.log.debug("Downloading %s", url)
|
||||
self.log.debug("Trying to download %s", url)
|
||||
timeouts = 0
|
||||
|
||||
while timeouts < 5:
|
||||
|
|
@ -713,9 +707,10 @@ class HacsBase:
|
|||
return await request.read()
|
||||
|
||||
raise HacsException(
|
||||
f"Got status code {request.status} when trying to download {url}"
|
||||
f"Got status code {
|
||||
request.status} when trying to download {url}"
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
self.log.warning(
|
||||
"A timeout of 60! seconds was encountered while downloading %s, "
|
||||
"using over 60 seconds to download a single file is not normal. "
|
||||
|
|
@ -731,23 +726,34 @@ class HacsBase:
|
|||
continue
|
||||
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.log.exception("Download failed - %s", exception)
|
||||
if not nolog:
|
||||
self.log.exception("Download failed - %s", exception)
|
||||
|
||||
return None
|
||||
|
||||
async def async_recreate_entities(self) -> None:
|
||||
"""Recreate entities."""
|
||||
if self.configuration == ConfigurationType.YAML or not self.configuration.experimental:
|
||||
return
|
||||
platforms = [Platform.UPDATE]
|
||||
|
||||
platforms = [Platform.SENSOR, Platform.UPDATE]
|
||||
|
||||
await self.hass.config_entries.async_unload_platforms(
|
||||
entry=self.configuration.config_entry,
|
||||
platforms=platforms,
|
||||
)
|
||||
# Workaround for core versions without https://github.com/home-assistant/core/pull/117084
|
||||
if self.core.ha_version < AwesomeVersion("2024.6.0"):
|
||||
unload_platforms_lock = asyncio.Lock()
|
||||
async with unload_platforms_lock:
|
||||
on_unload = self.configuration.config_entry._on_unload
|
||||
self.configuration.config_entry._on_unload = []
|
||||
await self.hass.config_entries.async_unload_platforms(
|
||||
entry=self.configuration.config_entry,
|
||||
platforms=platforms,
|
||||
)
|
||||
self.configuration.config_entry._on_unload = on_unload
|
||||
else:
|
||||
await self.hass.config_entries.async_unload_platforms(
|
||||
entry=self.configuration.config_entry,
|
||||
platforms=platforms,
|
||||
)
|
||||
await self.hass.config_entries.async_forward_entry_setups(
|
||||
self.configuration.config_entry, platforms
|
||||
)
|
||||
|
|
@ -760,49 +766,40 @@ class HacsBase:
|
|||
def set_active_categories(self) -> None:
|
||||
"""Set the active categories."""
|
||||
self.common.categories = set()
|
||||
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN):
|
||||
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN, HacsCategory.TEMPLATE):
|
||||
self.enable_hacs_category(HacsCategory(category))
|
||||
|
||||
if self.configuration.experimental and self.core.ha_version >= "2023.4.0b0":
|
||||
self.enable_hacs_category(HacsCategory.TEMPLATE)
|
||||
|
||||
if HacsCategory.PYTHON_SCRIPT in self.hass.config.components:
|
||||
if (
|
||||
HacsCategory.PYTHON_SCRIPT in self.hass.config.components
|
||||
or self.repositories.category_downloaded(HacsCategory.PYTHON_SCRIPT)
|
||||
):
|
||||
self.enable_hacs_category(HacsCategory.PYTHON_SCRIPT)
|
||||
|
||||
if self.hass.services.has_service("frontend", "reload_themes"):
|
||||
if self.hass.services.has_service(
|
||||
"frontend", "reload_themes"
|
||||
) or self.repositories.category_downloaded(HacsCategory.THEME):
|
||||
self.enable_hacs_category(HacsCategory.THEME)
|
||||
|
||||
if self.configuration.appdaemon:
|
||||
self.enable_hacs_category(HacsCategory.APPDAEMON)
|
||||
if self.configuration.netdaemon:
|
||||
downloaded_netdaemon = [
|
||||
x
|
||||
for x in self.repositories.list_downloaded
|
||||
if x.data.category == HacsCategory.NETDAEMON
|
||||
]
|
||||
if len(downloaded_netdaemon) != 0:
|
||||
self.log.warning(
|
||||
"NetDaemon in HACS is deprectaded. It will stop working in the future. "
|
||||
"Please remove all your current NetDaemon repositories from HACS "
|
||||
"and download them manually if you want to continue using them."
|
||||
)
|
||||
self.enable_hacs_category(HacsCategory.NETDAEMON)
|
||||
|
||||
async def async_load_hacs_from_github(self, _=None) -> None:
|
||||
"""Load HACS from GitHub."""
|
||||
if self.configuration.experimental and self.status.inital_fetch_done:
|
||||
if self.status.inital_fetch_done:
|
||||
return
|
||||
|
||||
try:
|
||||
repository = self.repositories.get_by_full_name(HacsGitHubRepo.INTEGRATION)
|
||||
should_recreate_entities = False
|
||||
if repository is None:
|
||||
should_recreate_entities = True
|
||||
await self.async_register_repository(
|
||||
repository_full_name=HacsGitHubRepo.INTEGRATION,
|
||||
category=HacsCategory.INTEGRATION,
|
||||
default=True,
|
||||
)
|
||||
repository = self.repositories.get_by_full_name(HacsGitHubRepo.INTEGRATION)
|
||||
elif self.configuration.experimental and not self.status.startup:
|
||||
elif not self.status.startup:
|
||||
self.log.error("Scheduling update of hacs/integration")
|
||||
self.queue.add(repository.common_update())
|
||||
if repository is None:
|
||||
|
|
@ -813,6 +810,9 @@ class HacsBase:
|
|||
repository.data.new = False
|
||||
repository.data.releases = True
|
||||
|
||||
if should_recreate_entities:
|
||||
await self.async_recreate_entities()
|
||||
|
||||
self.repository = repository.repository_object
|
||||
self.repositories.mark_default(repository)
|
||||
except HacsException as exception:
|
||||
|
|
@ -832,8 +832,6 @@ class HacsBase:
|
|||
await asyncio.gather(
|
||||
*[
|
||||
self.async_get_category_repositories_experimental(category)
|
||||
if self.configuration.experimental
|
||||
else self.async_get_category_repositories(HacsCategory(category))
|
||||
for category in self.common.categories or []
|
||||
]
|
||||
)
|
||||
|
|
@ -842,7 +840,7 @@ class HacsBase:
|
|||
"""Update all category repositories."""
|
||||
self.log.debug("Fetching updated content for %s", category)
|
||||
try:
|
||||
category_data = await self.data_client.get_data(category)
|
||||
category_data = await self.data_client.get_data(category, validate=True)
|
||||
except HacsNotModifiedException:
|
||||
self.log.debug("No updates for %s", category)
|
||||
return
|
||||
|
|
@ -853,14 +851,14 @@ class HacsBase:
|
|||
await self.data.register_unknown_repositories(category_data, category)
|
||||
|
||||
for repo_id, repo_data in category_data.items():
|
||||
repo = repo_data["full_name"]
|
||||
if self.common.renamed_repositories.get(repo):
|
||||
repo = self.common.renamed_repositories[repo]
|
||||
if self.repositories.is_removed(repo):
|
||||
repo_name = repo_data["full_name"]
|
||||
if self.common.renamed_repositories.get(repo_name):
|
||||
repo_name = self.common.renamed_repositories[repo_name]
|
||||
if self.repositories.is_removed(repo_name):
|
||||
continue
|
||||
if repo in self.common.archived_repositories:
|
||||
if repo_name in self.common.archived_repositories:
|
||||
continue
|
||||
if repository := self.repositories.get_by_full_name(repo):
|
||||
if repository := self.repositories.get_by_full_name(repo_name):
|
||||
self.repositories.set_repository_id(repository, repo_id)
|
||||
self.repositories.mark_default(repository)
|
||||
if repository.data.last_fetched is None or (
|
||||
|
|
@ -871,15 +869,6 @@ class HacsBase:
|
|||
repository.repository_manifest.update_data(
|
||||
{**dict(HACS_MANIFEST_KEYS_TO_EXPORT), **manifest}
|
||||
)
|
||||
self.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
{
|
||||
"id": 1337,
|
||||
"action": "update",
|
||||
"repository": repository.data.full_name,
|
||||
"repository_id": repository.data.id,
|
||||
},
|
||||
)
|
||||
|
||||
if category == "integration":
|
||||
self.status.inital_fetch_done = True
|
||||
|
|
@ -896,50 +885,8 @@ class HacsBase:
|
|||
)
|
||||
self.repositories.unregister(repository)
|
||||
|
||||
async def async_get_category_repositories(self, category: HacsCategory) -> None:
|
||||
"""Get repositories from category."""
|
||||
if self.system.disabled:
|
||||
return
|
||||
try:
|
||||
repositories = await self.async_github_get_hacs_default_file(category)
|
||||
except HacsException:
|
||||
return
|
||||
|
||||
for repo in repositories:
|
||||
if self.common.renamed_repositories.get(repo):
|
||||
repo = self.common.renamed_repositories[repo]
|
||||
if self.repositories.is_removed(repo):
|
||||
continue
|
||||
if repo in self.common.archived_repositories:
|
||||
continue
|
||||
repository = self.repositories.get_by_full_name(repo)
|
||||
if repository is not None:
|
||||
self.repositories.mark_default(repository)
|
||||
if self.status.new and self.configuration.dev:
|
||||
# Force update for new installations
|
||||
self.queue.add(repository.common_update())
|
||||
continue
|
||||
|
||||
self.queue.add(
|
||||
self.async_register_repository(
|
||||
repository_full_name=repo,
|
||||
category=category,
|
||||
default=True,
|
||||
)
|
||||
)
|
||||
|
||||
async def async_update_all_repositories(self, _=None) -> None:
|
||||
"""Update all repositories."""
|
||||
if self.system.disabled:
|
||||
return
|
||||
self.log.debug("Starting recurring background task for all repositories")
|
||||
|
||||
for repository in self.repositories.list_all:
|
||||
if repository.data.category in self.common.categories:
|
||||
self.queue.add(repository.common_update())
|
||||
|
||||
self.async_dispatch(HacsDispatchEvent.REPOSITORY, {"action": "reload"})
|
||||
self.log.debug("Recurring background task for all repositories done")
|
||||
self.async_dispatch(HacsDispatchEvent.REPOSITORY, {})
|
||||
self.coordinators[category].async_update_listeners()
|
||||
|
||||
async def async_check_rate_limit(self, _=None) -> None:
|
||||
"""Check rate limit."""
|
||||
|
|
@ -951,9 +898,9 @@ class HacsBase:
|
|||
self.log.debug("Ratelimit indicate we can update %s", can_update)
|
||||
if can_update > 0:
|
||||
self.enable_hacs()
|
||||
await self.async_prosess_queue()
|
||||
await self.async_process_queue()
|
||||
|
||||
async def async_prosess_queue(self, _=None) -> None:
|
||||
async def async_process_queue(self, _=None) -> None:
|
||||
"""Process the queue."""
|
||||
if self.system.disabled:
|
||||
self.log.debug("HACS is disabled")
|
||||
|
|
@ -993,12 +940,7 @@ class HacsBase:
|
|||
self.log.info("Loading removed repositories")
|
||||
|
||||
try:
|
||||
if self.configuration.experimental:
|
||||
removed_repositories = await self.data_client.get_data("removed")
|
||||
else:
|
||||
removed_repositories = await self.async_github_get_hacs_default_file(
|
||||
HacsCategory.REMOVED
|
||||
)
|
||||
removed_repositories = await self.data_client.get_data("removed", validate=True)
|
||||
except HacsException:
|
||||
return
|
||||
|
||||
|
|
@ -1013,21 +955,20 @@ class HacsBase:
|
|||
continue
|
||||
if repository.data.installed:
|
||||
if removed.removal_type != "critical":
|
||||
if self.configuration.experimental:
|
||||
async_create_issue(
|
||||
hass=self.hass,
|
||||
domain=DOMAIN,
|
||||
issue_id=f"removed_{repository.data.id}",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="removed",
|
||||
translation_placeholders={
|
||||
"name": repository.data.full_name,
|
||||
"reason": removed.reason,
|
||||
"repositry_id": repository.data.id,
|
||||
},
|
||||
)
|
||||
async_create_issue(
|
||||
hass=self.hass,
|
||||
domain=DOMAIN,
|
||||
issue_id=f"removed_{repository.data.id}",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="removed",
|
||||
translation_placeholders={
|
||||
"name": repository.data.full_name,
|
||||
"reason": removed.reason,
|
||||
"repositry_id": repository.data.id,
|
||||
},
|
||||
)
|
||||
self.log.warning(
|
||||
"You have '%s' installed with HACS "
|
||||
"this repository has been removed from HACS, please consider removing it. "
|
||||
|
|
@ -1042,30 +983,43 @@ class HacsBase:
|
|||
if need_to_save:
|
||||
await self.data.async_write()
|
||||
|
||||
async def async_update_downloaded_repositories(self, _=None) -> None:
|
||||
"""Execute the task."""
|
||||
if self.system.disabled or self.configuration.experimental:
|
||||
return
|
||||
self.log.info("Starting recurring background task for downloaded repositories")
|
||||
|
||||
for repository in self.repositories.list_downloaded:
|
||||
if repository.data.category in self.common.categories:
|
||||
self.queue.add(repository.update_repository(ignore_issues=True))
|
||||
|
||||
self.log.debug("Recurring background task for downloaded repositories done")
|
||||
|
||||
async def async_update_downloaded_custom_repositories(self, _=None) -> None:
|
||||
"""Execute the task."""
|
||||
if self.system.disabled or not self.configuration.experimental:
|
||||
if self.system.disabled:
|
||||
return
|
||||
self.log.info("Starting recurring background task for downloaded custom repositories")
|
||||
|
||||
repositories_to_update = 0
|
||||
repositories_updated = asyncio.Event()
|
||||
|
||||
async def update_repository(repository: HacsRepository) -> None:
|
||||
"""Update a repository"""
|
||||
nonlocal repositories_to_update
|
||||
await repository.update_repository(ignore_issues=True)
|
||||
repositories_to_update -= 1
|
||||
if not repositories_to_update:
|
||||
repositories_updated.set()
|
||||
|
||||
for repository in self.repositories.list_downloaded:
|
||||
if (
|
||||
repository.data.category in self.common.categories
|
||||
and not self.repositories.is_default(repository.data.id)
|
||||
):
|
||||
self.queue.add(repository.update_repository(ignore_issues=True))
|
||||
repositories_to_update += 1
|
||||
self.queue.add(update_repository(repository))
|
||||
|
||||
async def update_coordinators() -> None:
|
||||
"""Update all coordinators."""
|
||||
await repositories_updated.wait()
|
||||
for coordinator in self.coordinators.values():
|
||||
coordinator.async_update_listeners()
|
||||
|
||||
if config_entry := self.configuration.config_entry:
|
||||
config_entry.async_create_background_task(
|
||||
self.hass, update_coordinators(), "update_coordinators"
|
||||
)
|
||||
else:
|
||||
self.hass.async_create_background_task(update_coordinators(), "update_coordinators")
|
||||
|
||||
self.log.debug("Recurring background task for downloaded custom repositories done")
|
||||
|
||||
|
|
@ -1077,10 +1031,7 @@ class HacsBase:
|
|||
was_installed = False
|
||||
|
||||
try:
|
||||
if self.configuration.experimental:
|
||||
critical = await self.data_client.get_data("critical")
|
||||
else:
|
||||
critical = await self.async_github_get_hacs_default_file("critical")
|
||||
critical = await self.data_client.get_data("critical", validate=True)
|
||||
except (GitHubNotModifiedException, HacsNotModifiedException):
|
||||
return
|
||||
except HacsException:
|
||||
|
|
@ -1134,11 +1085,10 @@ class HacsBase:
|
|||
self.log.critical("Restarting Home Assistant")
|
||||
self.hass.async_create_task(self.hass.async_stop(100))
|
||||
|
||||
@callback
|
||||
def async_setup_frontend_endpoint_plugin(self) -> None:
|
||||
async def async_setup_frontend_endpoint_plugin(self) -> None:
|
||||
"""Setup the http endpoints for plugins if its not already handled."""
|
||||
if self.status.active_frontend_endpoint_plugin or not os.path.exists(
|
||||
self.hass.config.path("www/community")
|
||||
if self.status.active_frontend_endpoint_plugin or not await async_exists(
|
||||
self.hass, self.hass.config.path("www/community")
|
||||
):
|
||||
return
|
||||
|
||||
|
|
@ -1150,26 +1100,11 @@ class HacsBase:
|
|||
use_cache,
|
||||
)
|
||||
|
||||
self.hass.http.register_static_path(
|
||||
await async_register_static_path(
|
||||
self.hass,
|
||||
URL_BASE,
|
||||
self.hass.config.path("www/community"),
|
||||
cache_headers=use_cache,
|
||||
)
|
||||
|
||||
self.status.active_frontend_endpoint_plugin = True
|
||||
|
||||
@callback
|
||||
def async_setup_frontend_endpoint_themes(self) -> None:
|
||||
"""Setup the http endpoints for themes if its not already handled."""
|
||||
if (
|
||||
self.configuration.experimental
|
||||
or self.status.active_frontend_endpoint_theme
|
||||
or not os.path.exists(self.hass.config.path("themes"))
|
||||
):
|
||||
return
|
||||
|
||||
self.log.info("Setting up themes endpoint")
|
||||
# Register themes
|
||||
self.hass.http.register_static_path(f"{URL_BASE}/themes", self.hass.config.path("themes"))
|
||||
|
||||
self.status.active_frontend_endpoint_theme = True
|
||||
|
|
|
|||
|
|
@ -1,29 +1,32 @@
|
|||
"""Adds config flow for HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogithubapi import GitHubDeviceAPI, GitHubException
|
||||
from aiogithubapi import (
|
||||
GitHubDeviceAPI,
|
||||
GitHubException,
|
||||
GitHubLoginDeviceModel,
|
||||
GitHubLoginOauthModel,
|
||||
)
|
||||
from aiogithubapi.common.const import OAUTH_USER_LOGIN
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import ConfigFlow, OptionsFlow
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import UnknownFlow
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.loader import async_get_integration
|
||||
import voluptuous as vol
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import CLIENT_ID, DOMAIN, LOCALE, MINIMUM_HA_VERSION
|
||||
from .enums import ConfigurationType
|
||||
from .utils.configuration_schema import (
|
||||
APPDAEMON,
|
||||
COUNTRY,
|
||||
DEBUG,
|
||||
EXPERIMENTAL,
|
||||
NETDAEMON,
|
||||
RELEASE_LIMIT,
|
||||
SIDEPANEL_ICON,
|
||||
SIDEPANEL_TITLE,
|
||||
)
|
||||
|
|
@ -33,23 +36,22 @@ if TYPE_CHECKING:
|
|||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
class HacsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for HACS."""
|
||||
|
||||
hass: HomeAssistant
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
hass: HomeAssistant
|
||||
activation_task: asyncio.Task | None = None
|
||||
device: GitHubDeviceAPI | None = None
|
||||
|
||||
_registration: GitHubLoginDeviceModel | None = None
|
||||
_activation: GitHubLoginOauthModel | None = None
|
||||
_reauth: bool = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize."""
|
||||
self._errors = {}
|
||||
self.device = None
|
||||
self.activation = None
|
||||
self.log = LOGGER
|
||||
self._progress_task = None
|
||||
self._login_device = None
|
||||
self._reauth = False
|
||||
self._user_input = {}
|
||||
|
||||
async def async_step_user(self, user_input):
|
||||
|
|
@ -69,48 +71,55 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
return await self.async_step_device(user_input)
|
||||
|
||||
## Initial form
|
||||
# Initial form
|
||||
return await self._show_config_form(user_input)
|
||||
|
||||
async def async_step_device(self, _user_input):
|
||||
"""Handle device steps"""
|
||||
"""Handle device steps."""
|
||||
|
||||
async def _wait_for_activation(_=None):
|
||||
if self._login_device is None or self._login_device.expires_in is None:
|
||||
async_call_later(self.hass, 1, _wait_for_activation)
|
||||
return
|
||||
async def _wait_for_activation() -> None:
|
||||
try:
|
||||
response = await self.device.activation(device_code=self._registration.device_code)
|
||||
self._activation = response.data
|
||||
finally:
|
||||
|
||||
response = await self.device.activation(device_code=self._login_device.device_code)
|
||||
self.activation = response.data
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
||||
)
|
||||
async def _progress():
|
||||
with suppress(UnknownFlow):
|
||||
await self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
||||
|
||||
if not self.activation:
|
||||
if not self.device:
|
||||
integration = await async_get_integration(self.hass, DOMAIN)
|
||||
if not self.device:
|
||||
self.device = GitHubDeviceAPI(
|
||||
client_id=CLIENT_ID,
|
||||
session=aiohttp_client.async_get_clientsession(self.hass),
|
||||
**{"client_name": f"HACS/{integration.version}"},
|
||||
)
|
||||
async_call_later(self.hass, 1, _wait_for_activation)
|
||||
self.device = GitHubDeviceAPI(
|
||||
client_id=CLIENT_ID,
|
||||
session=aiohttp_client.async_get_clientsession(self.hass),
|
||||
**{"client_name": f"HACS/{integration.version}"},
|
||||
)
|
||||
try:
|
||||
response = await self.device.register()
|
||||
self._login_device = response.data
|
||||
return self.async_show_progress(
|
||||
step_id="device",
|
||||
progress_action="wait_for_device",
|
||||
description_placeholders={
|
||||
"url": OAUTH_USER_LOGIN,
|
||||
"code": self._login_device.user_code,
|
||||
},
|
||||
)
|
||||
self._registration = response.data
|
||||
except GitHubException as exception:
|
||||
self.log.error(exception)
|
||||
return self.async_abort(reason="github")
|
||||
LOGGER.exception(exception)
|
||||
return self.async_abort(reason="could_not_register")
|
||||
|
||||
return self.async_show_progress_done(next_step_id="device_done")
|
||||
if self.activation_task is None:
|
||||
self.activation_task = self.hass.async_create_task(_wait_for_activation())
|
||||
|
||||
if self.activation_task.done():
|
||||
if (exception := self.activation_task.exception()) is not None:
|
||||
LOGGER.exception(exception)
|
||||
return self.async_show_progress_done(next_step_id="could_not_register")
|
||||
return self.async_show_progress_done(next_step_id="device_done")
|
||||
|
||||
show_progress_kwargs = {
|
||||
"step_id": "device",
|
||||
"progress_action": "wait_for_device",
|
||||
"description_placeholders": {
|
||||
"url": OAUTH_USER_LOGIN,
|
||||
"code": self._registration.user_code,
|
||||
},
|
||||
"progress_task": self.activation_task,
|
||||
}
|
||||
return self.async_show_progress(**show_progress_kwargs)
|
||||
|
||||
async def _show_config_form(self, user_input):
|
||||
"""Show the configuration form to edit location data."""
|
||||
|
|
@ -133,9 +142,6 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"acc_untested", default=user_input.get("acc_untested", False)
|
||||
): bool,
|
||||
vol.Required("acc_disable", default=user_input.get("acc_disable", False)): bool,
|
||||
vol.Optional(
|
||||
"experimental", default=user_input.get("experimental", False)
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
errors=self._errors,
|
||||
|
|
@ -146,7 +152,7 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
if self._reauth:
|
||||
existing_entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
self.hass.config_entries.async_update_entry(
|
||||
existing_entry, data={**existing_entry.data, "token": self.activation.access_token}
|
||||
existing_entry, data={**existing_entry.data, "token": self._activation.access_token}
|
||||
)
|
||||
await self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
|
@ -154,13 +160,17 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_create_entry(
|
||||
title="",
|
||||
data={
|
||||
"token": self.activation.access_token,
|
||||
"token": self._activation.access_token,
|
||||
},
|
||||
options={
|
||||
"experimental": self._user_input.get("experimental", False),
|
||||
"experimental": True,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_could_not_register(self, _user_input=None):
|
||||
"""Handle issues that need transition await from progress step."""
|
||||
return self.async_abort(reason="could_not_register")
|
||||
|
||||
async def async_step_reauth(self, _user_input=None):
|
||||
"""Perform reauth upon an API authentication error."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
|
@ -181,12 +191,13 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return HacsOptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
class HacsOptionsFlowHandler(OptionsFlow):
|
||||
"""HACS config flow options handler."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize HACS options flow."""
|
||||
self.config_entry = config_entry
|
||||
if AwesomeVersion(HAVERSION) < "2024.11.99":
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, _user_input=None):
|
||||
"""Manage the options."""
|
||||
|
|
@ -196,10 +207,7 @@ class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
|||
"""Handle a flow initialized by the user."""
|
||||
hacs: HacsBase = self.hass.data.get(DOMAIN)
|
||||
if user_input is not None:
|
||||
limit = int(user_input.get(RELEASE_LIMIT, 5))
|
||||
if limit <= 0 or limit > 100:
|
||||
return self.async_abort(reason="release_limit_value")
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
return self.async_create_entry(title="", data={**user_input, "experimental": True})
|
||||
|
||||
if hacs is None or hacs.configuration is None:
|
||||
return self.async_abort(reason="not_setup")
|
||||
|
|
@ -207,18 +215,11 @@ class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
|||
if hacs.queue.has_pending_tasks:
|
||||
return self.async_abort(reason="pending_tasks")
|
||||
|
||||
if hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
schema = {vol.Optional("not_in_use", default=""): str}
|
||||
else:
|
||||
schema = {
|
||||
vol.Optional(SIDEPANEL_TITLE, default=hacs.configuration.sidepanel_title): str,
|
||||
vol.Optional(SIDEPANEL_ICON, default=hacs.configuration.sidepanel_icon): str,
|
||||
vol.Optional(RELEASE_LIMIT, default=hacs.configuration.release_limit): int,
|
||||
vol.Optional(COUNTRY, default=hacs.configuration.country): vol.In(LOCALE),
|
||||
vol.Optional(APPDAEMON, default=hacs.configuration.appdaemon): bool,
|
||||
vol.Optional(NETDAEMON, default=hacs.configuration.netdaemon): bool,
|
||||
vol.Optional(DEBUG, default=hacs.configuration.debug): bool,
|
||||
vol.Optional(EXPERIMENTAL, default=hacs.configuration.experimental): bool,
|
||||
}
|
||||
schema = {
|
||||
vol.Optional(SIDEPANEL_TITLE, default=hacs.configuration.sidepanel_title): str,
|
||||
vol.Optional(SIDEPANEL_ICON, default=hacs.configuration.sidepanel_icon): str,
|
||||
vol.Optional(COUNTRY, default=hacs.configuration.country): vol.In(LOCALE),
|
||||
vol.Optional(APPDAEMON, default=hacs.configuration.appdaemon): bool,
|
||||
}
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Constants for HACS"""
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
from aiogithubapi.common.const import ACCEPT_HEADERS
|
||||
|
|
@ -6,7 +7,7 @@ from aiogithubapi.common.const import ACCEPT_HEADERS
|
|||
NAME_SHORT = "HACS"
|
||||
DOMAIN = "hacs"
|
||||
CLIENT_ID = "395a8e669c5de9f7c6e8"
|
||||
MINIMUM_HA_VERSION = "2023.6.0"
|
||||
MINIMUM_HA_VERSION = "2024.4.1"
|
||||
|
||||
URL_BASE = "/hacsfiles"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,25 @@
|
|||
"""HACS Data client."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
import voluptuous as vol
|
||||
|
||||
from .exceptions import HacsException, HacsNotModifiedException
|
||||
from .utils.logger import LOGGER
|
||||
from .utils.validate import (
|
||||
VALIDATE_FETCHED_V2_CRITICAL_REPO_SCHEMA,
|
||||
VALIDATE_FETCHED_V2_REMOVED_REPO_SCHEMA,
|
||||
VALIDATE_FETCHED_V2_REPO_DATA,
|
||||
)
|
||||
|
||||
CRITICAL_REMOVED_VALIDATORS = {
|
||||
"critical": VALIDATE_FETCHED_V2_CRITICAL_REPO_SCHEMA,
|
||||
"removed": VALIDATE_FETCHED_V2_REMOVED_REPO_SCHEMA,
|
||||
}
|
||||
|
||||
|
||||
class HacsDataClient:
|
||||
|
|
@ -39,7 +52,7 @@ class HacsDataClient:
|
|||
response.raise_for_status()
|
||||
except HacsNotModifiedException:
|
||||
raise
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
raise HacsException("Timeout of 60s reached") from None
|
||||
except Exception as exception:
|
||||
raise HacsException(f"Error fetching data from HACS: {exception}") from exception
|
||||
|
|
@ -48,9 +61,37 @@ class HacsDataClient:
|
|||
|
||||
return await response.json()
|
||||
|
||||
async def get_data(self, section: str | None) -> dict[str, dict[str, Any]]:
|
||||
async def get_data(self, section: str | None, *, validate: bool) -> dict[str, dict[str, Any]]:
|
||||
"""Get data."""
|
||||
return await self._do_request(filename="data.json", section=section)
|
||||
data = await self._do_request(filename="data.json", section=section)
|
||||
if not validate:
|
||||
return data
|
||||
|
||||
if section in VALIDATE_FETCHED_V2_REPO_DATA:
|
||||
validated = {}
|
||||
for key, repo_data in data.items():
|
||||
try:
|
||||
validated[key] = VALIDATE_FETCHED_V2_REPO_DATA[section](repo_data)
|
||||
except vol.Invalid as exception:
|
||||
LOGGER.info(
|
||||
"Got invalid data for %s (%s)", repo_data.get("full_name", key), exception
|
||||
)
|
||||
continue
|
||||
|
||||
return validated
|
||||
|
||||
if not (validator := CRITICAL_REMOVED_VALIDATORS.get(section)):
|
||||
raise ValueError(f"Do not know how to validate {section}")
|
||||
|
||||
validated = []
|
||||
for repo_data in data:
|
||||
try:
|
||||
validated.append(validator(repo_data))
|
||||
except vol.Invalid as exception:
|
||||
LOGGER.info("Got invalid data for %s (%s)", section, exception)
|
||||
continue
|
||||
|
||||
return validated
|
||||
|
||||
async def get_repositories(self, section: str) -> list[str]:
|
||||
"""Get repositories."""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Diagnostics support for HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
|
@ -10,7 +11,6 @@ from homeassistant.core import HomeAssistant
|
|||
|
||||
from .base import HacsBase
|
||||
from .const import DOMAIN
|
||||
from .utils.configuration_schema import TOKEN
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
|
|
@ -48,8 +48,6 @@ async def async_get_config_entry_diagnostics(
|
|||
"country",
|
||||
"debug",
|
||||
"dev",
|
||||
"experimental",
|
||||
"netdaemon",
|
||||
"python_script",
|
||||
"release_limit",
|
||||
"theme",
|
||||
|
|
@ -79,4 +77,4 @@ async def async_get_config_entry_diagnostics(
|
|||
except GitHubException as exception:
|
||||
data["rate_limit"] = str(exception)
|
||||
|
||||
return async_redact_data(data, (TOKEN,))
|
||||
return async_redact_data(data, ("token",))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""HACS Base entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
|
@ -7,8 +8,10 @@ from homeassistant.core import callback
|
|||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import BaseCoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, HACS_SYSTEM_ID, NAME_SHORT
|
||||
from .coordinator import HacsUpdateCoordinator
|
||||
from .enums import HacsDispatchEvent, HacsGitHubRepo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -39,6 +42,10 @@ class HacsBaseEntity(Entity):
|
|||
"""Initialize."""
|
||||
self.hacs = hacs
|
||||
|
||||
|
||||
class HacsDispatcherEntity(HacsBaseEntity):
|
||||
"""Base HACS entity listening to dispatcher signals."""
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register for status events."""
|
||||
self.async_on_remove(
|
||||
|
|
@ -64,7 +71,7 @@ class HacsBaseEntity(Entity):
|
|||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class HacsSystemEntity(HacsBaseEntity):
|
||||
class HacsSystemEntity(HacsDispatcherEntity):
|
||||
"""Base system entity."""
|
||||
|
||||
_attr_icon = "hacs:hacs"
|
||||
|
|
@ -76,7 +83,7 @@ class HacsSystemEntity(HacsBaseEntity):
|
|||
return system_info(self.hacs)
|
||||
|
||||
|
||||
class HacsRepositoryEntity(HacsBaseEntity):
|
||||
class HacsRepositoryEntity(BaseCoordinatorEntity[HacsUpdateCoordinator], HacsBaseEntity):
|
||||
"""Base repository entity."""
|
||||
|
||||
def __init__(
|
||||
|
|
@ -85,9 +92,11 @@ class HacsRepositoryEntity(HacsBaseEntity):
|
|||
repository: HacsRepository,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(hacs=hacs)
|
||||
BaseCoordinatorEntity.__init__(self, hacs.coordinators[repository.data.category])
|
||||
HacsBaseEntity.__init__(self, hacs=hacs)
|
||||
self.repository = repository
|
||||
self._attr_unique_id = str(repository.data.id)
|
||||
self._repo_last_fetched = repository.data.last_fetched
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
|
@ -100,20 +109,35 @@ class HacsRepositoryEntity(HacsBaseEntity):
|
|||
if self.repository.data.full_name == HacsGitHubRepo.INTEGRATION:
|
||||
return system_info(self.hacs)
|
||||
|
||||
def _manufacturer():
|
||||
if authors := self.repository.data.authors:
|
||||
return ", ".join(author.replace("@", "") for author in authors)
|
||||
return self.repository.data.full_name.split("/")[0]
|
||||
|
||||
return {
|
||||
"identifiers": {(DOMAIN, str(self.repository.data.id))},
|
||||
"name": self.repository.display_name,
|
||||
"model": self.repository.data.category,
|
||||
"manufacturer": ", ".join(
|
||||
author.replace("@", "") for author in self.repository.data.authors
|
||||
),
|
||||
"configuration_url": "homeassistant://hacs",
|
||||
"manufacturer": _manufacturer(),
|
||||
"configuration_url": f"homeassistant://hacs/repository/{self.repository.data.id}",
|
||||
"entry_type": DeviceEntryType.SERVICE,
|
||||
}
|
||||
|
||||
@callback
|
||||
def _update_and_write_state(self, data: dict) -> None:
|
||||
"""Update the entity and write state."""
|
||||
if data.get("repository_id") == self.repository.data.id:
|
||||
self._update()
|
||||
self.async_write_ha_state()
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
if (
|
||||
self._repo_last_fetched is not None
|
||||
and self.repository.data.last_fetched is not None
|
||||
and self._repo_last_fetched >= self.repository.data.last_fetched
|
||||
):
|
||||
return
|
||||
|
||||
self._repo_last_fetched = self.repository.data.last_fetched
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the entity.
|
||||
|
||||
Only used by the generic entity update service.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,20 +1,7 @@
|
|||
"""Helper constants."""
|
||||
|
||||
# pylint: disable=missing-class-docstring
|
||||
import sys
|
||||
|
||||
if sys.version_info.minor >= 11:
|
||||
# Needs Python 3.11
|
||||
from enum import StrEnum # # pylint: disable=no-name-in-module
|
||||
else:
|
||||
try:
|
||||
# https://github.com/home-assistant/core/blob/dev/homeassistant/backports/enum.py
|
||||
# Considered internal to Home Assistant, can be removed whenever.
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
except ImportError:
|
||||
from enum import Enum
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
pass
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class HacsGitHubRepo(StrEnum):
|
||||
|
|
@ -29,7 +16,6 @@ class HacsCategory(StrEnum):
|
|||
INTEGRATION = "integration"
|
||||
LOVELACE = "lovelace"
|
||||
PLUGIN = "plugin" # Kept for legacy purposes
|
||||
NETDAEMON = "netdaemon"
|
||||
PYTHON_SCRIPT = "python_script"
|
||||
TEMPLATE = "template"
|
||||
THEME = "theme"
|
||||
|
|
@ -59,11 +45,6 @@ class RepositoryFile(StrEnum):
|
|||
MAINIFEST_JSON = "manifest.json"
|
||||
|
||||
|
||||
class ConfigurationType(StrEnum):
|
||||
YAML = "yaml"
|
||||
CONFIG_ENTRY = "config_entry"
|
||||
|
||||
|
||||
class LovelaceMode(StrEnum):
|
||||
"""Lovelace Modes."""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,61 +1,53 @@
|
|||
""""Starting setup task: Frontend"."""
|
||||
"""Starting setup task: Frontend."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.components.frontend import (
|
||||
add_extra_js_url,
|
||||
async_register_built_in_panel,
|
||||
)
|
||||
|
||||
from .const import DOMAIN, URL_BASE
|
||||
from .hacs_frontend import VERSION as FE_VERSION, locate_dir
|
||||
from .hacs_frontend_experimental import (
|
||||
VERSION as EXPERIMENTAL_FE_VERSION,
|
||||
locate_dir as experimental_locate_dir,
|
||||
)
|
||||
from .utils.workarounds import async_register_static_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .base import HacsBase
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
||||
async def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
||||
"""Register the frontend."""
|
||||
|
||||
# Setup themes endpoint if needed
|
||||
hacs.async_setup_frontend_endpoint_themes()
|
||||
|
||||
# Register frontend
|
||||
if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")):
|
||||
hacs.log.warning(
|
||||
"<HacsFrontend> Frontend development mode enabled. Do not run in production!"
|
||||
)
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
|
||||
)
|
||||
elif hacs.configuration.experimental:
|
||||
hacs.log.info("<HacsFrontend> Using experimental frontend")
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/frontend", experimental_locate_dir(), cache_headers=False
|
||||
await async_register_static_path(
|
||||
hass, f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
|
||||
)
|
||||
hacs.frontend_version = "dev"
|
||||
else:
|
||||
#
|
||||
hass.http.register_static_path(f"{URL_BASE}/frontend", locate_dir(), cache_headers=False)
|
||||
await async_register_static_path(
|
||||
hass, f"{URL_BASE}/frontend", locate_dir(), cache_headers=False
|
||||
)
|
||||
hacs.frontend_version = FE_VERSION
|
||||
|
||||
# Custom iconset
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/iconset.js", str(hacs.integration_dir / "iconset.js")
|
||||
)
|
||||
if "frontend_extra_module_url" not in hass.data:
|
||||
hass.data["frontend_extra_module_url"] = set()
|
||||
hass.data["frontend_extra_module_url"].add(f"{URL_BASE}/iconset.js")
|
||||
|
||||
hacs.frontend_version = (
|
||||
FE_VERSION if not hacs.configuration.experimental else EXPERIMENTAL_FE_VERSION
|
||||
await async_register_static_path(
|
||||
hass, f"{URL_BASE}/iconset.js", str(hacs.integration_dir / "iconset.js")
|
||||
)
|
||||
add_extra_js_url(hass, f"{URL_BASE}/iconset.js")
|
||||
|
||||
# Add to sidepanel if needed
|
||||
if DOMAIN not in hass.data.get("frontend_panels", {}):
|
||||
hass.components.frontend.async_register_built_in_panel(
|
||||
async_register_built_in_panel(
|
||||
hass,
|
||||
component_name="custom",
|
||||
sidebar_title=hacs.configuration.sidepanel_title,
|
||||
sidebar_icon=hacs.configuration.sidepanel_icon,
|
||||
|
|
@ -72,4 +64,4 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
|||
)
|
||||
|
||||
# Setup plugin endpoint if needed
|
||||
hacs.async_setup_frontend_endpoint_plugin()
|
||||
await hacs.async_setup_frontend_endpoint_plugin()
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,23 +0,0 @@
|
|||
import{a as t,r as i,n as a}from"./main-ad130be7.js";import{L as n,s}from"./c.82eccc94.js";let r=t([a("ha-list-item")],(function(t,a){return{F:class extends a{constructor(...i){super(...i),t(this)}},d:[{kind:"get",static:!0,key:"styles",value:function(){return[s,i`
|
||||
:host {
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
}
|
||||
:host([graphic="avatar"]:not([twoLine])),
|
||||
:host([graphic="icon"]:not([twoLine])) {
|
||||
height: 48px;
|
||||
}
|
||||
span.material-icons:first-of-type {
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: var(
|
||||
--mdc-list-item-graphic-margin,
|
||||
16px
|
||||
) !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
span.material-icons:last-of-type {
|
||||
margin-inline-start: auto !important;
|
||||
margin-inline-end: 0px !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`]}}]}}),n);const e=t=>`https://brands.home-assistant.io/${t.useFallback?"_/":""}${t.domain}/${t.darkOptimized?"dark_":""}${t.type}.png`,o=t=>t.split("/")[4],p=t=>t.startsWith("https://brands.home-assistant.io/");export{r as H,e as b,o as e,p as i};
|
||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
|||
import{a as e,h as t,Y as i,e as n,i as o,$ as r,L as l,N as a,r as d,n as s}from"./main-ad130be7.js";import"./c.9b92f489.js";e([s("ha-button-menu")],(function(e,t){class s extends t{constructor(...t){super(...t),e(this)}}return{F:s,d:[{kind:"field",key:i,value:void 0},{kind:"field",decorators:[n()],key:"corner",value:()=>"TOP_START"},{kind:"field",decorators:[n()],key:"menuCorner",value:()=>"START"},{kind:"field",decorators:[n({type:Number})],key:"x",value:()=>null},{kind:"field",decorators:[n({type:Number})],key:"y",value:()=>null},{kind:"field",decorators:[n({type:Boolean})],key:"multi",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"activatable",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"disabled",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"fixed",value:()=>!1},{kind:"field",decorators:[o("mwc-menu",!0)],key:"_menu",value:void 0},{kind:"get",key:"items",value:function(){var e;return null===(e=this._menu)||void 0===e?void 0:e.items}},{kind:"get",key:"selected",value:function(){var e;return null===(e=this._menu)||void 0===e?void 0:e.selected}},{kind:"method",key:"focus",value:function(){var e,t;null!==(e=this._menu)&&void 0!==e&&e.open?this._menu.focusItemAtIndex(0):null===(t=this._triggerButton)||void 0===t||t.focus()}},{kind:"method",key:"render",value:function(){return r`
|
||||
<div @click=${this._handleClick}>
|
||||
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
|
||||
</div>
|
||||
<mwc-menu
|
||||
.corner=${this.corner}
|
||||
.menuCorner=${this.menuCorner}
|
||||
.fixed=${this.fixed}
|
||||
.multi=${this.multi}
|
||||
.activatable=${this.activatable}
|
||||
.y=${this.y}
|
||||
.x=${this.x}
|
||||
>
|
||||
<slot></slot>
|
||||
</mwc-menu>
|
||||
`}},{kind:"method",key:"firstUpdated",value:function(e){l(a(s.prototype),"firstUpdated",this).call(this,e),"rtl"===document.dir&&this.updateComplete.then((()=>{this.querySelectorAll("mwc-list-item").forEach((e=>{const t=document.createElement("style");t.innerHTML="span.material-icons:first-of-type { margin-left: var(--mdc-list-item-graphic-margin, 32px) !important; margin-right: 0px !important;}",e.shadowRoot.appendChild(t)}))}))}},{kind:"method",key:"_handleClick",value:function(){this.disabled||(this._menu.anchor=this,this._menu.show())}},{kind:"get",key:"_triggerButton",value:function(){return this.querySelector('ha-icon-button[slot="trigger"], mwc-button[slot="trigger"]')}},{kind:"method",key:"_setTriggerAria",value:function(){this._triggerButton&&(this._triggerButton.ariaHasPopup="menu")}},{kind:"get",static:!0,key:"styles",value:function(){return d`
|
||||
:host {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
::slotted([disabled]) {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
`}}]}}),t);
|
||||
Binary file not shown.
|
|
@ -1,390 +0,0 @@
|
|||
import{a as e,h as t,e as i,g as a,t as s,$ as o,j as r,R as n,w as l,r as h,n as c,m as d,L as p,N as u,o as v,b as f,aI as b,ai as m,c as k,E as g,aJ as y,aC as w,aK as x,aL as $,d as _,s as R}from"./main-ad130be7.js";import{f as z}from"./c.3243a8b0.js";import{c as j}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.2d5ed670.js";import"./c.97b7c4b0.js";import{r as F}from"./c.4204ca09.js";import{i as P}from"./c.21c042d4.js";import{s as I}from"./c.2645c235.js";import"./c.a5f69ed4.js";import"./c.3f859915.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.4feb0cb8.js";import"./c.0ca5587f.js";import"./c.5d3ce9d6.js";import"./c.f6611997.js";import"./c.743a15a1.js";import"./c.4266acdb.js";e([c("ha-tab")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"active",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[i()],key:"name",value:void 0},{kind:"field",decorators:[a("mwc-ripple")],key:"_ripple",value:void 0},{kind:"field",decorators:[s()],key:"_shouldRenderRipple",value:()=>!1},{kind:"method",key:"render",value:function(){return o`
|
||||
<div
|
||||
tabindex="0"
|
||||
role="tab"
|
||||
aria-selected=${this.active}
|
||||
aria-label=${r(this.name)}
|
||||
@focus=${this.handleRippleFocus}
|
||||
@blur=${this.handleRippleBlur}
|
||||
@mousedown=${this.handleRippleActivate}
|
||||
@mouseup=${this.handleRippleDeactivate}
|
||||
@mouseenter=${this.handleRippleMouseEnter}
|
||||
@mouseleave=${this.handleRippleMouseLeave}
|
||||
@touchstart=${this.handleRippleActivate}
|
||||
@touchend=${this.handleRippleDeactivate}
|
||||
@touchcancel=${this.handleRippleDeactivate}
|
||||
@keydown=${this._handleKeyDown}
|
||||
>
|
||||
${this.narrow?o`<slot name="icon"></slot>`:""}
|
||||
<span class="name">${this.name}</span>
|
||||
${this._shouldRenderRipple?o`<mwc-ripple></mwc-ripple>`:""}
|
||||
</div>
|
||||
`}},{kind:"field",key:"_rippleHandlers",value(){return new n((()=>(this._shouldRenderRipple=!0,this._ripple)))}},{kind:"method",key:"_handleKeyDown",value:function(e){13===e.keyCode&&e.target.click()}},{kind:"method",decorators:[l({passive:!0})],key:"handleRippleActivate",value:function(e){this._rippleHandlers.startPress(e)}},{kind:"method",key:"handleRippleDeactivate",value:function(){this._rippleHandlers.endPress()}},{kind:"method",key:"handleRippleMouseEnter",value:function(){this._rippleHandlers.startHover()}},{kind:"method",key:"handleRippleMouseLeave",value:function(){this._rippleHandlers.endHover()}},{kind:"method",key:"handleRippleFocus",value:function(){this._rippleHandlers.startFocus()}},{kind:"method",key:"handleRippleBlur",value:function(){this._rippleHandlers.endFocus()}},{kind:"get",static:!0,key:"styles",value:function(){return h`
|
||||
div {
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: var(--header-height);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:host([active]) {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
:host(:not([narrow])[active]) div {
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host([narrow]) div {
|
||||
padding: 0 4px;
|
||||
}
|
||||
`}}]}}),t),e([c("hass-tabs-subpage")],(function(e,t){class a extends t{constructor(...t){super(...t),e(this)}}return{F:a,d:[{kind:"field",decorators:[i({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[i({type:Boolean})],key:"supervisor",value:()=>!1},{kind:"field",decorators:[i({attribute:!1})],key:"localizeFunc",value:void 0},{kind:"field",decorators:[i({type:String,attribute:"back-path"})],key:"backPath",value:void 0},{kind:"field",decorators:[i()],key:"backCallback",value:void 0},{kind:"field",decorators:[i({type:Boolean,attribute:"main-page"})],key:"mainPage",value:()=>!1},{kind:"field",decorators:[i({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"tabs",value:void 0},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0,attribute:"is-wide"})],key:"isWide",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"rtl",value:()=>!1},{kind:"field",decorators:[s()],key:"_activeTab",value:void 0},{kind:"field",decorators:[F(".content")],key:"_savedScrollPos",value:void 0},{kind:"field",key:"_getTabs",value(){return d(((e,t,i,a,s,r,n)=>{const l=e.filter((e=>(!e.component||e.core||P(this.hass,e.component))&&(!e.advancedOnly||i)));if(l.length<2){if(1===l.length){const e=l[0];return[e.translationKey?n(e.translationKey):e.name]}return[""]}return l.map((e=>o`
|
||||
<a href=${e.path}>
|
||||
<ha-tab
|
||||
.hass=${this.hass}
|
||||
.active=${e.path===(null==t?void 0:t.path)}
|
||||
.narrow=${this.narrow}
|
||||
.name=${e.translationKey?n(e.translationKey):e.name}
|
||||
>
|
||||
${e.iconPath?o`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${e.iconPath}
|
||||
></ha-svg-icon>`:""}
|
||||
</ha-tab>
|
||||
</a>
|
||||
`))}))}},{kind:"method",key:"willUpdate",value:function(e){if(e.has("route")&&(this._activeTab=this.tabs.find((e=>`${this.route.prefix}${this.route.path}`.includes(e.path)))),e.has("hass")){const t=e.get("hass");t&&t.language===this.hass.language||(this.rtl=j(this.hass))}p(u(a.prototype),"willUpdate",this).call(this,e)}},{kind:"method",key:"render",value:function(){var e,t;const i=this._getTabs(this.tabs,this._activeTab,null===(e=this.hass.userData)||void 0===e?void 0:e.showAdvanced,this.hass.config.components,this.hass.language,this.narrow,this.localizeFunc||this.hass.localize),a=i.length>1;return o`
|
||||
<div class="toolbar">
|
||||
${this.mainPage||!this.backPath&&null!==(t=history.state)&&void 0!==t&&t.root?o`
|
||||
<ha-menu-button
|
||||
.hassio=${this.supervisor}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`:this.backPath?o`
|
||||
<a href=${this.backPath}>
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
</a>
|
||||
`:o`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
${this.narrow||!a?o`<div class="main-title">
|
||||
<slot name="header">${a?"":i[0]}</slot>
|
||||
</div>`:""}
|
||||
${a?o`
|
||||
<div id="tabbar" class=${v({"bottom-bar":this.narrow})}>
|
||||
${i}
|
||||
</div>
|
||||
`:""}
|
||||
<div id="toolbar-icon">
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="content ${v({tabs:a})}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="fab" class=${v({tabs:a})}>
|
||||
<slot name="fab"></slot>
|
||||
</div>
|
||||
`}},{kind:"method",decorators:[l({passive:!0})],key:"_saveScrollPos",value:function(e){this._savedScrollPos=e.target.scrollTop}},{kind:"method",key:"_backTapped",value:function(){this.backCallback?this.backCallback():history.back()}},{kind:"get",static:!0,key:"styles",value:function(){return h`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
ha-menu-button {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
background-color: var(--sidebar-background-color);
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.toolbar a {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.bottom-bar a {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
#tabbar {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#tabbar > a {
|
||||
overflow: hidden;
|
||||
max-width: 45%;
|
||||
}
|
||||
|
||||
#tabbar.bottom-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--sidebar-background-color);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
justify-content: space-around;
|
||||
z-index: 2;
|
||||
font-size: 12px;
|
||||
width: 100%;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
#tabbar:not(.bottom-bar) {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) #toolbar-icon {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
.main-title {
|
||||
flex: 1;
|
||||
max-height: var(--header-height);
|
||||
line-height: 20px;
|
||||
color: var(--sidebar-text-color);
|
||||
margin: var(--main-title-margin, 0 0 0 24px);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
width: calc(
|
||||
100% - env(safe-area-inset-left) - env(safe-area-inset-right)
|
||||
);
|
||||
margin-left: env(safe-area-inset-left);
|
||||
margin-right: env(safe-area-inset-right);
|
||||
height: calc(100% - 1px - var(--header-height));
|
||||
height: calc(
|
||||
100% - 1px - var(--header-height) - env(safe-area-inset-bottom)
|
||||
);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:host([narrow]) .content.tabs {
|
||||
height: calc(100% - 2 * var(--header-height));
|
||||
height: calc(
|
||||
100% - 2 * var(--header-height) - env(safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
#fab {
|
||||
position: fixed;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
:host([narrow]) #fab.tabs {
|
||||
bottom: calc(84px + env(safe-area-inset-bottom));
|
||||
}
|
||||
#fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
:host([rtl]) #fab {
|
||||
right: auto;
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
}
|
||||
:host([rtl][is-wide]) #fab {
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
right: auto;
|
||||
}
|
||||
`}}]}}),t);let E=e([c("hacs-store-panel")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i({attribute:!1})],key:"filters",value:()=>({})},{kind:"field",decorators:[i({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[i()],key:"_searchInput",value:()=>""},{kind:"field",decorators:[i({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"narrow",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"isWide",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"sections",value:void 0},{kind:"field",decorators:[i()],key:"section",value:void 0},{kind:"field",key:"_repositoriesInActiveSection",value(){return d(((e,t)=>[(null==e?void 0:e.filter((e=>{var i,a,s;return(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===t)))||void 0===a||null===(s=a.categories)||void 0===s?void 0:s.includes(e.category))&&e.installed})))||[],(null==e?void 0:e.filter((e=>{var i,a,s;return(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===t)))||void 0===a||null===(s=a.categories)||void 0===s?void 0:s.includes(e.category))&&e.new&&!e.installed})))||[]]))}},{kind:"get",key:"allRepositories",value:function(){const[e,t]=this._repositoriesInActiveSection(this.hacs.repositories,this.section);return t.concat(e)}},{kind:"field",key:"_filterRepositories",value:()=>d(z)},{kind:"get",key:"visibleRepositories",value:function(){const e=this.allRepositories.filter((e=>{var t,i;return null===(t=this.filters[this.section])||void 0===t||null===(i=t.find((t=>t.id===e.category)))||void 0===i?void 0:i.checked}));return this._filterRepositories(e,this._searchInput)}},{kind:"method",key:"firstUpdated",value:async function(){this.addEventListener("filter-change",(e=>this._updateFilters(e)))}},{kind:"method",key:"_updateFilters",value:function(e){var t;const i=null===(t=this.filters[this.section])||void 0===t?void 0:t.find((t=>t.id===e.detail.id));this.filters[this.section].find((e=>e.id===i.id)).checked=!i.checked,this.requestUpdate()}},{kind:"method",key:"render",value:function(){var e;if(!this.hacs)return o``;const t=this._repositoriesInActiveSection(this.hacs.repositories,this.section)[1];if(!this.filters[this.section]&&this.hacs.info.categories){var i;const e=null===(i=f(this.hacs.language,this.route))||void 0===i?void 0:i.categories;this.filters[this.section]=[],null==e||e.filter((e=>{var t;return null===(t=this.hacs.info)||void 0===t?void 0:t.categories.includes(e)})).forEach((e=>{this.filters[this.section].push({id:e,value:e,checked:!0})}))}return o`<hass-tabs-subpage
|
||||
back-path="/hacs/entry"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${this.hacs.sections}
|
||||
hasFab
|
||||
>
|
||||
<ha-icon-overflow-menu
|
||||
slot="toolbar-icon"
|
||||
narrow
|
||||
.hass=${this.hass}
|
||||
.items=${[{path:b,label:this.hacs.localize("menu.documentation"),action:()=>m.open("https://hacs.xyz/","_blank","noreferrer=true")},{path:k,label:"GitHub",action:()=>m.open("https://github.com/hacs","_blank","noreferrer=true")},{path:g,label:this.hacs.localize("menu.open_issue"),action:()=>m.open("https://hacs.xyz/docs/issues","_blank","noreferrer=true")},{path:y,label:this.hacs.localize("menu.custom_repositories"),disabled:this.hacs.info.disabled_reason,action:()=>this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"custom-repositories",repositories:this.hacs.repositories},bubbles:!0,composed:!0}))},{path:w,label:this.hacs.localize("menu.about"),action:()=>I(this,this.hacs)}]}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
${this.narrow?o`
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
class="header"
|
||||
slot="header"
|
||||
.label=${this.hacs.localize("search.downloaded")}
|
||||
.filter=${this._searchInput||""}
|
||||
@value-changed=${this._inputValueChanged}
|
||||
></search-input>
|
||||
`:o`<div class="search">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
.label=${0===t.length?this.hacs.localize("search.downloaded"):this.hacs.localize("search.downloaded_new")}
|
||||
.filter=${this._searchInput||""}
|
||||
@value-changed=${this._inputValueChanged}
|
||||
></search-input>
|
||||
</div>`}
|
||||
<div class="content ${this.narrow?"narrow-content":""}">
|
||||
${(null===(e=this.filters[this.section])||void 0===e?void 0:e.length)>1?o`<div class="filters">
|
||||
<hacs-filter
|
||||
.hacs=${this.hacs}
|
||||
.filters="${this.filters[this.section]}"
|
||||
></hacs-filter>
|
||||
</div>`:""}
|
||||
${null!=t&&t.length?o`<ha-alert .rtl=${j(this.hass)}>
|
||||
${this.hacs.localize("store.new_repositories_note")}
|
||||
<mwc-button
|
||||
class="max-content"
|
||||
slot="action"
|
||||
.label=${this.hacs.localize("menu.dismiss")}
|
||||
@click=${this._clearAllNewRepositories}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-alert> `:""}
|
||||
<div class="container ${this.narrow?"narrow":""}">
|
||||
${void 0===this.hacs.repositories?"":0===this.allRepositories.length?this._renderEmpty():0===this.visibleRepositories.length?this._renderNoResultsFound():this._renderRepositories()}
|
||||
</div>
|
||||
</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hacs.localize("store.explore")}
|
||||
.extended=${!this.narrow}
|
||||
@click=${this._addRepository}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${x}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>`}},{kind:"method",key:"_renderRepositories",value:function(){return this.visibleRepositories.map((e=>o`<hacs-repository-card
|
||||
.hass=${this.hass}
|
||||
.hacs=${this.hacs}
|
||||
.repository=${e}
|
||||
.narrow=${this.narrow}
|
||||
?narrow=${this.narrow}
|
||||
></hacs-repository-card>`))}},{kind:"method",key:"_clearAllNewRepositories",value:async function(){var e;await $(this.hass,{categories:(null===(e=f(this.hacs.language,this.route))||void 0===e?void 0:e.categories)||[]})}},{kind:"method",key:"_renderNoResultsFound",value:function(){return o`<ha-alert
|
||||
.rtl=${j(this.hass)}
|
||||
alert-type="warning"
|
||||
.title="${this.hacs.localize("store.no_repositories")} 😕"
|
||||
>
|
||||
${this.hacs.localize("store.no_repositories_found_desc1",{searchInput:this._searchInput})}
|
||||
<br />
|
||||
${this.hacs.localize("store.no_repositories_found_desc2")}
|
||||
</ha-alert>`}},{kind:"method",key:"_renderEmpty",value:function(){return o`<ha-alert
|
||||
.title="${this.hacs.localize("store.no_repositories")} 😕"
|
||||
.rtl=${j(this.hass)}
|
||||
>
|
||||
${this.hacs.localize("store.no_repositories_desc1")}
|
||||
<br />
|
||||
${this.hacs.localize("store.no_repositories_desc2")}
|
||||
</ha-alert>`}},{kind:"method",key:"_inputValueChanged",value:function(e){this._searchInput=e.detail.value,window.localStorage.setItem("hacs-search",this._searchInput)}},{kind:"method",key:"_addRepository",value:function(){this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"add-repository",repositories:this.hacs.repositories,section:this.section},bubbles:!0,composed:!0}))}},{kind:"get",static:!0,key:"styles",value:function(){return[_,R,h`
|
||||
.filter {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.content {
|
||||
height: calc(100vh - 128px);
|
||||
overflow: auto;
|
||||
}
|
||||
.narrow-content {
|
||||
height: calc(100vh - 128px);
|
||||
}
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
|
||||
justify-items: center;
|
||||
grid-gap: 8px 8px;
|
||||
padding: 8px 16px 16px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
hacs-repository-card {
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
hacs-repository-card[narrow] {
|
||||
width: 100%;
|
||||
}
|
||||
hacs-repository-card[narrow]:last-of-type {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
ha-alert {
|
||||
color: var(--hcv-text-color-primary);
|
||||
display: block;
|
||||
margin-top: -4px;
|
||||
}
|
||||
.narrow {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 0px;
|
||||
margin: 0;
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
search-input.header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed !important;
|
||||
}
|
||||
.max-content {
|
||||
width: max-content;
|
||||
}
|
||||
`]}}]}}),t);export{E as HacsStorePanel};
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,16 +0,0 @@
|
|||
import{a as e,e as t,i,L as a,N as d,$ as r,r as n,n as o}from"./main-ad130be7.js";import{H as s}from"./c.0a1cf8d0.js";e([o("ha-clickable-list-item")],(function(e,o){class s extends o{constructor(...t){super(...t),e(this)}}return{F:s,d:[{kind:"field",decorators:[t()],key:"href",value:void 0},{kind:"field",decorators:[t({type:Boolean})],key:"disableHref",value:()=>!1},{kind:"field",decorators:[t({type:Boolean,reflect:!0})],key:"openNewTab",value:()=>!1},{kind:"field",decorators:[i("a")],key:"_anchor",value:void 0},{kind:"method",key:"render",value:function(){const e=a(d(s.prototype),"render",this).call(this),t=this.href||"";return r`${this.disableHref?r`<a aria-role="option">${e}</a>`:r`<a
|
||||
aria-role="option"
|
||||
target=${this.openNewTab?"_blank":""}
|
||||
href=${t}
|
||||
>${e}</a
|
||||
>`}`}},{kind:"method",key:"firstUpdated",value:function(){a(d(s.prototype),"firstUpdated",this).call(this),this.addEventListener("keydown",(e=>{"Enter"!==e.key&&" "!==e.key||this._anchor.click()}))}},{kind:"get",static:!0,key:"styles",value:function(){return[a(d(s),"styles",this),n`
|
||||
a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
`]}}]}}),s);
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
const n=(n,o)=>n&&n.config.components.includes(o);export{n as i};
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
import{al as e,am as a,aj as s,an as r,ao as u}from"./main-ad130be7.js";async function i(i,o,n){const t=new e("updateLovelaceResources"),l=await a(i),d=`/hacsfiles/${o.full_name.split("/")[1]}`,c=s({repository:o,version:n}),p=l.find((e=>e.url.includes(d)));t.debug({namespace:d,url:c,exsisting:p}),p&&p.url!==c?(t.debug(`Updating exsusting resource for ${d}`),await r(i,{url:c,resource_id:p.id,res_type:p.type})):l.map((e=>e.url)).includes(c)||(t.debug(`Adding ${c} to Lovelace resources`),await u(i,{url:c,res_type:"module"}))}export{i as u};
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
import{m as o}from"./c.f6611997.js";import{a as t}from"./c.4266acdb.js";const n=async(n,s)=>t(n,{title:"Home Assistant Community Store",confirmText:s.localize("common.close"),text:o.html(`\n **${s.localize("dialog_about.integration_version")}:** | ${s.info.version}\n --|--\n **${s.localize("dialog_about.frontend_version")}:** | 20220906112053\n **${s.localize("common.repositories")}:** | ${s.repositories.length}\n **${s.localize("dialog_about.downloaded_repositories")}:** | ${s.repositories.filter((o=>o.installed)).length}\n\n **${s.localize("dialog_about.useful_links")}:**\n\n - [General documentation](https://hacs.xyz/)\n - [Configuration](https://hacs.xyz/docs/configuration/start)\n - [FAQ](https://hacs.xyz/docs/faq/what)\n - [GitHub](https://github.com/hacs)\n - [Discord](https://discord.gg/apgchf8)\n - [Become a GitHub sponsor? ❤️](https://github.com/sponsors/ludeeus)\n - [BuyMe~~Coffee~~Beer? 🍺🙈](https://buymeacoffee.com/ludeeus)\n\n ***\n\n _Everything you find in HACS is **not** tested by Home Assistant, that includes HACS itself.\n The HACS and Home Assistant teams do not support **anything** you find here._`)});export{n as s};
|
||||
Binary file not shown.
|
|
@ -1,61 +0,0 @@
|
|||
import{a as r,h as a,e as o,r as e,$ as d,n as t}from"./main-ad130be7.js";r([t("ha-card")],(function(r,a){return{F:class extends a{constructor(...a){super(...a),r(this)}},d:[{kind:"field",decorators:[o()],key:"header",value:void 0},{kind:"field",decorators:[o({type:Boolean,reflect:!0})],key:"outlined",value:()=>!1},{kind:"get",static:!0,key:"styles",value:function(){return e`
|
||||
:host {
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
box-shadow: var(
|
||||
--ha-card-box-shadow,
|
||||
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
transition: all 0.3s ease-out;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([outlined]) {
|
||||
box-shadow: none;
|
||||
border-width: var(--ha-card-border-width, 1px);
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
}
|
||||
|
||||
.card-header,
|
||||
:host ::slotted(.card-header) {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
font-family: var(--ha-card-header-font-family, inherit);
|
||||
font-size: var(--ha-card-header-font-size, 24px);
|
||||
letter-spacing: -0.012em;
|
||||
line-height: 48px;
|
||||
padding: 12px 16px 16px;
|
||||
display: block;
|
||||
margin-block-start: 0px;
|
||||
margin-block-end: 0px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-content:not(:first-child)),
|
||||
slot:not(:first-child)::slotted(.card-content) {
|
||||
padding-top: 0px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-content) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-actions) {
|
||||
border-top: 1px solid var(--divider-color, #e8e8e8);
|
||||
padding: 5px 16px;
|
||||
}
|
||||
`}},{kind:"method",key:"render",value:function(){return d`
|
||||
${this.header?d`<h1 class="card-header">${this.header}</h1>`:d``}
|
||||
<slot></slot>
|
||||
`}}]}}),a);
|
||||
Binary file not shown.
|
|
@ -1,121 +0,0 @@
|
|||
import{a as e,h as t,e as n,t as i,i as o,$ as a,av as d,o as s,L as r,N as l,A as h,ae as c,r as p,n as u}from"./main-ad130be7.js";e([u("ha-expansion-panel")],(function(e,t){class u extends t{constructor(...t){super(...t),e(this)}}return{F:u,d:[{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"expanded",value:()=>!1},{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"outlined",value:()=>!1},{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"leftChevron",value:()=>!1},{kind:"field",decorators:[n()],key:"header",value:void 0},{kind:"field",decorators:[n()],key:"secondary",value:void 0},{kind:"field",decorators:[i()],key:"_showContent",value(){return this.expanded}},{kind:"field",decorators:[o(".container")],key:"_container",value:void 0},{kind:"method",key:"render",value:function(){return a`
|
||||
<div class="top">
|
||||
<div
|
||||
id="summary"
|
||||
@click=${this._toggleContainer}
|
||||
@keydown=${this._toggleContainer}
|
||||
@focus=${this._focusChanged}
|
||||
@blur=${this._focusChanged}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-expanded=${this.expanded}
|
||||
aria-controls="sect1"
|
||||
>
|
||||
${this.leftChevron?a`
|
||||
<ha-svg-icon
|
||||
.path=${d}
|
||||
class="summary-icon ${s({expanded:this.expanded})}"
|
||||
></ha-svg-icon>
|
||||
`:""}
|
||||
<slot name="header">
|
||||
<div class="header">
|
||||
${this.header}
|
||||
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
||||
</div>
|
||||
</slot>
|
||||
${this.leftChevron?"":a`
|
||||
<ha-svg-icon
|
||||
.path=${d}
|
||||
class="summary-icon ${s({expanded:this.expanded})}"
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
</div>
|
||||
<slot name="icons"></slot>
|
||||
</div>
|
||||
<div
|
||||
class="container ${s({expanded:this.expanded})}"
|
||||
@transitionend=${this._handleTransitionEnd}
|
||||
role="region"
|
||||
aria-labelledby="summary"
|
||||
aria-hidden=${!this.expanded}
|
||||
tabindex="-1"
|
||||
>
|
||||
${this._showContent?a`<slot></slot>`:""}
|
||||
</div>
|
||||
`}},{kind:"method",key:"willUpdate",value:function(e){r(l(u.prototype),"willUpdate",this).call(this,e),e.has("expanded")&&this.expanded&&(this._showContent=this.expanded,setTimeout((()=>{this.expanded&&(this._container.style.overflow="initial")}),300))}},{kind:"method",key:"_handleTransitionEnd",value:function(){this._container.style.removeProperty("height"),this._container.style.overflow=this.expanded?"initial":"hidden",this._showContent=this.expanded}},{kind:"method",key:"_toggleContainer",value:async function(e){if(e.defaultPrevented)return;if("keydown"===e.type&&"Enter"!==e.key&&" "!==e.key)return;e.preventDefault();const t=!this.expanded;h(this,"expanded-will-change",{expanded:t}),this._container.style.overflow="hidden",t&&(this._showContent=!0,await c());const n=this._container.scrollHeight;this._container.style.height=`${n}px`,t||setTimeout((()=>{this._container.style.height="0px"}),0),this.expanded=t,h(this,"expanded-changed",{expanded:this.expanded})}},{kind:"method",key:"_focusChanged",value:function(e){this.shadowRoot.querySelector(".top").classList.toggle("focused","focus"===e.type)}},{kind:"get",static:!0,key:"styles",value:function(){return p`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top.focused {
|
||||
background: var(--input-fill-color);
|
||||
}
|
||||
|
||||
:host([outlined]) {
|
||||
box-shadow: none;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
:host([leftchevron]) .summary-icon {
|
||||
margin-left: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#summary {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: var(--expansion-panel-summary-padding, 0 8px);
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.summary-icon.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.header,
|
||||
::slotted([slot="header"]) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--expansion-panel-content-padding, 0 8px);
|
||||
overflow: hidden;
|
||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.container.expanded {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
display: block;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
`}}]}}),t);
|
||||
Binary file not shown.
|
|
@ -1,50 +0,0 @@
|
|||
import{a as e,h as i,e as t,i as a,$ as n,O as l,z as o,A as s,r as c,n as r,m as d}from"./main-ad130be7.js";import"./c.3f859915.js";e([r("search-input")],(function(e,i){return{F:class extends i{constructor(...i){super(...i),e(this)}},d:[{kind:"field",decorators:[t({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[t()],key:"filter",value:void 0},{kind:"field",decorators:[t({type:Boolean})],key:"suffix",value:()=>!1},{kind:"field",decorators:[t({type:Boolean})],key:"autofocus",value:()=>!1},{kind:"field",decorators:[t({type:String})],key:"label",value:void 0},{kind:"method",key:"focus",value:function(){var e;null===(e=this._input)||void 0===e||e.focus()}},{kind:"field",decorators:[a("ha-textfield",!0)],key:"_input",value:void 0},{kind:"method",key:"render",value:function(){return n`
|
||||
<ha-textfield
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label||"Search"}
|
||||
.value=${this.filter||""}
|
||||
icon
|
||||
.iconTrailing=${this.filter||this.suffix}
|
||||
@input=${this._filterInputChanged}
|
||||
>
|
||||
<slot name="prefix" slot="leadingIcon">
|
||||
<ha-svg-icon
|
||||
tabindex="-1"
|
||||
class="prefix"
|
||||
.path=${l}
|
||||
></ha-svg-icon>
|
||||
</slot>
|
||||
<div class="trailing" slot="trailingIcon">
|
||||
${this.filter&&n`
|
||||
<ha-icon-button
|
||||
@click=${this._clearSearch}
|
||||
.label=${this.hass.localize("ui.common.clear")}
|
||||
.path=${o}
|
||||
class="clear-button"
|
||||
></ha-icon-button>
|
||||
`}
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
</ha-textfield>
|
||||
`}},{kind:"method",key:"_filterChanged",value:async function(e){s(this,"value-changed",{value:String(e)})}},{kind:"method",key:"_filterInputChanged",value:async function(e){this._filterChanged(e.target.value)}},{kind:"method",key:"_clearSearch",value:async function(){this._filterChanged("")}},{kind:"get",static:!0,key:"styles",value:function(){return c`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
}
|
||||
ha-svg-icon,
|
||||
ha-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-svg-icon {
|
||||
outline: none;
|
||||
}
|
||||
.clear-button {
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: inherit;
|
||||
}
|
||||
.trailing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`}}]}}),i);const u=d(((e,i)=>e.filter((e=>h(e.name).includes(h(i))||h(e.description).includes(h(i))||h(e.category).includes(h(i))||h(e.full_name).includes(h(i))||h(e.authors).includes(h(i))||h(e.domain).includes(h(i)))))),h=d((e=>String(e||"").toLocaleLowerCase().replace(/-|_| /g,"")));export{u as f};
|
||||
Binary file not shown.
|
|
@ -1,94 +0,0 @@
|
|||
import{a6 as e,a7 as t,a as o,h as i,e as n,$ as a,r,n as l}from"./main-ad130be7.js";e({_template:t`
|
||||
<style>
|
||||
:host {
|
||||
overflow: hidden; /* needed for text-overflow: ellipsis to work on ff */
|
||||
@apply --layout-vertical;
|
||||
@apply --layout-center-justified;
|
||||
@apply --layout-flex;
|
||||
}
|
||||
|
||||
:host([two-line]) {
|
||||
min-height: var(--paper-item-body-two-line-min-height, 72px);
|
||||
}
|
||||
|
||||
:host([three-line]) {
|
||||
min-height: var(--paper-item-body-three-line-min-height, 88px);
|
||||
}
|
||||
|
||||
:host > ::slotted(*) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:host > ::slotted([secondary]) {
|
||||
@apply --paper-font-body1;
|
||||
|
||||
color: var(--paper-item-body-secondary-color, var(--secondary-text-color));
|
||||
|
||||
@apply --paper-item-body-secondary;
|
||||
}
|
||||
</style>
|
||||
|
||||
<slot></slot>
|
||||
`,is:"paper-item-body"}),o([l("ha-settings-row")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"narrow",value:void 0},{kind:"field",decorators:[n({type:Boolean,attribute:"three-line"})],key:"threeLine",value:()=>!1},{kind:"method",key:"render",value:function(){return a`
|
||||
<div class="prefix-wrap">
|
||||
<slot name="prefix"></slot>
|
||||
<paper-item-body
|
||||
?two-line=${!this.threeLine}
|
||||
?three-line=${this.threeLine}
|
||||
>
|
||||
<slot name="heading"></slot>
|
||||
<div secondary><slot name="description"></slot></div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
<div class="content"><slot></slot></div>
|
||||
`}},{kind:"get",static:!0,key:"styles",value:function(){return r`
|
||||
:host {
|
||||
display: flex;
|
||||
padding: 0 16px;
|
||||
align-content: normal;
|
||||
align-self: auto;
|
||||
align-items: center;
|
||||
}
|
||||
paper-item-body {
|
||||
padding: 8px 16px 8px 0;
|
||||
}
|
||||
paper-item-body[two-line] {
|
||||
min-height: calc(
|
||||
var(--paper-item-body-two-line-min-height, 72px) - 16px
|
||||
);
|
||||
flex: 1;
|
||||
}
|
||||
.content {
|
||||
display: contents;
|
||||
}
|
||||
:host(:not([narrow])) .content {
|
||||
display: var(--settings-row-content-display, flex);
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.content ::slotted(*) {
|
||||
width: var(--settings-row-content-width);
|
||||
}
|
||||
:host([narrow]) {
|
||||
align-items: normal;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
::slotted(ha-switch) {
|
||||
padding: 16px 0;
|
||||
}
|
||||
div[secondary] {
|
||||
white-space: normal;
|
||||
}
|
||||
.prefix-wrap {
|
||||
display: var(--settings-row-prefix-display);
|
||||
}
|
||||
:host([narrow]) .prefix-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`}}]}}),i);
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,190 +0,0 @@
|
|||
import{a as e,h as t,e as o,$ as r,aM as i,r as a,n as s,o as n,aL as d,d as c}from"./main-ad130be7.js";import"./c.2d5ed670.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.4feb0cb8.js";import"./c.0ca5587f.js";import"./c.5d3ce9d6.js";e([s("ha-icon-overflow-menu")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[o({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[o({type:Array})],key:"items",value:()=>[]},{kind:"field",decorators:[o({type:Boolean})],key:"narrow",value:()=>!1},{kind:"method",key:"render",value:function(){return r`
|
||||
${this.narrow?r` <!-- Collapsed representation for small screens -->
|
||||
<ha-button-menu
|
||||
@click=${this._handleIconOverflowMenuOpened}
|
||||
@closed=${this._handleIconOverflowMenuClosed}
|
||||
class="ha-icon-overflow-menu-overflow"
|
||||
corner="BOTTOM_START"
|
||||
absolute
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
.path=${i}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
|
||||
${this.items.map((e=>r`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
.disabled=${e.disabled}
|
||||
@click=${e.action}
|
||||
>
|
||||
<div slot="graphic">
|
||||
<ha-svg-icon .path=${e.path}></ha-svg-icon>
|
||||
</div>
|
||||
${e.label}
|
||||
</mwc-list-item>
|
||||
`))}
|
||||
</ha-button-menu>`:r`
|
||||
<!-- Icon representation for big screens -->
|
||||
${this.items.map((e=>e.narrowOnly?"":r`<div>
|
||||
${e.tooltip?r`<paper-tooltip animation-delay="0" position="left">
|
||||
${e.tooltip}
|
||||
</paper-tooltip>`:""}
|
||||
<ha-icon-button
|
||||
@click=${e.action}
|
||||
.label=${e.label}
|
||||
.path=${e.path}
|
||||
.disabled=${e.disabled}
|
||||
></ha-icon-button>
|
||||
</div> `))}
|
||||
`}
|
||||
`}},{kind:"method",key:"_handleIconOverflowMenuOpened",value:function(){const e=this.closest(".mdc-data-table__row");e&&(e.style.zIndex="1")}},{kind:"method",key:"_handleIconOverflowMenuClosed",value:function(){const e=this.closest(".mdc-data-table__row");e&&(e.style.zIndex="")}},{kind:"get",static:!0,key:"styles",value:function(){return a`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`}}]}}),t);const l=e=>t=>({kind:"method",placement:"prototype",key:t.key,descriptor:{set(e){this[`__${String(t.key)}`]=e},get(){return this[`__${String(t.key)}`]},enumerable:!0,configurable:!0},finisher(o){const r=o.prototype.connectedCallback;o.prototype.connectedCallback=function(){if(r.call(this),this[t.key]){const o=this.renderRoot.querySelector(e);if(!o)return;o.scrollTop=this[t.key]}}}});e([s("hacs-repository-card")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[o({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[o({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[o({attribute:!1})],key:"repository",value:void 0},{kind:"field",decorators:[o({type:Boolean})],key:"narrow",value:void 0},{kind:"get",key:"_borderClass",value:function(){const e={};return this.hacs.addedToLovelace(this.hacs,this.repository)&&"pending-restart"!==this.repository.status?this.repository.pending_upgrade?e["status-update"]=!0:this.repository.new&&!this.repository.installed&&(e["status-new"]=!0):e["status-issue"]=!0,0!==Object.keys(e).length&&(e["status-border"]=!0),e}},{kind:"get",key:"_headerClass",value:function(){const e={};return this.hacs.addedToLovelace(this.hacs,this.repository)&&"pending-restart"!==this.repository.status?this.repository.pending_upgrade?e["update-header"]=!0:this.repository.new&&!this.repository.installed?e["new-header"]=!0:e["default-header"]=!0:e["issue-header"]=!0,e}},{kind:"get",key:"_headerTitle",value:function(){return this.hacs.addedToLovelace(this.hacs,this.repository)?"pending-restart"===this.repository.status?this.hacs.localize("repository_card.pending_restart"):this.repository.pending_upgrade?this.hacs.localize("repository_card.pending_update"):this.repository.new&&!this.repository.installed?this.hacs.localize("repository_card.new_repository"):"":this.hacs.localize("repository_card.not_loaded")}},{kind:"method",key:"render",value:function(){return r`
|
||||
<a href="/hacs/repository/${this.repository.id}">
|
||||
<ha-card class=${n(this._borderClass)} ?narrow=${this.narrow} outlined>
|
||||
<div class="card-content">
|
||||
<div class="group-header">
|
||||
<div class="status-header ${n(this._headerClass)}">${this._headerTitle}</div>
|
||||
|
||||
<div class="title pointer">
|
||||
<h1>${this.repository.name}</h1>
|
||||
${"integration"!==this.repository.category?r` <ha-chip>
|
||||
${this.hacs.localize(`common.${this.repository.category}`)}
|
||||
</ha-chip>`:""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">${this.repository.description}</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this.repository.new&&!this.repository.installed?r`<div>
|
||||
<mwc-button class="status-new" @click=${this._setNotNew}>
|
||||
${this.hacs.localize("repository_card.dismiss")}
|
||||
</mwc-button>
|
||||
</div>`:this.repository.pending_upgrade&&this.hacs.addedToLovelace(this.hacs,this.repository)?r`<div>
|
||||
<mwc-button class="update-header" @click=${this._updateRepository} raised>
|
||||
${this.hacs.localize("common.update")}
|
||||
</mwc-button>
|
||||
</div> `:""}
|
||||
</div>
|
||||
</ha-card>
|
||||
</a>
|
||||
`}},{kind:"method",key:"_updateRepository",value:function(e){e.preventDefault(),this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"update",repository:this.repository.id},bubbles:!0,composed:!0}))}},{kind:"method",key:"_setNotNew",value:async function(e){e.preventDefault(),await d(this.hass,{repository:String(this.repository.id)})}},{kind:"get",static:!0,key:"styles",value:function(){return[c,a`
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 195px;
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card-content {
|
||||
padding: 0 0 3px 0;
|
||||
height: 100%;
|
||||
}
|
||||
.card-actions {
|
||||
border-top: none;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
}
|
||||
.group-header {
|
||||
height: auto;
|
||||
align-content: center;
|
||||
}
|
||||
.group-header h1 {
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
font-size: 22px;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
min-height: 24px;
|
||||
}
|
||||
a {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.description {
|
||||
opacity: var(--dark-primary-opacity);
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
max-height: 52px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status-new {
|
||||
border-color: var(--hcv-color-new);
|
||||
--mdc-theme-primary: var(--hcv-color-new);
|
||||
}
|
||||
|
||||
.status-update {
|
||||
border-color: var(--hcv-color-update);
|
||||
}
|
||||
|
||||
.status-issue {
|
||||
border-color: var(--hcv-color-error);
|
||||
}
|
||||
|
||||
.new-header {
|
||||
background-color: var(--hcv-color-new);
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.issue-header {
|
||||
background-color: var(--hcv-color-error);
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.update-header {
|
||||
background-color: var(--hcv-color-update);
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.default-header {
|
||||
padding: 2px 0 !important;
|
||||
}
|
||||
|
||||
mwc-button.update-header {
|
||||
--mdc-theme-primary: var(--hcv-color-update);
|
||||
--mdc-theme-on-primary: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.status-border {
|
||||
border-style: solid;
|
||||
border-width: min(var(--ha-card-border-width, 1px), 10px);
|
||||
}
|
||||
|
||||
.status-header {
|
||||
top: 0;
|
||||
padding: 6px 1px;
|
||||
margin: -1px;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
border-top-left-radius: var(--ha-card-border-radius, 4px);
|
||||
border-top-right-radius: var(--ha-card-border-radius, 4px);
|
||||
}
|
||||
|
||||
ha-card[narrow] {
|
||||
width: calc(100% - 24px);
|
||||
margin: 11px;
|
||||
}
|
||||
|
||||
ha-chip {
|
||||
padding: 4px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
`]}}]}}),t);export{l as r};
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
import{A as o}from"./main-ad130be7.js";const a=()=>import("./c.f12697b4.js"),i=(i,l,m)=>new Promise((n=>{const r=l.cancel,s=l.confirm;o(i,"show-dialog",{dialogTag:"dialog-box",dialogImport:a,dialogParams:{...l,...m,cancel:()=>{n(!(null==m||!m.prompt)&&null),r&&r()},confirm:o=>{n(null==m||!m.prompt||o),s&&s(o)}}})})),l=(o,a)=>i(o,a),m=(o,a)=>i(o,a,{confirmation:!0}),n=(o,a)=>i(o,a,{prompt:!0});export{l as a,n as b,m as s};
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
function t(t){const a=t.language||"en";return t.translationMetadata.translations[a]&&t.translationMetadata.translations[a].isRTL||!1}function a(a){return t(a)?"rtl":"ltr"}export{a,t as c};
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,59 +0,0 @@
|
|||
import{a as o,H as t,e as s,t as e,m as i,a0 as a,a1 as r,$ as l,aj as n,ak as h,a3 as c,ai as d,d as p,r as _,n as m}from"./main-ad130be7.js";import{c as y}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.d262aab0.js";import{s as v}from"./c.4266acdb.js";import{f as g,r as u,a as f}from"./c.fe747ba2.js";import{u as w}from"./c.25ed1ae4.js";import"./c.5d3ce9d6.js";import"./c.82e03b89.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.3f859915.js";import"./c.0ca5587f.js";import"./c.42d6aebd.js";import"./c.710a50fc.js";let b=o([m("hacs-download-dialog")],(function(o,t){return{F:class extends t{constructor(...t){super(...t),o(this)}},d:[{kind:"field",decorators:[s()],key:"repository",value:void 0},{kind:"field",decorators:[e()],key:"_toggle",value:()=>!0},{kind:"field",decorators:[e()],key:"_installing",value:()=>!1},{kind:"field",decorators:[e()],key:"_error",value:void 0},{kind:"field",decorators:[e()],key:"_repository",value:void 0},{kind:"field",decorators:[e()],key:"_downloadRepositoryData",value:()=>({beta:!1,version:""})},{kind:"method",key:"shouldUpdate",value:function(o){return o.forEach(((o,t)=>{"hass"===t&&(this.sidebarDocked='"docked"'===window.localStorage.getItem("dockedSidebar")),"repositories"===t&&this._fetchRepository()})),o.has("sidebarDocked")||o.has("narrow")||o.has("active")||o.has("_toggle")||o.has("_error")||o.has("_repository")||o.has("_downloadRepositoryData")||o.has("_installing")}},{kind:"field",key:"_getInstallPath",value:()=>i((o=>{let t=o.local_path;return"theme"===o.category&&(t=`${t}/${o.file_name}`),t}))},{kind:"method",key:"firstUpdated",value:async function(){var o;await this._fetchRepository(),this._toggle=!1,a(this.hass,(o=>this._error=o),r.ERROR),this._downloadRepositoryData.beta=this._repository.beta,this._downloadRepositoryData.version="version"===(null===(o=this._repository)||void 0===o?void 0:o.version_or_commit)?this._repository.releases[0]:""}},{kind:"method",key:"_fetchRepository",value:async function(){this._repository=await g(this.hass,this.repository)}},{kind:"method",key:"render",value:function(){var o;if(!this.active||!this._repository)return l``;const t=this._getInstallPath(this._repository),s=[{name:"beta",selector:{boolean:{}}},{name:"version",selector:{select:{options:"version"===this._repository.version_or_commit?this._repository.releases.concat("hacs/integration"===this._repository.full_name||this._repository.hide_default_branch?[]:[this._repository.default_branch]):[],mode:"dropdown"}}}];return l`
|
||||
<hacs-dialog
|
||||
.active=${this.active}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.secondary=${this.secondary}
|
||||
.title=${this._repository.name}
|
||||
>
|
||||
<div class="content">
|
||||
${"version"===this._repository.version_or_commit?l`
|
||||
<ha-form
|
||||
.disabled=${this._toggle}
|
||||
?narrow=${this.narrow}
|
||||
.data=${this._downloadRepositoryData}
|
||||
.schema=${s}
|
||||
.computeLabel=${o=>"beta"===o.name?this.hacs.localize("dialog_download.show_beta"):this.hacs.localize("dialog_download.select_version")}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-form>
|
||||
`:""}
|
||||
${this._repository.can_download?"":l`<ha-alert alert-type="error" .rtl=${y(this.hass)}>
|
||||
${this.hacs.localize("confirm.home_assistant_version_not_correct",{haversion:this.hass.config.version,minversion:this._repository.homeassistant})}
|
||||
</ha-alert>`}
|
||||
<div class="note">
|
||||
${this.hacs.localize("dialog_download.note_downloaded",{location:l`<code>'${t}'</code>`})}
|
||||
${"plugin"===this._repository.category&&"storage"!==this.hacs.info.lovelace_mode?l`
|
||||
<p>${this.hacs.localize("dialog_download.lovelace_instruction")}</p>
|
||||
<pre>
|
||||
url: ${n({repository:this._repository,skipTag:!0})}
|
||||
type: module
|
||||
</pre
|
||||
>
|
||||
`:""}
|
||||
${"integration"===this._repository.category?l`<p>${this.hacs.localize("dialog_download.restart")}</p>`:""}
|
||||
</div>
|
||||
${null!==(o=this._error)&&void 0!==o&&o.message?l`<ha-alert alert-type="error" .rtl=${y(this.hass)}>
|
||||
${this._error.message}
|
||||
</ha-alert>`:""}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryaction"
|
||||
?disabled=${!(this._repository.can_download&&!this._toggle&&"version"!==this._repository.version_or_commit)&&!this._downloadRepositoryData.version}
|
||||
@click=${this._installRepository}
|
||||
>
|
||||
${this._installing?l`<ha-circular-progress active size="small"></ha-circular-progress>`:this.hacs.localize("common.download")}
|
||||
</mwc-button>
|
||||
</hacs-dialog>
|
||||
`}},{kind:"method",key:"_valueChanged",value:async function(o){let t=!1;if(this._downloadRepositoryData.beta!==o.detail.value.beta&&(t=!0,this._toggle=!0,await h(this.hass,this.repository,o.detail.value.beta)),o.detail.value.version&&(t=!0,this._toggle=!0,await u(this.hass,this.repository,o.detail.value.version)),t){const o=await c(this.hass);await this._fetchRepository(),this.dispatchEvent(new CustomEvent("update-hacs",{detail:{repositories:o},bubbles:!0,composed:!0})),this._toggle=!1}this._downloadRepositoryData=o.detail.value}},{kind:"method",key:"_installRepository",value:async function(){var o;if(this._installing=!0,!this._repository)return;const t=this._downloadRepositoryData.version||this._repository.available_version||this._repository.default_branch;"commit"!==(null===(o=this._repository)||void 0===o?void 0:o.version_or_commit)?await f(this.hass,String(this._repository.id),t):await f(this.hass,String(this._repository.id)),this.hacs.log.debug(this._repository.category,"_installRepository"),this.hacs.log.debug(this.hacs.info.lovelace_mode,"_installRepository"),"plugin"===this._repository.category&&"storage"===this.hacs.info.lovelace_mode&&await w(this.hass,this._repository,t),this._installing=!1,this.dispatchEvent(new Event("hacs-secondary-dialog-closed",{bubbles:!0,composed:!0})),this.dispatchEvent(new Event("hacs-dialog-closed",{bubbles:!0,composed:!0})),"plugin"===this._repository.category&&v(this,{title:this.hacs.localize("common.reload"),text:l`${this.hacs.localize("dialog.reload.description")}<br />${this.hacs.localize("dialog.reload.confirm")}`,dismissText:this.hacs.localize("common.cancel"),confirmText:this.hacs.localize("common.reload"),confirm:()=>{d.location.href=d.location.href}})}},{kind:"get",static:!0,key:"styles",value:function(){return[p,_`
|
||||
.note {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.lovelace {
|
||||
margin-top: 8px;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-line;
|
||||
user-select: all;
|
||||
}
|
||||
`]}}]}}),t);export{b as HacsDonwloadDialog};
|
||||
Binary file not shown.
|
|
@ -1,176 +0,0 @@
|
|||
import{a6 as t,a7 as i,a8 as a}from"./main-ad130be7.js";t({_template:i`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
position: absolute;
|
||||
outline: none;
|
||||
z-index: 1002;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#tooltip {
|
||||
display: block;
|
||||
outline: none;
|
||||
@apply --paper-font-common-base;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
background-color: var(--paper-tooltip-background, #616161);
|
||||
color: var(--paper-tooltip-text-color, white);
|
||||
padding: 8px;
|
||||
border-radius: 2px;
|
||||
@apply --paper-tooltip;
|
||||
}
|
||||
|
||||
@keyframes keyFrameScaleUp {
|
||||
0% {
|
||||
transform: scale(0.0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameScaleDown {
|
||||
0% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameFadeInOpacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameFadeOutOpacity {
|
||||
0% {
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameSlideDownIn {
|
||||
0% {
|
||||
transform: translateY(-2000px);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameSlideDownOut {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
10% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-2000px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-animation {
|
||||
opacity: 0;
|
||||
animation-delay: var(--paper-tooltip-delay-in, 500ms);
|
||||
animation-name: keyFrameFadeInOpacity;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-in, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.fade-out-animation {
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-out, 0ms);
|
||||
animation-name: keyFrameFadeOutOpacity;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.scale-up-animation {
|
||||
transform: scale(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-in, 500ms);
|
||||
animation-name: keyFrameScaleUp;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-in, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.scale-down-animation {
|
||||
transform: scale(1);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-out, 500ms);
|
||||
animation-name: keyFrameScaleDown;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.slide-down-animation {
|
||||
transform: translateY(-2000px);
|
||||
opacity: 0;
|
||||
animation-delay: var(--paper-tooltip-delay-out, 500ms);
|
||||
animation-name: keyFrameSlideDownIn;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.slide-down-animation-out {
|
||||
transform: translateY(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-out, 500ms);
|
||||
animation-name: keyFrameSlideDownOut;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: cubic-bezier(0.4, 0.0, 1, 1);
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.cancel-animation {
|
||||
animation-delay: -30s !important;
|
||||
}
|
||||
|
||||
/* Thanks IE 10. */
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="tooltip" class="hidden">
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,is:"paper-tooltip",hostAttributes:{role:"tooltip",tabindex:-1},properties:{for:{type:String,observer:"_findTarget"},manualMode:{type:Boolean,value:!1,observer:"_manualModeChanged"},position:{type:String,value:"bottom"},fitToVisibleBounds:{type:Boolean,value:!1},offset:{type:Number,value:14},marginTop:{type:Number,value:14},animationDelay:{type:Number,value:500,observer:"_delayChange"},animationEntry:{type:String,value:""},animationExit:{type:String,value:""},animationConfig:{type:Object,value:function(){return{entry:[{name:"fade-in-animation",node:this,timing:{delay:0}}],exit:[{name:"fade-out-animation",node:this}]}}},_showing:{type:Boolean,value:!1}},listeners:{webkitAnimationEnd:"_onAnimationEnd"},get target(){var t=a(this).parentNode,i=a(this).getOwnerRoot();return this.for?a(i).querySelector("#"+this.for):t.nodeType==Node.DOCUMENT_FRAGMENT_NODE?i.host:t},attached:function(){this._findTarget()},detached:function(){this.manualMode||this._removeListeners()},playAnimation:function(t){"entry"===t?this.show():"exit"===t&&this.hide()},cancelAnimation:function(){this.$.tooltip.classList.add("cancel-animation")},show:function(){if(!this._showing){if(""===a(this).textContent.trim()){for(var t=!0,i=a(this).getEffectiveChildNodes(),n=0;n<i.length;n++)if(""!==i[n].textContent.trim()){t=!1;break}if(t)return}this._showing=!0,this.$.tooltip.classList.remove("hidden"),this.$.tooltip.classList.remove("cancel-animation"),this.$.tooltip.classList.remove(this._getAnimationType("exit")),this.updatePosition(),this._animationPlaying=!0,this.$.tooltip.classList.add(this._getAnimationType("entry"))}},hide:function(){if(this._showing){if(this._animationPlaying)return this._showing=!1,void this._cancelAnimation();this._onAnimationFinish(),this._showing=!1,this._animationPlaying=!0}},updatePosition:function(){if(this._target&&this.offsetParent){var t=this.offset;14!=this.marginTop&&14==this.offset&&(t=this.marginTop);var i,a,n=this.offsetParent.getBoundingClientRect(),e=this._target.getBoundingClientRect(),o=this.getBoundingClientRect(),s=(e.width-o.width)/2,r=(e.height-o.height)/2,l=e.left-n.left,p=e.top-n.top;switch(this.position){case"top":i=l+s,a=p-o.height-t;break;case"bottom":i=l+s,a=p+e.height+t;break;case"left":i=l-o.width-t,a=p+r;break;case"right":i=l+e.width+t,a=p+r}this.fitToVisibleBounds?(n.left+i+o.width>window.innerWidth?(this.style.right="0px",this.style.left="auto"):(this.style.left=Math.max(0,i)+"px",this.style.right="auto"),n.top+a+o.height>window.innerHeight?(this.style.bottom=n.height-p+t+"px",this.style.top="auto"):(this.style.top=Math.max(-n.top,a)+"px",this.style.bottom="auto")):(this.style.left=i+"px",this.style.top=a+"px")}},_addListeners:function(){this._target&&(this.listen(this._target,"mouseenter","show"),this.listen(this._target,"focus","show"),this.listen(this._target,"mouseleave","hide"),this.listen(this._target,"blur","hide"),this.listen(this._target,"tap","hide")),this.listen(this.$.tooltip,"animationend","_onAnimationEnd"),this.listen(this,"mouseenter","hide")},_findTarget:function(){this.manualMode||this._removeListeners(),this._target=this.target,this.manualMode||this._addListeners()},_delayChange:function(t){500!==t&&this.updateStyles({"--paper-tooltip-delay-in":t+"ms"})},_manualModeChanged:function(){this.manualMode?this._removeListeners():this._addListeners()},_cancelAnimation:function(){this.$.tooltip.classList.remove(this._getAnimationType("entry")),this.$.tooltip.classList.remove(this._getAnimationType("exit")),this.$.tooltip.classList.remove("cancel-animation"),this.$.tooltip.classList.add("hidden")},_onAnimationFinish:function(){this._showing&&(this.$.tooltip.classList.remove(this._getAnimationType("entry")),this.$.tooltip.classList.remove("cancel-animation"),this.$.tooltip.classList.add(this._getAnimationType("exit")))},_onAnimationEnd:function(){this._animationPlaying=!1,this._showing||(this.$.tooltip.classList.remove(this._getAnimationType("exit")),this.$.tooltip.classList.add("hidden"))},_getAnimationType:function(t){if("entry"===t&&""!==this.animationEntry)return this.animationEntry;if("exit"===t&&""!==this.animationExit)return this.animationExit;if(this.animationConfig[t]&&"string"==typeof this.animationConfig[t][0].name){if(this.animationConfig[t][0].timing&&this.animationConfig[t][0].timing.delay&&0!==this.animationConfig[t][0].timing.delay){var i=this.animationConfig[t][0].timing.delay;"entry"===t?this.updateStyles({"--paper-tooltip-delay-in":i+"ms"}):"exit"===t&&this.updateStyles({"--paper-tooltip-delay-out":i+"ms"})}return this.animationConfig[t][0].name}},_removeListeners:function(){this._target&&(this.unlisten(this._target,"mouseenter","show"),this.unlisten(this._target,"focus","show"),this.unlisten(this._target,"mouseleave","hide"),this.unlisten(this._target,"blur","hide"),this.unlisten(this._target,"tap","hide")),this.unlisten(this.$.tooltip,"animationend","_onAnimationEnd"),this.unlisten(this,"mouseenter","hide")}});
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
const e=()=>{const e={},r=new URLSearchParams(location.search);for(const[n,t]of r.entries())e[n]=t;return e},r=e=>{const r=new URLSearchParams;return Object.entries(e).forEach((([e,n])=>{r.append(e,n)})),r.toString()};export{r as c,e};
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,7 +0,0 @@
|
|||
import{a as t,h as e,e as r,$ as n,ah as i,ai as a,r as o,n as s}from"./main-ad130be7.js";t([s("hacs-link")],(function(t,e){return{F:class extends e{constructor(...e){super(...e),t(this)}},d:[{kind:"field",decorators:[r({type:Boolean})],key:"newtab",value:()=>!1},{kind:"field",decorators:[r({type:Boolean})],key:"parent",value:()=>!1},{kind:"field",decorators:[r()],key:"title",value:()=>""},{kind:"field",decorators:[r()],key:"url",value:void 0},{kind:"method",key:"render",value:function(){return n`<span title=${this.title||this.url} @click=${this._open}><slot></slot></span>`}},{kind:"method",key:"_open",value:function(){var t;if(this.url.startsWith("/")&&!this.newtab)return void i(this.url,{replace:!0});const e=null===(t=this.url)||void 0===t?void 0:t.startsWith("http");let r="",n="_blank";e&&(r="noreferrer=true"),e||this.newtab||(n="_blank"),e||this.parent||(n="_parent"),a.open(this.url,n,r)}},{kind:"get",static:!0,key:"styles",value:function(){return o`
|
||||
span {
|
||||
cursor: pointer;
|
||||
color: var(--hcv-text-color-link);
|
||||
text-decoration: var(--hcv-text-decoration-link);
|
||||
}
|
||||
`}}]}}),e);
|
||||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
var a=[];export{a as default};
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function o(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function t(e,o){return e(o={exports:{}},o.exports),o.exports}function n(e){return e&&e.default||e}export{e as a,t as c,n as g,o as u};
|
||||
Binary file not shown.
|
|
@ -1,121 +0,0 @@
|
|||
import{_ as e,n as t,a as i,H as a,e as s,b as r,m as o,$ as l,o as c,c as d,s as n,d as h,r as u}from"./main-ad130be7.js";import"./c.82eccc94.js";import{s as p,S as f,a as m}from"./c.42d6aebd.js";import"./c.f1291e50.js";import"./c.9b92f489.js";import"./c.11ad1623.js";import{f as v}from"./c.3243a8b0.js";import{b as g}from"./c.0a1cf8d0.js";import"./c.a5f69ed4.js";import"./c.82e03b89.js";import"./c.8e28b461.js";import"./c.3f859915.js";import"./c.710a50fc.js";let y=class extends f{};y.styles=[p],y=e([t("mwc-select")],y);const _=["stars","last_updated","name"];let k=i([t("hacs-add-repository-dialog")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[s({attribute:!1})],key:"filters",value:()=>[]},{kind:"field",decorators:[s({type:Number})],key:"_load",value:()=>30},{kind:"field",decorators:[s({type:Number})],key:"_top",value:()=>0},{kind:"field",decorators:[s()],key:"_searchInput",value:()=>""},{kind:"field",decorators:[s()],key:"_sortBy",value:()=>_[0]},{kind:"field",decorators:[s()],key:"section",value:void 0},{kind:"method",key:"shouldUpdate",value:function(e){return e.forEach(((e,t)=>{"hass"===t&&(this.sidebarDocked='"docked"'===window.localStorage.getItem("dockedSidebar"))})),e.has("narrow")||e.has("filters")||e.has("active")||e.has("_searchInput")||e.has("_load")||e.has("_sortBy")}},{kind:"field",key:"_repositoriesInActiveCategory",value(){return(e,t)=>null==e?void 0:e.filter((e=>{var i,a;return!e.installed&&(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===this.section)).categories)||void 0===a?void 0:a.includes(e.category))&&!e.installed&&(null==t?void 0:t.includes(e.category))}))}},{kind:"method",key:"firstUpdated",value:async function(){var e;if(this.addEventListener("filter-change",(e=>this._updateFilters(e))),0===(null===(e=this.filters)||void 0===e?void 0:e.length)){var t;const e=null===(t=r(this.hacs.language,this.route))||void 0===t?void 0:t.categories;null==e||e.filter((e=>{var t;return null===(t=this.hacs.info)||void 0===t?void 0:t.categories.includes(e)})).forEach((e=>{this.filters.push({id:e,value:e,checked:!0})})),this.requestUpdate("filters")}}},{kind:"method",key:"_updateFilters",value:function(e){const t=this.filters.find((t=>t.id===e.detail.id));this.filters.find((e=>e.id===t.id)).checked=!t.checked,this.requestUpdate("filters")}},{kind:"field",key:"_filterRepositories",value:()=>o(v)},{kind:"method",key:"render",value:function(){var e;if(!this.active)return l``;this._searchInput=window.localStorage.getItem("hacs-search")||"";let t=this._filterRepositories(this._repositoriesInActiveCategory(this.repositories,null===(e=this.hacs.info)||void 0===e?void 0:e.categories),this._searchInput);return 0!==this.filters.length&&(t=t.filter((e=>{var t;return null===(t=this.filters.find((t=>t.id===e.category)))||void 0===t?void 0:t.checked}))),l`
|
||||
<hacs-dialog
|
||||
.active=${this.active}
|
||||
.hass=${this.hass}
|
||||
.title=${this.hacs.localize("dialog_add_repo.title")}
|
||||
hideActions
|
||||
scrimClickAction
|
||||
maxWidth
|
||||
>
|
||||
<div class="searchandfilter" ?narrow=${this.narrow}>
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
.label=${this.hacs.localize("search.placeholder")}
|
||||
.filter=${this._searchInput}
|
||||
@value-changed=${this._inputValueChanged}
|
||||
?narrow=${this.narrow}
|
||||
></search-input>
|
||||
<mwc-select
|
||||
?narrow=${this.narrow}
|
||||
.label=${this.hacs.localize("dialog_add_repo.sort_by")}
|
||||
.value=${this._sortBy}
|
||||
@selected=${e=>this._sortBy=e.currentTarget.value}
|
||||
@closed=${m}
|
||||
>
|
||||
${_.map((e=>l`<mwc-list-item .value=${e}>
|
||||
${this.hacs.localize(`dialog_add_repo.sort_by_values.${e}`)||e}
|
||||
</mwc-list-item>`))}
|
||||
</mwc-select>
|
||||
</div>
|
||||
${this.filters.length>1?l`<div class="filters">
|
||||
<hacs-filter .hacs=${this.hacs} .filters="${this.filters}"></hacs-filter>
|
||||
</div>`:""}
|
||||
<div class=${c({content:!0,narrow:this.narrow})} @scroll=${this._loadMore}>
|
||||
<mwc-list>
|
||||
${0===t.length?l`<ha-alert>${this.hacs.localize("dialog_add_repo.no_match")}</ha-alert>`:t.sort(((e,t)=>"name"===this._sortBy?e.name.toLocaleLowerCase()<t.name.toLocaleLowerCase()?-1:1:e[this._sortBy]>t[this._sortBy]?-1:1)).slice(0,this._load).map((e=>l`<ha-clickable-list-item
|
||||
graphic=${this.narrow?"":"avatar"}
|
||||
twoline
|
||||
@click=${()=>this.active=!1}
|
||||
href="/hacs/repository/${e.id}"
|
||||
.hasMeta=${!this.narrow&&"integration"!==e.category}
|
||||
>
|
||||
${this.narrow?"":"integration"===e.category?l`
|
||||
<img
|
||||
loading="lazy"
|
||||
.src=${g({domain:e.domain,darkOptimized:this.hass.themes.darkMode,type:"icon"})}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
slot="graphic"
|
||||
/>
|
||||
`:l`
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
path="${d}"
|
||||
style="padding-left: 0; height: 40px; width: 40px;"
|
||||
>
|
||||
</ha-svg-icon>
|
||||
`}
|
||||
<span>${e.name}</span>
|
||||
<span slot="secondary">${e.description}</span>
|
||||
<ha-chip slot="meta">
|
||||
${this.hacs.localize(`common.${e.category}`)}
|
||||
</ha-chip>
|
||||
</ha-clickable-list-item>`))}
|
||||
</mwc-list>
|
||||
</div>
|
||||
</hacs-dialog>
|
||||
`}},{kind:"method",key:"_loadMore",value:function(e){const t=e.target.scrollTop;t>=this._top?this._load+=1:this._load-=1,this._top=t}},{kind:"method",key:"_inputValueChanged",value:function(e){this._searchInput=e.detail.value,window.localStorage.setItem("hacs-search",this._searchInput)}},{kind:"method",key:"_onImageLoad",value:function(e){e.target.style.visibility="initial"}},{kind:"method",key:"_onImageError",value:function(e){var t;if(null!==(t=e.target)&&void 0!==t&&t.outerHTML)try{e.target.outerHTML=`<ha-svg-icon path="${d}" slot="graphic"></ha-svg-icon>`}catch(e){}}},{kind:"get",static:!0,key:"styles",value:function(){return[n,h,u`
|
||||
.content {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin-top: -12px;
|
||||
display: flex;
|
||||
width: 200px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 16px;
|
||||
width: 1024px;
|
||||
max-width: 100%;
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 75%;
|
||||
}
|
||||
search-input[narrow],
|
||||
mwc-select[narrow] {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.filters {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
hacs-filter {
|
||||
width: 100%;
|
||||
margin-left: -32px;
|
||||
}
|
||||
|
||||
.searchandfilter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: self-end;
|
||||
}
|
||||
|
||||
.searchandfilter[narrow] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ha-chip {
|
||||
margin-left: -52px;
|
||||
}
|
||||
`]}}]}}),a);export{k as HacsAddRepositoryDialog};
|
||||
Binary file not shown.
|
|
@ -1,178 +0,0 @@
|
|||
import{a as o,h as t,e,$ as i,w as a,r as s,n as r,t as n,L as h,N as l,ai as c,a2 as d,a3 as p,m as u,c as y,aN as m,aO as f,aP as v,E as _,aQ as g,z as b,aR as k,aS as w,aT as $,aU as x,aV as z,aW as j,ah as R,am as L,ar as S,as as F,aX as I,d as P}from"./main-ad130be7.js";import"./c.bc5a73e9.js";import{e as C}from"./c.50bfd408.js";import"./c.97b7c4b0.js";import{r as E}from"./c.4204ca09.js";import{g as T}from"./c.f2bb3724.js";import{s as U}from"./c.4266acdb.js";import"./c.a5f69ed4.js";import{f as D}from"./c.fe747ba2.js";import{m as K}from"./c.f6611997.js";import"./c.2d5ed670.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.4feb0cb8.js";import"./c.0ca5587f.js";import"./c.5d3ce9d6.js";import"./c.743a15a1.js";o([r("hass-subpage")],(function(o,t){return{F:class extends t{constructor(...t){super(...t),o(this)}},d:[{kind:"field",decorators:[e({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[e()],key:"header",value:void 0},{kind:"field",decorators:[e({type:Boolean,attribute:"main-page"})],key:"mainPage",value:()=>!1},{kind:"field",decorators:[e({type:String,attribute:"back-path"})],key:"backPath",value:void 0},{kind:"field",decorators:[e({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[e({type:Boolean})],key:"supervisor",value:()=>!1},{kind:"field",decorators:[E(".content")],key:"_savedScrollPos",value:void 0},{kind:"method",key:"render",value:function(){var o;return i`
|
||||
<div class="toolbar">
|
||||
${this.mainPage||null!==(o=history.state)&&void 0!==o&&o.root?i`
|
||||
<ha-menu-button
|
||||
.hassio=${this.supervisor}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`:this.backPath?i`
|
||||
<a href=${this.backPath}>
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
</a>
|
||||
`:i`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
<div class="main-title">${this.header}</div>
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
<div class="content" @scroll=${this._saveScrollPos}><slot></slot></div>
|
||||
`}},{kind:"method",decorators:[a({passive:!0})],key:"_saveScrollPos",value:function(o){this._savedScrollPos=o.target.scrollTop}},{kind:"method",key:"_backTapped",value:function(){history.back()}},{kind:"get",static:!0,key:"styles",value:function(){return s`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
padding: 0 16px;
|
||||
pointer-events: none;
|
||||
background-color: var(--app-header-background-color);
|
||||
font-weight: 400;
|
||||
color: var(--app-header-text-color, white);
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.toolbar a {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
.main-title {
|
||||
margin: 0 0 0 24px;
|
||||
line-height: 20px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 1px - var(--header-height));
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
`}}]}}),t);let M=o([r("hacs-repository-panel")],(function(o,t){class a extends t{constructor(...t){super(...t),o(this)}}return{F:a,d:[{kind:"field",decorators:[e({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"narrow",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"isWide",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"_repository",value:void 0},{kind:"field",decorators:[n()],key:"_error",value:void 0},{kind:"method",key:"connectedCallback",value:function(){h(l(a.prototype),"connectedCallback",this).call(this),document.body.addEventListener("keydown",this._generateMyLink)}},{kind:"method",key:"disconnectedCallback",value:function(){h(l(a.prototype),"disconnectedCallback",this).call(this),document.body.removeEventListener("keydown",this._generateMyLink)}},{kind:"field",key:"_generateMyLink",value(){return o=>{if(!(o.ctrlKey||o.shiftKey||o.metaKey||o.altKey)&&"m"===o.key&&c.location.pathname.startsWith("/hacs/repository/")){if(!this._repository)return;const o=new URLSearchParams({redirect:"hacs_repository",owner:this._repository.full_name.split("/")[0],repository:this._repository.full_name.split("/")[1],category:this._repository.category});window.open(`https://my.home-assistant.io/create-link/?${o.toString()}`,"_blank")}}}},{kind:"method",key:"firstUpdated",value:async function(o){h(l(a.prototype),"firstUpdated",this).call(this,o);const t=C();if(Object.entries(t).length){let o;const e=`${t.owner}/${t.repository}`;if(o=this.hacs.repositories.find((o=>o.full_name.toLocaleLowerCase()===e.toLocaleLowerCase())),!o&&t.category){if(!await U(this,{title:this.hacs.localize("my.add_repository_title"),text:this.hacs.localize("my.add_repository_description",{repository:e}),confirmText:this.hacs.localize("common.add"),dismissText:this.hacs.localize("common.cancel")}))return void(this._error=this.hacs.localize("my.repository_not_found",{repository:e}));try{await d(this.hass,e,t.category),this.hacs.repositories=await p(this.hass),o=this.hacs.repositories.find((o=>o.full_name.toLocaleLowerCase()===e.toLocaleLowerCase()))}catch(o){return void(this._error=o)}}o?this._fetchRepository(String(o.id)):this._error=this.hacs.localize("my.repository_not_found",{repository:e})}else{const o=this.route.path.indexOf("/",1),t=this.route.path.substr(o+1);if(!t)return void(this._error="Missing repositoryId from route");this._fetchRepository(t)}}},{kind:"method",key:"updated",value:function(o){h(l(a.prototype),"updated",this).call(this,o),o.has("repositories")&&this._repository&&this._fetchRepository()}},{kind:"method",key:"_fetchRepository",value:async function(o){try{this._repository=await D(this.hass,o||String(this._repository.id))}catch(o){this._error=null==o?void 0:o.message}}},{kind:"field",key:"_getAuthors",value:()=>u((o=>{const t=[];if(!o.authors)return t;if(o.authors.forEach((o=>t.push(o.replace("@","")))),0===t.length){const e=o.full_name.split("/")[0];if(["custom-cards","custom-components","home-assistant-community-themes"].includes(e))return t;t.push(e)}return t}))},{kind:"method",key:"render",value:function(){if(this._error)return i`<hass-error-screen .error=${this._error}></hass-error-screen>`;if(!this._repository)return i`<hass-loading-screen></hass-loading-screen>`;const o=this._getAuthors(this._repository);return i`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.header=${this._repository.name}
|
||||
hasFab
|
||||
>
|
||||
<ha-icon-overflow-menu
|
||||
slot="toolbar-icon"
|
||||
narrow
|
||||
.hass=${this.hass}
|
||||
.items=${[{path:y,label:this.hacs.localize("common.repository"),action:()=>c.open(`https://github.com/${this._repository.full_name}`,"_blank","noreferrer=true")},{path:m,label:this.hacs.localize("repository_card.update_information"),action:()=>this._refreshReopsitoryInfo()},{path:f,label:this.hacs.localize("repository_card.redownload"),action:()=>this._downloadRepositoryDialog(),hideForUninstalled:!0},{category:"plugin",hideForUninstalled:!0,path:v,label:this.hacs.localize("repository_card.open_source"),action:()=>c.open(`/hacsfiles/${this._repository.local_path.split("/").pop()}/${this._repository.file_name}`,"_blank","noreferrer=true")},{path:_,label:this.hacs.localize("repository_card.open_issue"),action:()=>c.open(`https://github.com/${this._repository.full_name}/issues`,"_blank","noreferrer=true")},{hideForId:"172733314",path:g,label:this.hacs.localize("repository_card.report"),hideForUninstalled:!0,action:()=>c.open(`https://github.com/hacs/integration/issues/new?assignees=ludeeus&labels=flag&template=removal.yml&repo=${this._repository.full_name}&title=Request for removal of ${this._repository.full_name}`,"_blank","noreferrer=true")},{hideForId:"172733314",hideForUninstalled:!0,path:b,label:this.hacs.localize("common.remove"),action:()=>this._removeRepositoryDialog()}].filter((o=>(!o.category||this._repository.category===o.category)&&(!o.hideForId||String(this._repository.id)!==o.hideForId)&&(!o.hideForUninstalled||this._repository.installed_version)))}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
<div class="content">
|
||||
<div class="chips">
|
||||
${this._repository.installed?i`
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.version_installed")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${k}></ha-svg-icon>
|
||||
${this._repository.installed_version}
|
||||
</ha-chip>
|
||||
`:""}
|
||||
${o?o.map((o=>i`<hacs-link .url="https://github.com/${o}">
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.author")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${w}></ha-svg-icon>
|
||||
@${o}
|
||||
</ha-chip>
|
||||
</hacs-link>`)):""}
|
||||
${this._repository.downloads?i` <ha-chip hasIcon title="${this.hacs.localize("dialog_info.downloads")}">
|
||||
<ha-svg-icon slot="icon" .path=${$}></ha-svg-icon>
|
||||
${this._repository.downloads}
|
||||
</ha-chip>`:""}
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.stars")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${x}></ha-svg-icon>
|
||||
${this._repository.stars}
|
||||
</ha-chip>
|
||||
<hacs-link .url="https://github.com/${this._repository.full_name}/issues">
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.open_issues")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${z}></ha-svg-icon>
|
||||
${this._repository.issues}
|
||||
</ha-chip>
|
||||
</hacs-link>
|
||||
</div>
|
||||
${K.html(this._repository.additional_info||this.hacs.localize("dialog_info.no_info"),this._repository)}
|
||||
</div>
|
||||
${this._repository.installed_version?"":i`<ha-fab
|
||||
.label=${this.hacs.localize("common.download")}
|
||||
.extended=${!this.narrow}
|
||||
@click=${this._downloadRepositoryDialog}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${j}></ha-svg-icon>
|
||||
</ha-fab>`}
|
||||
</hass-subpage>
|
||||
`}},{kind:"method",key:"_downloadRepositoryDialog",value:function(){this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"download",repository:this._repository.id},bubbles:!0,composed:!0}))}},{kind:"method",key:"_removeRepositoryDialog",value:async function(){if("integration"===this._repository.category&&this._repository.config_flow){if((await T(this.hass)).some((o=>o.domain===this._repository.domain))){if(await U(this,{title:this.hacs.localize("dialog.configured.title"),text:this.hacs.localize("dialog.configured.message",{name:this._repository.name}),dismissText:this.hacs.localize("common.ignore"),confirmText:this.hacs.localize("common.navigate"),confirm:()=>{R("/config/integrations",{replace:!0})}}))return}}this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"progress",title:this.hacs.localize("dialog.remove.title"),confirmText:this.hacs.localize("dialog.remove.title"),content:this.hacs.localize("dialog.remove.message",{name:this._repository.name}),confirm:async()=>{await this._repositoryRemove()}},bubbles:!0,composed:!0}))}},{kind:"method",key:"_repositoryRemove",value:async function(){var o;if("plugin"===this._repository.category&&"yaml"!==(null===(o=this.hacs.info)||void 0===o?void 0:o.lovelace_mode)){(await L(this.hass)).filter((o=>o.url.startsWith(`/hacsfiles/${this._repository.full_name.split("/")[1]}/${this._repository.file_name}`))).forEach((async o=>{await S(this.hass,String(o.id))}))}await F(this.hass,String(this._repository.id)),history.back()}},{kind:"method",key:"_refreshReopsitoryInfo",value:async function(){await I(this.hass,String(this._repository.id))}},{kind:"get",static:!0,key:"styles",value:function(){return[P,s`
|
||||
hass-loading-screen {
|
||||
--app-header-background-color: var(--sidebar-background-color);
|
||||
--app-header-text-color: var(--sidebar-text-color);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
hass-subpage {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
float: right;
|
||||
right: calc(18px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ha-fab.rtl {
|
||||
float: left;
|
||||
right: auto;
|
||||
left: calc(18px + env(safe-area-inset-left));
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 12px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 8px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 500px) {
|
||||
.content {
|
||||
margin: 8px 4px 64px;
|
||||
}
|
||||
}
|
||||
`]}}]}}),t);export{M as HacsRepositoryPanel};
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue