This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
class Noolite(hass.Hass):
|
||||
brightness = 40
|
||||
force = False
|
||||
|
||||
def initialize(self):
|
||||
self.run_at_sunrise(self.sunrise_cb)
|
||||
# self.run_at_sunset(self.sunset_cb)
|
||||
self.listen_state(self.light_change, "group.mainroom_light")
|
||||
self.listen_event(self.away_mode, "away_mode")
|
||||
|
||||
def away_mode(self, event_id, event_args, kwargs):
|
||||
self.noolite_off()
|
||||
|
||||
def light_change(self, entity, attribute, old, new, kwargs):
|
||||
sun_down = self.sun_down()
|
||||
if new == "off" and (sun_down or self.force) and self.somebody_home():
|
||||
self.noolite_on(self.brightness)
|
||||
else:
|
||||
self.noolite_off()
|
||||
|
||||
def noolite_off(self):
|
||||
self.log("Turning off salt lamp.")
|
||||
self.turn_off(self.args['entity_id'])
|
||||
|
||||
def noolite_on(self, brightness=120):
|
||||
self.log("Turning on salt lamp for brightness {}.".format(brightness))
|
||||
self.turn_on(self.args['entity_id'], brightness=self.brightness)
|
||||
|
||||
def sunset_cb(self, kwargs):
|
||||
if not self.somebody_home():
|
||||
return
|
||||
self.noolite_on(self.brightness)
|
||||
|
||||
def somebody_home(self):
|
||||
if 'ha_panel' not in self.args:
|
||||
return True
|
||||
ha_panel_state = self.get_state(self.args['ha_panel'])
|
||||
if ha_panel_state == 'armed_away':
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def sunrise_cb(self, kwargs):
|
||||
self.noolite_off()
|
||||
@@ -1,5 +0,0 @@
|
||||
salt_lamp:
|
||||
module: salt_lamp
|
||||
class: Noolite
|
||||
entity_id: light.salt_lamp
|
||||
ha_panel: alarm_control_panel.ha_alarm
|
||||
@@ -1,51 +0,0 @@
|
||||
esphome:
|
||||
name: bath_light
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_24_name
|
||||
password: !secret wifi_24_pass
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "BathLight Fallback Hotspot"
|
||||
password: !secret esphome_captive_pass
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
password: !secret esphome_pass
|
||||
|
||||
ota:
|
||||
password: !secret esphome_pass
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO14
|
||||
mode: INPUT_PULLUP
|
||||
inverted: True
|
||||
id: button_1
|
||||
on_press:
|
||||
then:
|
||||
- light.toggle: light_1
|
||||
|
||||
- platform: status
|
||||
name: "Bath status"
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: GPIO12
|
||||
id: relay_1
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: "bath"
|
||||
id: light_1
|
||||
output: relay_1
|
||||
|
||||
status_led:
|
||||
pin:
|
||||
number: GPIO13
|
||||
inverted: yes
|
||||
@@ -1,40 +0,0 @@
|
||||
# Need to turn off logging because senseair uses uart and have conflicts with logging
|
||||
logger:
|
||||
baud_rate: 0
|
||||
|
||||
esphome:
|
||||
name: senseair
|
||||
platform: ESP8266
|
||||
board: d1_mini
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_24_name
|
||||
password: !secret wifi_24_pass
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Senseair Fallback Hotspot"
|
||||
password: !secret esphome_captive_pass
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
password: !secret esphome_pass
|
||||
|
||||
ota:
|
||||
password: !secret esphome_pass
|
||||
|
||||
# senseair communication uart
|
||||
uart:
|
||||
rx_pin: D7
|
||||
tx_pin: D8
|
||||
baud_rate: 9600
|
||||
|
||||
# hass sensor =)
|
||||
sensor:
|
||||
- platform: senseair
|
||||
co2:
|
||||
name: "SenseAir CO2 Value"
|
||||
update_interval: 60s
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "esphome.h"
|
||||
#include <Bounce2.h>
|
||||
|
||||
#define CounterNum 2
|
||||
#define COUNTER_COLD 0
|
||||
#define COUNTER_HOT 1
|
||||
|
||||
static const char *TAG = "watercounter.sensor";
|
||||
|
||||
class WaterCountersSensor : public Component, public Sensor, public CustomAPIDevice {
|
||||
protected:
|
||||
int CounterPin[CounterNum] = {D1,D2};
|
||||
float CounterValues[CounterNum] = {};
|
||||
Bounce CounterBouncer[CounterNum] = {};
|
||||
|
||||
public:
|
||||
Sensor *cold_sensor = new Sensor();
|
||||
Sensor *hot_sensor = new Sensor();
|
||||
bool initReadValues = false;
|
||||
|
||||
globals::GlobalsComponent<float> *counter_cold_value;
|
||||
globals::GlobalsComponent<float> *counter_hot_value;
|
||||
|
||||
void setup() override {
|
||||
register_service(&WaterCountersSensor::on_set_counter_values, "set_counter_values",
|
||||
{"cold_value", "hot_value"});
|
||||
|
||||
// init flash variables
|
||||
counter_cold_value = new globals::GlobalsComponent<float>();
|
||||
App.register_component(counter_cold_value);
|
||||
counter_cold_value->set_restore_value(1547432152);
|
||||
|
||||
counter_hot_value = new globals::GlobalsComponent<float>();
|
||||
App.register_component(counter_hot_value);
|
||||
counter_hot_value->set_restore_value(2087162808);
|
||||
|
||||
ESP_LOGD(TAG, "Initializing counters.");
|
||||
for (int i=0; i<CounterNum; i++) {
|
||||
pinMode(CounterPin[i], INPUT_PULLUP);
|
||||
CounterBouncer[i].attach(CounterPin[i]);
|
||||
CounterBouncer[i].interval(100);
|
||||
}
|
||||
ESP_LOGD(TAG, "Initialize done.");
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if (is_connected() && !initReadValues) {
|
||||
ESP_LOGD(TAG, "Reading counters from fs.");
|
||||
for (int i=0; i<CounterNum; i++) {
|
||||
readCounterFromFile(i);
|
||||
sendCounterValue(i);
|
||||
}
|
||||
initReadValues = true;
|
||||
}
|
||||
for (int i=0; i<CounterNum; i++) {
|
||||
boolean changed = CounterBouncer[i].update();
|
||||
if ( changed ) {
|
||||
int value = CounterBouncer[i].read();
|
||||
if ( value == LOW) {
|
||||
ESP_LOGD(TAG, "Got counter %d 'tick'.", i);
|
||||
ESP_LOGD(TAG, "Incrementing value. Old: %.2f", CounterValues[i]);
|
||||
CounterValues[i] = CounterValues[i] + 0.01;
|
||||
ESP_LOGD(TAG, "New: %.2f", CounterValues[i]);
|
||||
sendCounterValue(i);
|
||||
writeFile(i);
|
||||
printState(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// void update() override {
|
||||
// for (int i=0; i<CounterNum; i++) {
|
||||
// sendCounterValue(i);
|
||||
// }
|
||||
// }
|
||||
|
||||
// --------------- HASS service call handling ----------------------------
|
||||
void on_set_counter_values(float cold_value, float hot_value)
|
||||
{
|
||||
ESP_LOGD(TAG, "Saving values: cold - %.2f, hot - %.2f", cold_value, hot_value);
|
||||
|
||||
CounterValues[COUNTER_COLD] = cold_value;
|
||||
CounterValues[COUNTER_HOT] = hot_value;
|
||||
|
||||
for (int i=0; i<CounterNum; i++) {
|
||||
writeFile(i);
|
||||
sendCounterValue(i);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Save success.");
|
||||
}
|
||||
|
||||
// --------------- functions ----------------------------
|
||||
void readCounterFromFile(int counterId) {
|
||||
if (counterId == COUNTER_COLD) {
|
||||
CounterValues[COUNTER_COLD] = counter_cold_value->value();
|
||||
}else if (counterId == COUNTER_HOT) {
|
||||
CounterValues[COUNTER_HOT] = counter_hot_value->value();
|
||||
}
|
||||
}
|
||||
|
||||
void writeFile(int counterId) {
|
||||
if (counterId == COUNTER_COLD) {
|
||||
counter_cold_value->value() = CounterValues[COUNTER_COLD];
|
||||
}else if (counterId == COUNTER_HOT) {
|
||||
counter_hot_value->value() = CounterValues[COUNTER_HOT];
|
||||
}
|
||||
ESP_LOGD(TAG, "Saving done.");
|
||||
}
|
||||
|
||||
void sendCounterValue(int counterId) {
|
||||
if (counterId == COUNTER_COLD) {
|
||||
ESP_LOGD(TAG, "Publishing cold state.");
|
||||
float value = CounterValues[counterId];
|
||||
cold_sensor->publish_state(value);
|
||||
}else if (counterId == COUNTER_HOT) {
|
||||
ESP_LOGD(TAG, "Publishing hot state.");
|
||||
float value = CounterValues[counterId];
|
||||
hot_sensor->publish_state(value);
|
||||
}
|
||||
}
|
||||
|
||||
String countrName(int Id) {
|
||||
switch(Id) {
|
||||
case COUNTER_HOT:
|
||||
return "hot";
|
||||
case COUNTER_COLD:
|
||||
return "cold";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void printState(int counterId) {
|
||||
ESP_LOGD(TAG, "Counter[%d]=%.2f", counterId, CounterValues[counterId]);
|
||||
}
|
||||
};
|
||||
@@ -2,10 +2,6 @@
|
||||
substitutions:
|
||||
devicename: watercontrol
|
||||
upper_devicename: Watercontrol
|
||||
pin_cold: D1
|
||||
init_cold: '216.08'
|
||||
pin_hot: D2
|
||||
init_hot: '112.3'
|
||||
water_open_pin: D5
|
||||
water_close_pin: D3
|
||||
ultrasonic_echo_pin: D6
|
||||
@@ -19,6 +15,10 @@ esphome:
|
||||
name: $devicename
|
||||
platform: ESP8266
|
||||
board: d1_mini
|
||||
libraries:
|
||||
- "Bounce2"
|
||||
includes:
|
||||
- water_counters.h
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_24_name
|
||||
@@ -43,58 +43,25 @@ globals:
|
||||
type: bool
|
||||
restore_value: yes
|
||||
initial_value: 'true'
|
||||
- id: counter_cold_value
|
||||
type: float
|
||||
restore_value: yes
|
||||
initial_value: $init_cold
|
||||
- id: counter_hot_value
|
||||
type: float
|
||||
restore_value: yes
|
||||
initial_value: $init_hot
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
id: pulse_cold
|
||||
pin:
|
||||
number: $pin_cold
|
||||
mode: INPUT_PULLUP
|
||||
filters:
|
||||
- delayed_on: 1s
|
||||
- delayed_off: 1s
|
||||
on_press:
|
||||
then:
|
||||
- lambda: 'id(counter_cold_value) += 0.01;'
|
||||
|
||||
- platform: gpio
|
||||
id: pulse_hot
|
||||
pin:
|
||||
number: $pin_hot
|
||||
mode: INPUT_PULLUP
|
||||
filters:
|
||||
- delayed_on: 1s
|
||||
- delayed_off: 1s
|
||||
on_press:
|
||||
then:
|
||||
- lambda: 'id(counter_hot_value) += 0.01;'
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "$upper_devicename Uptime"
|
||||
|
||||
- platform: template
|
||||
name: "counter_hot"
|
||||
accuracy_decimals: 2
|
||||
#------------------------------------------------------------------------------
|
||||
- platform: custom
|
||||
lambda: |-
|
||||
return id(counter_hot_value);
|
||||
update_interval: 60s
|
||||
|
||||
- platform: template
|
||||
name: "counter_cold"
|
||||
auto my_sensor = new WaterCountersSensor();
|
||||
App.register_component(my_sensor);
|
||||
return {my_sensor->cold_sensor, my_sensor->hot_sensor};
|
||||
sensors:
|
||||
- name: "Counter Cold"
|
||||
accuracy_decimals: 2
|
||||
- name: "Counter Hot"
|
||||
accuracy_decimals: 2
|
||||
lambda: |-
|
||||
return id(counter_cold_value);
|
||||
update_interval: 60s
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
- platform: ultrasonic
|
||||
trigger_pin: $ultrasonic_trig_pin
|
||||
echo_pin: $ultrasonic_echo_pin
|
||||
@@ -131,12 +98,3 @@ switch:
|
||||
- delay: 5s
|
||||
- switch.turn_off: water_close
|
||||
- lambda: 'id(water_state) = false;'
|
||||
|
||||
- platform: template
|
||||
name: "Reset"
|
||||
turn_on_action:
|
||||
- lambda: 'id(counter_cold_value) = 216.08;'
|
||||
- lambda: 'id(counter_hot_value) = 112.3;'
|
||||
turn_off_action:
|
||||
- lambda: 'id(counter_cold_value) = 216.08;'
|
||||
- lambda: 'id(counter_hot_value) = 112.3;'
|
||||
|
||||
@@ -11,8 +11,6 @@ homeassistant:
|
||||
|
||||
logger:
|
||||
default: info
|
||||
logs:
|
||||
custom_components.yandex_smart_home: debug
|
||||
|
||||
api:
|
||||
|
||||
@@ -68,7 +66,7 @@ remote: !include_dir_merge_list configuration/remote
|
||||
#one item
|
||||
mqtt: !include configuration/mqtt/mqtt.yaml
|
||||
telegram_bot: !include configuration/bot/telegram_bot.yaml
|
||||
vacuum: !include configuration/xiaomi_aqara/vacuum.yaml
|
||||
# vacuum: !include configuration/xiaomi_aqara/vacuum.yaml
|
||||
xiaomi_aqara: !include configuration/xiaomi_aqara/xiaomi_aqara.yaml
|
||||
nest: !include configuration/nest/nest.yaml
|
||||
homekit: !include configuration/homekit/homekit.yaml
|
||||
@@ -81,4 +79,4 @@ modbus: !include configuration/modbus.yaml
|
||||
utility_meter: !include_dir_merge_named configuration/utility_meter
|
||||
|
||||
speedtestdotnet: !include configuration/speedtest.yaml
|
||||
yandex_smart_home: !include configuration/alice.yaml
|
||||
# yandex_smart_home: !include configuration/alice.yaml
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
filter:
|
||||
include_domains:
|
||||
- climate
|
||||
- switch
|
||||
- light
|
||||
- vacuum
|
||||
- cover
|
||||
- fan
|
||||
include_entities:
|
||||
- input_boolean.alice_night_mode
|
||||
- input_boolean.alice_turn_off_light
|
||||
- input_boolean.alice_away_mode
|
||||
|
||||
exclude_entities:
|
||||
- switch.plug_158d00020f3a29
|
||||
- switch.plug_158d00026ea017
|
||||
- switch.plug_158d00010dd98d
|
||||
- switch.xiaomi_miio_switch
|
||||
- switch.domofon_mode
|
||||
- switch.domofon_open
|
||||
- light.gateway_light_f0b429aa1463
|
||||
- switch.domofon_automatically_open
|
||||
- switch.domofon_automatically_open_once
|
||||
- switch.domofon_force_open
|
||||
- switch.wled_sync_receive
|
||||
- switch.wled_sync_send
|
||||
- switch.airfresher_fresh
|
||||
- switch.airfresher_reset
|
||||
|
||||
|
||||
entity_config:
|
||||
input_boolean.alice_away_mode:
|
||||
name: "Я ушёл"
|
||||
room: "Сценарии"
|
||||
input_boolean.alice_night_mode:
|
||||
name: "Спокойной ночи"
|
||||
room: "Сценарии"
|
||||
input_boolean.alice_turn_off_light:
|
||||
name: "Выключи свет"
|
||||
room: "Сценарии"
|
||||
climate.living_room:
|
||||
name: "Термостат"
|
||||
room: "Гостиная"
|
||||
light.yeelight_mono1_f0b429a8f6df:
|
||||
name: "Люстра"
|
||||
room: "Гостиная"
|
||||
light.yeelight_color1_34ce008fe328:
|
||||
name: "Люстра"
|
||||
room: "Гостиная"
|
||||
light.yeelight_color1_286c071065c7:
|
||||
name: "Люстра"
|
||||
room: "Гостиная"
|
||||
light.yeelight_mono1_f0b4290e5444:
|
||||
name: "Люстра"
|
||||
room: "Гостиная"
|
||||
light.yeelight_mono1_34ce0087ff33:
|
||||
name: "Люстра"
|
||||
room: "Гостиная"
|
||||
light.salt_lamp:
|
||||
name: "Соляная лампа"
|
||||
room: "Гостиная"
|
||||
switch.wifi_socket:
|
||||
name: "Гирлянда"
|
||||
room: "Гостиная"
|
||||
fan.xiaomi_miio_device:
|
||||
name: "Воздухоочиститель"
|
||||
room: "Гостиная"
|
||||
switch.plug_158d0001104a0c:
|
||||
name: "Телевизор"
|
||||
room: "Гостиная"
|
||||
cover.curtain_158d0002b0c46a:
|
||||
name: "Шторы"
|
||||
room: "Гостиная"
|
||||
light.bath:
|
||||
name: "Люстра"
|
||||
room: "Туалет"
|
||||
light.philips_led:
|
||||
name: "Люстра"
|
||||
room: "Туалет"
|
||||
light.kitchen_mini_light:
|
||||
name: "Подсветка"
|
||||
room: "Кухня"
|
||||
light.kitchen:
|
||||
name: "Люстра"
|
||||
room: "Кухня"
|
||||
switch.plug_158d0001a68c64:
|
||||
name: "Посудомойка"
|
||||
room: "Кухня"
|
||||
switch.plug_158d0001a4cfb4:
|
||||
name: "Стиралка"
|
||||
room: "Кухня"
|
||||
light.hall:
|
||||
name: "Люстра"
|
||||
room: "Коридор"
|
||||
switch.plug_158d0001a68b18:
|
||||
name: "Термопот"
|
||||
room: "Кухня"
|
||||
light.yeelight_mono1_f0b429a8d936:
|
||||
name: "Люстра"
|
||||
room: "Балкон"
|
||||
light.yeelight_mono1_286c07f12bed:
|
||||
name: "Люстра"
|
||||
room: "Балкон"
|
||||
switch.plug_158d0002924779:
|
||||
name: "Фитолампа"
|
||||
room: "Балкон"
|
||||
vacuum.xiaomi_vacuum_cleaner:
|
||||
name: "Пылесос"
|
||||
room: "Дом"
|
||||
switch.water_valve:
|
||||
name: "Подача воды"
|
||||
room: "Дом"
|
||||
light.wled:
|
||||
name: "Ёлка"
|
||||
room: "Гостиная"
|
||||
@@ -1,4 +0,0 @@
|
||||
- name: appletv4
|
||||
host: !secret atv4_ip
|
||||
login_id: !secret atv4_login
|
||||
credentials: !secret atv4_credentials
|
||||
@@ -1,6 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/magnet/balcony/status"
|
||||
name: "door_window_sensor_158d00010f7e19"
|
||||
payload_on: "open"
|
||||
payload_off: "close"
|
||||
device_class: opening
|
||||
@@ -1,6 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/magnet/bath/status"
|
||||
name: "door_window_sensor_158d000111a469"
|
||||
payload_on: "open"
|
||||
payload_off: "close"
|
||||
device_class: opening
|
||||
@@ -1,6 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/magnet/hall/status"
|
||||
name: "door_window_sensor_158d0001130141"
|
||||
payload_on: "open"
|
||||
payload_off: "close"
|
||||
device_class: opening
|
||||
@@ -1,18 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/motion/bath/status"
|
||||
name: "motion_sensor_158d0000f9546b" #"bath_motion"
|
||||
payload_on: "motion"
|
||||
payload_off: "no_motion"
|
||||
device_class: motion
|
||||
- platform: mqtt
|
||||
state_topic: "home/sensor_motion.aq2/bath2/status"
|
||||
name: "motion_sensor_158d000165fed5" #"bath_motion2"
|
||||
payload_on: "motion"
|
||||
payload_off: "no_motion"
|
||||
device_class: motion
|
||||
- platform: mqtt
|
||||
state_topic: "home/motion/bath3/status"
|
||||
name: "motion_sensor_158d0001537e21" #"bath_motion3"
|
||||
payload_on: "motion"
|
||||
payload_off: "no_motion"
|
||||
device_class: motion
|
||||
@@ -1,6 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/motion/hall/status"
|
||||
name: "motion_sensor_158d0000e7c7ad"
|
||||
payload_on: "motion"
|
||||
payload_off: "no_motion"
|
||||
device_class: motion
|
||||
@@ -1,12 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/motion/kitchen/status"
|
||||
name: "motion_sensor_158d0000fbc664"
|
||||
payload_on: "motion"
|
||||
payload_off: "no_motion"
|
||||
device_class: motion
|
||||
- platform: mqtt
|
||||
state_topic: "home/motion/kitchen2/status"
|
||||
name: "motion_sensor_158d0001535c97"
|
||||
payload_on: "motion"
|
||||
payload_off: "no_motion"
|
||||
device_class: motion
|
||||
@@ -1,6 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/motion/mainroom/status"
|
||||
name: "motion_sensor_158d0000f6b2fa"
|
||||
payload_on: "motion"
|
||||
payload_off: "no_motion"
|
||||
device_class: motion
|
||||
@@ -1,6 +0,0 @@
|
||||
- platform: mqtt
|
||||
state_topic: "home/sensor_wleak.aq1/kitchen/status"
|
||||
name: "water_leak_sensor_158d0001d594fa"
|
||||
payload_on: "leak"
|
||||
payload_off: "no_leak"
|
||||
device_class: moisture
|
||||
@@ -1,4 +0,0 @@
|
||||
- platform: xiaomi_miio
|
||||
model: zhimi.airpurifier.mc1
|
||||
host: !secret purifier_ip
|
||||
token: !secret purifier_key
|
||||
@@ -2,6 +2,5 @@ bath_motion:
|
||||
name: Движение (ванна)
|
||||
control: hidden
|
||||
entities:
|
||||
- binary_sensor.motion_sensor_158d0000f9546b
|
||||
- binary_sensor.motion_sensor_158d000165fed5
|
||||
- binary_sensor.motion_sensor_158d0001537e21
|
||||
@@ -2,5 +2,4 @@ hall_motion:
|
||||
name: Движение (прихожая)
|
||||
control: hidden
|
||||
entities:
|
||||
- binary_sensor.motion_sensor_158d0000e7c7ad
|
||||
- binary_sensor.motion_sensor_158d0002044548
|
||||
@@ -2,5 +2,4 @@ kitchen_motion:
|
||||
name: Движение (кухня)
|
||||
control: hidden
|
||||
entities:
|
||||
- binary_sensor.motion_sensor_158d0000fbc664
|
||||
- binary_sensor.motion_sensor_158d0001535c97
|
||||
@@ -3,5 +3,4 @@ mainroom_motion:
|
||||
control: hidden
|
||||
entities:
|
||||
- binary_sensor.motion_sensor_158d0000f6b2fa
|
||||
- binary_sensor.motion_sensor_158d0001e50ce4
|
||||
- binary_sensor.motion_sensor_158d0001e80f0d
|
||||
@@ -1,7 +0,0 @@
|
||||
# - platform: mqtt
|
||||
# name: "balcony"
|
||||
# state_topic: "stat/BALCONYV2/POWER"
|
||||
# command_topic: "cmnd/BALCONYV2/POWER"
|
||||
# payload_on: "ON"
|
||||
# payload_off: "OFF"
|
||||
# optimistic: false
|
||||
@@ -1,13 +0,0 @@
|
||||
- platform: mqtt
|
||||
name: "salt_lamp"
|
||||
availability_topic: "home/noolite/$online"
|
||||
payload_available: "true"
|
||||
payload_not_available: "false"
|
||||
state_topic: "home/noolite/ch0/power"
|
||||
command_topic: "home/noolite/ch0/power/set"
|
||||
on_command_type: first
|
||||
payload_on: "on"
|
||||
payload_off: "off"
|
||||
brightness_state_topic: "home/noolite/ch0/brightness"
|
||||
brightness_command_topic: "home/noolite/ch0/brightness/set"
|
||||
brightness_scale: 120
|
||||
@@ -1,4 +0,0 @@
|
||||
#gateways:
|
||||
# - device: !secret mysensors_gateway_ip
|
||||
# tcp_port: 5003
|
||||
#version: '2.0'
|
||||
@@ -1,4 +0,0 @@
|
||||
# - platform: xiaomi_miio
|
||||
# name: xiaomi_remote
|
||||
# host: !secret xiaomi_remote_host
|
||||
# token: !secret xiaomi_remote_token
|
||||
@@ -1,6 +0,0 @@
|
||||
airfresher_reset:
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
topic: 'home/airfresher/airfresher/remain/set'
|
||||
payload: 1500
|
||||
@@ -1,6 +0,0 @@
|
||||
audio_bass_minus:
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
topic: "home/remote/rm2/code/set"
|
||||
payload: "260050000001339514371412131313131312131313131313131313381337133814371337143714371412133814361437141213131213131313381412131313131238143714371436140005060001334a14000d050000000000000000"
|
||||
@@ -1,6 +0,0 @@
|
||||
audio_bass_plus:
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
topic: "home/remote/rm2/code/set"
|
||||
payload: "260050000001339414371412131313131213131313131313131313381436143714371437143713381412133813121338141213131313131313381312133814121338143614371437140005060001324a14000d050000000000000000"
|
||||
@@ -1,6 +0,0 @@
|
||||
audio_vol_minus:
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
topic: "home/remote/rm2/code/set"
|
||||
payload: "260050000001329513381313131313121313131313131313131312381338133813371338133813381313121313131313133812131313131313381338123813381313123813381338120005060001314b13000d050000000000000000"
|
||||
@@ -1,6 +0,0 @@
|
||||
audio_vol_plus:
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
topic: "home/remote/rm2/code/set"
|
||||
payload: "260058000001309613381213131313131213141214121313141113381437143614371437133714371437141213121313143713121313131313131238143713381313123814371436150005030001324916000c5b0001324914000d05"
|
||||
@@ -1,7 +0,0 @@
|
||||
- platform: template
|
||||
sensors:
|
||||
xiaomi_airpurifier_air_quality_pm25:
|
||||
friendly_name: "Air quality"
|
||||
value_template: "{{ state_attr('fan.xiaomi_miio_device', 'aqi') }}"
|
||||
unit_of_measurement: "μg/m³"
|
||||
icon_template: "mdi:weather-fog"
|
||||
@@ -1,12 +0,0 @@
|
||||
# - platform: mqtt
|
||||
# name: kitchen_mini_light
|
||||
# state_topic: "home/plug/kitchen-mini-light/status"
|
||||
# command_topic: "home/plug/kitchen-mini-light/status/set"
|
||||
# payload_on: "on"
|
||||
# payload_off: "off"
|
||||
|
||||
# - platform: broadlink
|
||||
# host: 192.168.1.63
|
||||
# mac: 'B4430DCB6C07'
|
||||
# type: 'sp2'
|
||||
# friendly_name: 'kitchen_mini_light'
|
||||
@@ -1,3 +0,0 @@
|
||||
- platform: xiaomi_miio
|
||||
host: !secret mirobot_ip
|
||||
token: !secret mirobot_key
|
||||
@@ -1,187 +0,0 @@
|
||||
"""
|
||||
Custom element manager for community created elements.
|
||||
|
||||
For more details about this integration, please refer to the documentation at
|
||||
https://hacs.xyz/
|
||||
"""
|
||||
import voluptuous as vol
|
||||
from aiogithubapi import AIOGitHub
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .configuration_schema import hacs_base_config_schema, hacs_config_option_schema
|
||||
from .const import DEV_MODE, DOMAIN, ELEMENT_TYPES, STARTUP, VERSION
|
||||
from .constrains import check_constans
|
||||
from .hacsbase import Hacs
|
||||
from .hacsbase.configuration import Configuration
|
||||
from .hacsbase.data import HacsData
|
||||
from .hacsbase.migration import ValidateData
|
||||
from .setup import add_sensor, load_hacs_repository, setup_frontend
|
||||
|
||||
SCHEMA = hacs_base_config_schema()
|
||||
SCHEMA[vol.Optional("options")] = hacs_config_option_schema()
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: SCHEMA}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up this integration using yaml."""
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
hass.data[DOMAIN] = config
|
||||
Hacs.hass = hass
|
||||
Hacs.configuration = Configuration.from_dict(
|
||||
config[DOMAIN], config[DOMAIN].get("options")
|
||||
)
|
||||
Hacs.configuration.config_type = "yaml"
|
||||
await startup_wrapper_for_yaml(Hacs)
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={}
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up this integration using UI."""
|
||||
conf = hass.data.get(DOMAIN)
|
||||
if config_entry.source == config_entries.SOURCE_IMPORT:
|
||||
if conf is None:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_remove(config_entry.entry_id)
|
||||
)
|
||||
return False
|
||||
Hacs.hass = hass
|
||||
Hacs.configuration = Configuration.from_dict(
|
||||
config_entry.data, config_entry.options
|
||||
)
|
||||
Hacs.configuration.config_type = "flow"
|
||||
Hacs.configuration.config_entry = config_entry
|
||||
config_entry.add_update_listener(reload_hacs)
|
||||
startup_result = await hacs_startup(Hacs)
|
||||
if not startup_result:
|
||||
raise ConfigEntryNotReady
|
||||
return startup_result
|
||||
|
||||
|
||||
async def startup_wrapper_for_yaml(hacs):
|
||||
"""Startup wrapper for yaml config."""
|
||||
startup_result = await hacs_startup(hacs)
|
||||
if not startup_result:
|
||||
hacs.hass.components.frontend.async_remove_panel(
|
||||
hacs.configuration.sidepanel_title.lower()
|
||||
.replace(" ", "_")
|
||||
.replace("-", "_")
|
||||
)
|
||||
hacs.logger.info("Could not setup HACS, trying again in 15 min")
|
||||
async_call_later(hacs.hass, 900, startup_wrapper_for_yaml(hacs))
|
||||
|
||||
|
||||
async def hacs_startup(hacs):
|
||||
"""HACS startup tasks."""
|
||||
hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}")
|
||||
hacs.version = VERSION
|
||||
hacs.logger.info(STARTUP)
|
||||
hacs.system.config_path = hacs.hass.config.path()
|
||||
hacs.system.ha_version = HAVERSION
|
||||
hacs.system.disabled = False
|
||||
hacs.github = AIOGitHub(
|
||||
hacs.configuration.token, async_create_clientsession(hacs.hass)
|
||||
)
|
||||
hacs.data = HacsData()
|
||||
|
||||
# Check HACS Constrains
|
||||
if not check_constans(hacs):
|
||||
if hacs.configuration.config_type == "flow":
|
||||
if hacs.configuration.config_entry is not None:
|
||||
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
||||
return False
|
||||
|
||||
# Set up frontend
|
||||
await setup_frontend(hacs)
|
||||
|
||||
# Load HACS
|
||||
if not await load_hacs_repository(hacs):
|
||||
if hacs.configuration.config_type == "flow":
|
||||
if hacs.configuration.config_entry is not None:
|
||||
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
||||
return False
|
||||
|
||||
val = ValidateData()
|
||||
if not val.validate_local_data_file():
|
||||
if hacs.configuration.config_type == "flow":
|
||||
if hacs.configuration.config_entry is not None:
|
||||
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
||||
return False
|
||||
|
||||
# Restore from storefiles
|
||||
if not await hacs.data.restore():
|
||||
hacs_repo = hacs().get_by_name("hacs/integration")
|
||||
hacs_repo.pending_restart = True
|
||||
if hacs.configuration.config_type == "flow":
|
||||
if hacs.configuration.config_entry is not None:
|
||||
await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
|
||||
return False
|
||||
|
||||
# Add aditional categories
|
||||
if hacs.configuration.appdaemon:
|
||||
ELEMENT_TYPES.append("appdaemon")
|
||||
if hacs.configuration.python_script:
|
||||
ELEMENT_TYPES.append("python_script")
|
||||
if hacs.configuration.theme:
|
||||
ELEMENT_TYPES.append("theme")
|
||||
hacs.common.categories = sorted(ELEMENT_TYPES)
|
||||
|
||||
# Setup startup tasks
|
||||
if hacs.configuration.config_type == "yaml":
|
||||
hacs.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, hacs().startup_tasks()
|
||||
)
|
||||
else:
|
||||
async_call_later(hacs.hass, 5, hacs().startup_tasks())
|
||||
|
||||
# Print DEV warning
|
||||
if hacs.configuration.dev:
|
||||
hacs.logger.warning(DEV_MODE)
|
||||
hacs.hass.components.persistent_notification.create(
|
||||
title="HACS DEV MODE", message=DEV_MODE, notification_id="hacs_dev_mode"
|
||||
)
|
||||
|
||||
# Add sensor
|
||||
add_sensor(hacs)
|
||||
|
||||
# Set up services
|
||||
# await add_services(hacs)
|
||||
|
||||
# Mischief managed!
|
||||
return True
|
||||
|
||||
|
||||
async def async_remove_entry(hass, config_entry):
|
||||
"""Handle removal of an entry."""
|
||||
Hacs().logger.info("Disabling HACS")
|
||||
Hacs().logger.info("Removing recuring tasks")
|
||||
for task in Hacs().tasks:
|
||||
task()
|
||||
Hacs().logger.info("Removing sensor")
|
||||
try:
|
||||
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
||||
except ValueError:
|
||||
pass
|
||||
Hacs().logger.info("Removing sidepanel")
|
||||
try:
|
||||
hass.components.frontend.async_remove_panel("hacs_web")
|
||||
except AttributeError:
|
||||
pass
|
||||
Hacs().system.disabled = True
|
||||
Hacs().logger.info("HACS is now disabled")
|
||||
|
||||
|
||||
async def reload_hacs(hass, config_entry):
|
||||
"""Reload HACS."""
|
||||
await async_remove_entry(hass, config_entry)
|
||||
await async_setup_entry(hass, config_entry)
|
||||
@@ -1,421 +0,0 @@
|
||||
"""API Endpoins."""
|
||||
from time import time
|
||||
from aiohttp import web
|
||||
|
||||
from integrationhelper import Logger
|
||||
|
||||
from .http import HacsWebResponse
|
||||
|
||||
APIRESPONSE = {}
|
||||
|
||||
|
||||
def apiresponse(classname):
|
||||
"""Decorator used to register API Responses."""
|
||||
APIRESPONSE[classname.name] = classname
|
||||
return classname
|
||||
|
||||
|
||||
class HacsAPI(HacsWebResponse):
|
||||
"""HacsAPI class."""
|
||||
|
||||
name = "hacsapi"
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
self.logger = Logger("hacs.api")
|
||||
self.url = self.hacsapi + "/{endpoint}"
|
||||
|
||||
async def post(self, request, endpoint): # pylint: disable=unused-argument
|
||||
"""Handle HACS API requests."""
|
||||
if self.system.disabled:
|
||||
return web.Response(status=404)
|
||||
self.endpoint = endpoint
|
||||
self.postdata = await request.post()
|
||||
self.raw_headers = request.raw_headers
|
||||
self.request = request
|
||||
self.logger.debug(f"Endpoint ({endpoint}) called")
|
||||
if self.configuration.dev:
|
||||
self.logger.debug(f"Raw headers ({self.raw_headers})")
|
||||
self.logger.debug(f"Postdata ({self.postdata})")
|
||||
if self.endpoint in APIRESPONSE:
|
||||
try:
|
||||
response = APIRESPONSE[self.endpoint]
|
||||
response = await response.response(self)
|
||||
except Exception as exception:
|
||||
render = self.render(f"error", message=exception)
|
||||
return web.Response(
|
||||
body=render, content_type="text/html", charset="utf-8"
|
||||
)
|
||||
else:
|
||||
# Return default response.
|
||||
response = await APIRESPONSE["generic"].response(self)
|
||||
|
||||
# set headers
|
||||
response.headers["Cache-Control"] = "max-age=0, must-revalidate"
|
||||
|
||||
# serve the response
|
||||
return response
|
||||
|
||||
|
||||
class HacsRunningTask(HacsAPI):
|
||||
"""Return if BG task is running."""
|
||||
|
||||
name = "hacs:task"
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
self.url = "/hacs_task"
|
||||
|
||||
async def get(self, request): # pylint: disable=unused-argument
|
||||
"""Handle GET request."""
|
||||
return web.json_response({"task": self.system.status.background_task})
|
||||
|
||||
|
||||
@apiresponse
|
||||
class Generic(HacsAPI):
|
||||
"""Generic API response."""
|
||||
|
||||
name = "generic"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
self.logger.error(f"Unknown endpoint '{self.endpoint}'")
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/settings?timestamp={time()}")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RemoveNewFlag(HacsAPI):
|
||||
"""Remove new flag on all repositories."""
|
||||
|
||||
name = "remove_new_flag"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
for repository in self.repositories:
|
||||
repository.status.new = False
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/{self.postdata['return']}?timestamp={time()}"
|
||||
)
|
||||
|
||||
|
||||
@apiresponse
|
||||
class DevTemplate(HacsAPI):
|
||||
"""Remove new flag on all repositories."""
|
||||
|
||||
name = "dev_template"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
from .handler.template import render_template
|
||||
|
||||
if "set" in self.postdata:
|
||||
self.developer.template_id = self.postdata.get("repository_id")
|
||||
repository = self.get_by_id(self.developer.template_id)
|
||||
template = render_template(self.postdata.get("template", ""), repository)
|
||||
info = await self.github.render_markdown(template)
|
||||
info = info.replace("<h3>", "<h6>").replace("</h3>", "</h6>")
|
||||
info = info.replace("<h2>", "<h5>").replace("</h2>", "</h5>")
|
||||
info = info.replace("<h1>", "<h4>").replace("</h1>", "</h4>")
|
||||
info = info.replace("<code>", "<code class='codeinfo'>")
|
||||
info = info.replace(
|
||||
'<a href="http', '<a rel="noreferrer" target="_blank" href="http'
|
||||
)
|
||||
info = info.replace("<ul>", "")
|
||||
info = info.replace("</ul>", "")
|
||||
self.developer.template_content = info
|
||||
else:
|
||||
self.developer.template_content = ""
|
||||
self.developer.template_id = "Repository ID"
|
||||
render = self.render("settings/dev/template_test")
|
||||
return web.Response(body=render, content_type="text/html", charset="utf-8")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class DevView(HacsAPI):
|
||||
"""Set HA version view."""
|
||||
|
||||
name = "devview"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
render = self.render(f"settings/dev/{self.postdata['view']}")
|
||||
return web.Response(body=render, content_type="text/html", charset="utf-8")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class SetHAVersionAction(HacsAPI):
|
||||
"""Set HA version action."""
|
||||
|
||||
name = "set_ha_version_action"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
self.common.ha_version = self.postdata["ha_version"]
|
||||
render = self.render("settings/dev/set_ha_version")
|
||||
return web.Response(body=render, content_type="text/html", charset="utf-8")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryInstall(HacsAPI):
|
||||
"""Install repository."""
|
||||
|
||||
name = "repository_install"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
await repository.install()
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/repository/{repository.information.uid}?timestamp={time()}"
|
||||
)
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryUpdate(HacsAPI):
|
||||
"""Update repository."""
|
||||
|
||||
name = "repository_update"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
await repository.update_repository()
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/repository/{repository.information.uid}?timestamp={time()}"
|
||||
)
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryUninstall(HacsAPI):
|
||||
"""Uninstall repository."""
|
||||
|
||||
name = "repository_uninstall"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
await repository.uninstall()
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/overview?timestamp={time()}")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryRemove(HacsAPI):
|
||||
"""Remove repository."""
|
||||
|
||||
name = "repository_remove"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
await repository.remove()
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/settings?timestamp={time()}")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryHide(HacsAPI):
|
||||
"""Hide repository."""
|
||||
|
||||
name = "repository_hide"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
repository.hide = True
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/store?timestamp={time()}")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryUnhide(HacsAPI):
|
||||
"""Unhide repository."""
|
||||
|
||||
name = "repository_unhide"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
repository.hide = False
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/settings?timestamp={time()}")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryBetaHide(HacsAPI):
|
||||
"""Hide Beta repository."""
|
||||
|
||||
name = "repository_beta_hide"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
repository.status.show_beta = False
|
||||
await repository.update_repository()
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/repository/{repository.information.uid}?timestamp={time()}"
|
||||
)
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryBetaShow(HacsAPI):
|
||||
"""Show Beta repository."""
|
||||
|
||||
name = "repository_beta_show"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
repository.status.show_beta = True
|
||||
await repository.update_repository()
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/repository/{repository.information.uid}?timestamp={time()}"
|
||||
)
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoriesReload(HacsAPI):
|
||||
"""Reload repository data."""
|
||||
|
||||
name = "repositories_reload"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
self.hass.async_create_task(self.recuring_tasks_all())
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/settings?timestamp={time()}")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoriesUpgradeAll(HacsAPI):
|
||||
"""Upgrade all repositories."""
|
||||
|
||||
name = "repositories_upgrade_all"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
for repository in self.repositories:
|
||||
if repository.pending_upgrade:
|
||||
await repository.install()
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/settings?timestamp={time()}")
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositoryRegister(HacsAPI):
|
||||
"""Register repository."""
|
||||
|
||||
name = "repository_register"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
repository_name = self.postdata.get("custom_url")
|
||||
repository_type = self.postdata.get("repository_type")
|
||||
|
||||
# Validate data
|
||||
if not repository_name:
|
||||
message = "Repository URL is missing."
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/settings?timestamp={time()}&message={message}"
|
||||
)
|
||||
if repository_type is None:
|
||||
message = "Type is missing for '{}'.".format(repository_name)
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/settings?timestamp={time()}&message={message}"
|
||||
)
|
||||
|
||||
# Stip first part if it's an URL.
|
||||
if "github" in repository_name:
|
||||
repository_name = repository_name.split("github.com/")[-1]
|
||||
|
||||
# Strip whitespace
|
||||
repository_name = repository_name.split()[0]
|
||||
|
||||
# If it still have content, continue.
|
||||
if repository_name != "":
|
||||
if len(repository_name.split("/")) != 2:
|
||||
message = f"""
|
||||
{repository_name} is not a valid format.
|
||||
Correct format is 'https://github.com/DEVELOPER/REPOSITORY'
|
||||
or 'DEVELOPER/REPOSITORY'.
|
||||
"""
|
||||
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/settings?timestamp={time()}&message={message}"
|
||||
)
|
||||
|
||||
is_known_repository = self.is_known(repository_name)
|
||||
if is_known_repository:
|
||||
message = f"'{repository_name}' is already registered, look for it in the store."
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/settings?timestamp={time()}&message={message}"
|
||||
)
|
||||
|
||||
if repository_name in self.common.blacklist:
|
||||
self.common.blacklist.remove(repository_name)
|
||||
|
||||
await self.register_repository(repository_name, repository_type)
|
||||
|
||||
repository = self.get_by_name(repository_name)
|
||||
if repository is not None:
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/repository/{repository.information.uid}?timestamp={time()}"
|
||||
)
|
||||
|
||||
message = f"""
|
||||
Could not add '{repository_name}' with type '{repository_type}' at this time.</br>
|
||||
If you used the correct type, check the log for more details."""
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/settings?timestamp={time()}&message={message}"
|
||||
)
|
||||
|
||||
|
||||
@apiresponse
|
||||
class RepositorySelectTag(HacsAPI):
|
||||
"""Select tag for Repository."""
|
||||
|
||||
name = "repository_select_tag"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
from aiogithubapi import AIOGitHubException
|
||||
from .hacsbase.exceptions import HacsRequirement
|
||||
|
||||
repository = self.get_by_id(self.postdata["repository_id"])
|
||||
if self.postdata["selected_tag"] == repository.releases.last_release:
|
||||
repository.status.selected_tag = None
|
||||
else:
|
||||
repository.status.selected_tag = self.postdata["selected_tag"]
|
||||
|
||||
try:
|
||||
await repository.update_repository()
|
||||
except (AIOGitHubException, HacsRequirement):
|
||||
repository.status.selected_tag = repository.releases.last_release
|
||||
await repository.update_repository()
|
||||
message = "The version {} is not valid for use with HACS.".format(
|
||||
self.postdata["selected_tag"]
|
||||
)
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/repository/{repository.information.uid}?timestamp={time()}&message={message}"
|
||||
)
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(
|
||||
f"/hacsweb/{self.token}/repository/{repository.information.uid}?timestamp={time()}"
|
||||
)
|
||||
|
||||
|
||||
@apiresponse
|
||||
class FrontentMode(HacsAPI):
|
||||
"""Set the frontend mode."""
|
||||
|
||||
name = "frontend_mode"
|
||||
|
||||
async def response(self):
|
||||
"""Response."""
|
||||
self.configuration.frontend_mode = self.postdata["view_type"]
|
||||
await self.data.async_write()
|
||||
return web.HTTPFound(f"/hacsweb/{self.token}/settings?timestamp={time()}")
|
||||
@@ -1,100 +0,0 @@
|
||||
"""Adds config flow for HACS."""
|
||||
# pylint: disable=dangerous-default-value
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
from aiogithubapi import AIOGitHub, AIOGitHubException, AIOGitHubAuthentication
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import DOMAIN
|
||||
from .configuration_schema import hacs_base_config_schema, hacs_config_option_schema
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for HACS."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
self._errors = {}
|
||||
|
||||
async def async_step_user(self, user_input={}):
|
||||
"""Handle a flow initialized by the user."""
|
||||
self._errors = {}
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
if self.hass.data.get(DOMAIN):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
if user_input is not None:
|
||||
if await self._test_token(user_input["token"]):
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
self._errors["base"] = "auth"
|
||||
return await self._show_config_form(user_input)
|
||||
|
||||
return await self._show_config_form(user_input)
|
||||
|
||||
async def _show_config_form(self, user_input):
|
||||
"""Show the configuration form to edit location data."""
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(hacs_base_config_schema(user_input)),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
return HacsOptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_import(self, user_input):
|
||||
"""Import a config entry.
|
||||
Special type of import, we're not actually going to store any data.
|
||||
Instead, we're going to rely on the values that are in config file.
|
||||
"""
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
return self.async_create_entry(title="configuration.yaml", data={})
|
||||
|
||||
async def _test_token(self, token):
|
||||
"""Return true if token is valid."""
|
||||
try:
|
||||
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||
client = AIOGitHub(token, session)
|
||||
await client.get_repo("hacs/org")
|
||||
return True
|
||||
except (AIOGitHubException, AIOGitHubAuthentication):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""HACS config flow options handler."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize HACS options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Manage the options."""
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
hacs_config_option_schema(self.config_entry.options)
|
||||
),
|
||||
)
|
||||
@@ -1,49 +0,0 @@
|
||||
"""HACS Configuration Schemas."""
|
||||
# pylint: disable=dangerous-default-value
|
||||
import voluptuous as vol
|
||||
from .const import LOCALE
|
||||
|
||||
# Configuration:
|
||||
TOKEN = "token"
|
||||
SIDEPANEL_TITLE = "sidepanel_title"
|
||||
SIDEPANEL_ICON = "sidepanel_icon"
|
||||
APPDAEMON = "appdaemon"
|
||||
PYTHON_SCRIPT = "python_script"
|
||||
THEME = "theme"
|
||||
|
||||
# Options:
|
||||
COUNTRY = "country"
|
||||
RELEASE_LIMIT = "release_limit"
|
||||
EXPERIMENTAL = "experimental"
|
||||
|
||||
|
||||
def hacs_base_config_schema(config: dict = {}) -> dict:
|
||||
"""Return a shcema configuration dict for HACS."""
|
||||
if not config:
|
||||
config = {
|
||||
TOKEN: "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
SIDEPANEL_TITLE: "Community",
|
||||
SIDEPANEL_ICON: "mdi:alpha-c-box",
|
||||
APPDAEMON: False,
|
||||
PYTHON_SCRIPT: False,
|
||||
THEME: False,
|
||||
}
|
||||
return {
|
||||
vol.Required(TOKEN, default=config.get(TOKEN)): str,
|
||||
vol.Optional(SIDEPANEL_TITLE, default=config.get(SIDEPANEL_TITLE)): str,
|
||||
vol.Optional(SIDEPANEL_ICON, default=config.get(SIDEPANEL_ICON)): str,
|
||||
vol.Optional(APPDAEMON, default=config.get(APPDAEMON)): bool,
|
||||
vol.Optional(PYTHON_SCRIPT, default=config.get(PYTHON_SCRIPT)): bool,
|
||||
vol.Optional(THEME, default=config.get(THEME)): bool,
|
||||
}
|
||||
|
||||
|
||||
def hacs_config_option_schema(options: dict = {}) -> dict:
|
||||
"""Return a shcema for HACS configuration options."""
|
||||
if not options:
|
||||
options = {COUNTRY: "ALL", RELEASE_LIMIT: 5, EXPERIMENTAL: False}
|
||||
return {
|
||||
vol.Optional("country", default=options.get(COUNTRY)): vol.In(LOCALE),
|
||||
vol.Optional(RELEASE_LIMIT, default=options.get(RELEASE_LIMIT)): int,
|
||||
vol.Optional(EXPERIMENTAL, default=options.get(EXPERIMENTAL)): bool,
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
"""Constants for HACS"""
|
||||
NAME_LONG = "HACS (Home Assistant Community Store)"
|
||||
NAME_SHORT = "HACS"
|
||||
VERSION = "0.16.3"
|
||||
DOMAIN = "hacs"
|
||||
PROJECT_URL = "https://github.com/hacs/integration/"
|
||||
CUSTOM_UPDATER_LOCATIONS = [
|
||||
"{}/custom_components/custom_updater.py",
|
||||
"{}/custom_components/custom_updater/__init__.py",
|
||||
]
|
||||
|
||||
ISSUE_URL = f"{PROJECT_URL}issues"
|
||||
DOMAIN_DATA = f"{NAME_SHORT.lower()}_data"
|
||||
|
||||
ELEMENT_TYPES = ["integration", "plugin"]
|
||||
|
||||
IFRAME = {
|
||||
"title": "Community",
|
||||
"icon": "mdi:alpha-c-box",
|
||||
"url": "/community_overview",
|
||||
"path": "community",
|
||||
"require_admin": True,
|
||||
}
|
||||
|
||||
|
||||
# Messages
|
||||
CUSTOM_UPDATER_WARNING = """
|
||||
This cannot be used with custom_updater.
|
||||
To use this you need to remove custom_updater form {}
|
||||
"""
|
||||
|
||||
DEV_MODE = "You have 'dev' enabled for HACS, this is not intended for regular use, no support will be given if you break something."
|
||||
|
||||
STARTUP = f"""
|
||||
-------------------------------------------------------------------
|
||||
HACS (Home Assistant Community Store)
|
||||
|
||||
Version: {VERSION}
|
||||
This is a custom integration
|
||||
If you have any issues with this you need to open an issue here:
|
||||
https://github.com/hacs/integration/issues
|
||||
-------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
LOCALE = [
|
||||
"ALL",
|
||||
"AF",
|
||||
"AL",
|
||||
"DZ",
|
||||
"AS",
|
||||
"AD",
|
||||
"AO",
|
||||
"AI",
|
||||
"AQ",
|
||||
"AG",
|
||||
"AR",
|
||||
"AM",
|
||||
"AW",
|
||||
"AU",
|
||||
"AT",
|
||||
"AZ",
|
||||
"BS",
|
||||
"BH",
|
||||
"BD",
|
||||
"BB",
|
||||
"BY",
|
||||
"BE",
|
||||
"BZ",
|
||||
"BJ",
|
||||
"BM",
|
||||
"BT",
|
||||
"BO",
|
||||
"BQ",
|
||||
"BA",
|
||||
"BW",
|
||||
"BV",
|
||||
"BR",
|
||||
"IO",
|
||||
"BN",
|
||||
"BG",
|
||||
"BF",
|
||||
"BI",
|
||||
"KH",
|
||||
"CM",
|
||||
"CA",
|
||||
"CV",
|
||||
"KY",
|
||||
"CF",
|
||||
"TD",
|
||||
"CL",
|
||||
"CN",
|
||||
"CX",
|
||||
"CC",
|
||||
"CO",
|
||||
"KM",
|
||||
"CG",
|
||||
"CD",
|
||||
"CK",
|
||||
"CR",
|
||||
"HR",
|
||||
"CU",
|
||||
"CW",
|
||||
"CY",
|
||||
"CZ",
|
||||
"CI",
|
||||
"DK",
|
||||
"DJ",
|
||||
"DM",
|
||||
"DO",
|
||||
"EC",
|
||||
"EG",
|
||||
"SV",
|
||||
"GQ",
|
||||
"ER",
|
||||
"EE",
|
||||
"ET",
|
||||
"FK",
|
||||
"FO",
|
||||
"FJ",
|
||||
"FI",
|
||||
"FR",
|
||||
"GF",
|
||||
"PF",
|
||||
"TF",
|
||||
"GA",
|
||||
"GM",
|
||||
"GE",
|
||||
"DE",
|
||||
"GH",
|
||||
"GI",
|
||||
"GR",
|
||||
"GL",
|
||||
"GD",
|
||||
"GP",
|
||||
"GU",
|
||||
"GT",
|
||||
"GG",
|
||||
"GN",
|
||||
"GW",
|
||||
"GY",
|
||||
"HT",
|
||||
"HM",
|
||||
"VA",
|
||||
"HN",
|
||||
"HK",
|
||||
"HU",
|
||||
"IS",
|
||||
"IN",
|
||||
"ID",
|
||||
"IR",
|
||||
"IQ",
|
||||
"IE",
|
||||
"IM",
|
||||
"IL",
|
||||
"IT",
|
||||
"JM",
|
||||
"JP",
|
||||
"JE",
|
||||
"JO",
|
||||
"KZ",
|
||||
"KE",
|
||||
"KI",
|
||||
"KP",
|
||||
"KR",
|
||||
"KW",
|
||||
"KG",
|
||||
"LA",
|
||||
"LV",
|
||||
"LB",
|
||||
"LS",
|
||||
"LR",
|
||||
"LY",
|
||||
"LI",
|
||||
"LT",
|
||||
"LU",
|
||||
"MO",
|
||||
"MK",
|
||||
"MG",
|
||||
"MW",
|
||||
"MY",
|
||||
"MV",
|
||||
"ML",
|
||||
"MT",
|
||||
"MH",
|
||||
"MQ",
|
||||
"MR",
|
||||
"MU",
|
||||
"YT",
|
||||
"MX",
|
||||
"FM",
|
||||
"MD",
|
||||
"MC",
|
||||
"MN",
|
||||
"ME",
|
||||
"MS",
|
||||
"MA",
|
||||
"MZ",
|
||||
"MM",
|
||||
"NA",
|
||||
"NR",
|
||||
"NP",
|
||||
"NL",
|
||||
"NC",
|
||||
"NZ",
|
||||
"NI",
|
||||
"NE",
|
||||
"NG",
|
||||
"NU",
|
||||
"NF",
|
||||
"MP",
|
||||
"NO",
|
||||
"OM",
|
||||
"PK",
|
||||
"PW",
|
||||
"PS",
|
||||
"PA",
|
||||
"PG",
|
||||
"PY",
|
||||
"PE",
|
||||
"PH",
|
||||
"PN",
|
||||
"PL",
|
||||
"PT",
|
||||
"PR",
|
||||
"QA",
|
||||
"RO",
|
||||
"RU",
|
||||
"RW",
|
||||
"RE",
|
||||
"BL",
|
||||
"SH",
|
||||
"KN",
|
||||
"LC",
|
||||
"MF",
|
||||
"PM",
|
||||
"VC",
|
||||
"WS",
|
||||
"SM",
|
||||
"ST",
|
||||
"SA",
|
||||
"SN",
|
||||
"RS",
|
||||
"SC",
|
||||
"SL",
|
||||
"SG",
|
||||
"SX",
|
||||
"SK",
|
||||
"SI",
|
||||
"SB",
|
||||
"SO",
|
||||
"ZA",
|
||||
"GS",
|
||||
"SS",
|
||||
"ES",
|
||||
"LK",
|
||||
"SD",
|
||||
"SR",
|
||||
"SJ",
|
||||
"SZ",
|
||||
"SE",
|
||||
"CH",
|
||||
"SY",
|
||||
"TW",
|
||||
"TJ",
|
||||
"TZ",
|
||||
"TH",
|
||||
"TL",
|
||||
"TG",
|
||||
"TK",
|
||||
"TO",
|
||||
"TT",
|
||||
"TN",
|
||||
"TR",
|
||||
"TM",
|
||||
"TC",
|
||||
"TV",
|
||||
"UG",
|
||||
"UA",
|
||||
"AE",
|
||||
"GB",
|
||||
"US",
|
||||
"UM",
|
||||
"UY",
|
||||
"UZ",
|
||||
"VU",
|
||||
"VE",
|
||||
"VN",
|
||||
"VG",
|
||||
"VI",
|
||||
"WF",
|
||||
"EH",
|
||||
"YE",
|
||||
"ZM",
|
||||
"ZW",
|
||||
]
|
||||
@@ -1,44 +0,0 @@
|
||||
"""HACS Startup constrains."""
|
||||
# pylint: disable=bad-continuation
|
||||
import os
|
||||
import json
|
||||
|
||||
from .const import CUSTOM_UPDATER_LOCATIONS, CUSTOM_UPDATER_WARNING
|
||||
from .helpers.misc import version_is_newer_than_version
|
||||
|
||||
|
||||
def check_constans(hacs):
|
||||
"""Check HACS constrains."""
|
||||
if not constrain_custom_updater(hacs):
|
||||
return False
|
||||
if not constrain_version(hacs):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def constrain_custom_updater(hacs):
|
||||
"""Check if custom_updater exist."""
|
||||
for location in CUSTOM_UPDATER_LOCATIONS:
|
||||
if os.path.exists(location.format(hacs.system.config_path)):
|
||||
msg = CUSTOM_UPDATER_WARNING.format(
|
||||
location.format(hacs.system.config_path)
|
||||
)
|
||||
hacs.logger.critical(msg)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def constrain_version(hacs):
|
||||
"""Check if the version is valid."""
|
||||
with open(
|
||||
f"{hacs.system.config_path}/custom_components/hacs/manifest.json", "r"
|
||||
) as read:
|
||||
manifest = json.loads(read.read())
|
||||
|
||||
# Check if HA is the required version.
|
||||
if version_is_newer_than_version(manifest["homeassistant"], hacs.system.ha_version):
|
||||
hacs.logger.critical(
|
||||
f"You need HA version {manifest['homeassistant']} or newer to use this integration."
|
||||
)
|
||||
return False
|
||||
return True
|
||||
@@ -1,49 +0,0 @@
|
||||
"""Deprecated functions, should be fine to remove."""
|
||||
|
||||
|
||||
async def add_services(hacs):
|
||||
"""Add services."""
|
||||
# Service registration
|
||||
async def service_hacs_install(call):
|
||||
"""Install a repository."""
|
||||
repository = str(call.data["repository"])
|
||||
if repository not in hacs().store.repositories:
|
||||
hacs.logger.error("%s is not a konwn repository!", repository)
|
||||
return
|
||||
repository = hacs().store.repositories[repository]
|
||||
await repository.install()
|
||||
|
||||
async def service_hacs_register(call):
|
||||
"""register a repository."""
|
||||
repository = call.data["repository"]
|
||||
repository_type = call.data["repository_type"]
|
||||
if await hacs().is_known_repository(repository):
|
||||
hacs.logger.error("%s is already a konwn repository!", repository)
|
||||
return
|
||||
await hacs().register_new_repository(repository_type, repository)
|
||||
|
||||
async def service_hacs_load(call):
|
||||
"""register a repository."""
|
||||
from homeassistant.loader import async_get_custom_components
|
||||
|
||||
del hacs.hass.data["custom_components"]
|
||||
await async_get_custom_components(hacs.hass)
|
||||
|
||||
hacs.hass.services.async_register("hacs", "install", service_hacs_install)
|
||||
hacs.hass.services.async_register("hacs", "register", service_hacs_register)
|
||||
hacs.hass.services.async_register("hacs", "load", service_hacs_load)
|
||||
|
||||
|
||||
# This should probably be in a dedicated test "package"
|
||||
async def test_repositories(hacs):
|
||||
"""Test repositories."""
|
||||
await hacs().register_repository("ludeeus/theme-hacs", "theme")
|
||||
await hacs().register_repository("ludeeus/ps-hacs", "python_script")
|
||||
await hacs().register_repository("ludeeus/integration-hacs", "integration")
|
||||
await hacs().register_repository(
|
||||
"rgruebel/ha_zigbee2mqtt_networkmap", "integration"
|
||||
)
|
||||
await hacs().register_repository("ludeeus/ad-hacs", "appdaemon")
|
||||
await hacs().register_repository("jonkristian/entur-card", "plugin") # Dist
|
||||
await hacs().register_repository("kalkih/mini-media-player", "plugin") # Release
|
||||
await hacs().register_repository("custom-cards/monster-card", "plugin") # root
|
||||
Binary file not shown.
@@ -1,504 +0,0 @@
|
||||
.yaml {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
display: inline-flex;
|
||||
width: calc(100% - 20px);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.hacslist {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.hacs-card-container {
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.hacs-overview-container {
|
||||
width: 95%;
|
||||
margin-left: 2.5%;
|
||||
margin-right: 2.5%;
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hacs-form {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.hacs-table {
|
||||
background-color: var(--paper-card-background-color);
|
||||
}
|
||||
|
||||
.hacs-content {
|
||||
position: absolute;
|
||||
top: 49px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.hacs-card {
|
||||
height: 100%;
|
||||
background-color: var(--paper-card-background-color) !important;
|
||||
padding: 3px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.overview {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progressbar {
|
||||
margin-left: 2.5%;
|
||||
margin-right: 2.5%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hacs-card-title {
|
||||
font-size: medium;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
tr.hacs-table-row.repository {
|
||||
border-bottom: 0px;
|
||||
display: table-row-group;
|
||||
}
|
||||
|
||||
td.repository {
|
||||
width: 50%;
|
||||
padding: 0px;
|
||||
}
|
||||
td.repository.text {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
|
||||
select.hacs-select.repository {
|
||||
width: 100%;
|
||||
}
|
||||
.hacs-card-content {
|
||||
color: var(--secondary-text-color);
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.card-status {
|
||||
font-size: 18px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.hacs-card-standalone {
|
||||
background-color: var(--paper-card-background-color) !important;
|
||||
color: var(--primary-text-color);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.installed {
|
||||
color: var(--hacs-status-installed);
|
||||
}
|
||||
|
||||
.pending-restart {
|
||||
color: var(--hacs-status-pending-restart);
|
||||
}
|
||||
|
||||
.pending-upgrade {
|
||||
color: var(--hacs-status-pending-update);
|
||||
}
|
||||
|
||||
.default {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.hacs-nav {
|
||||
background-color: var(--primary-color) !important;
|
||||
position: fixed;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.disabledaction {
|
||||
cursor: default;
|
||||
color: var(--primary-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.hacs-bar {
|
||||
background-color: var(--primary-color) !important;
|
||||
}
|
||||
.hacs-bar-background {
|
||||
background-color: var(--dark-primary-color) !important;
|
||||
}
|
||||
|
||||
.errorview {
|
||||
display: block;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.hacscolor {
|
||||
background-color: var(--paper-card-background-color) !important;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.hacsbutton {
|
||||
margin: 3%;
|
||||
background-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.hacsadminbutton {
|
||||
margin: 1%;
|
||||
background-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--primary-background-color) !important;
|
||||
color: var(--primary-text-color);
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="text"]:not(.browser-default):focus:not([readonly]) {
|
||||
border-bottom: 1px solid var(--primary-color);
|
||||
-webkit-box-shadow: 0 1px 0 0 var(--primary-color);
|
||||
box-shadow: 0 1px 0 0 var(--primary-color);
|
||||
}
|
||||
|
||||
.dropdown-list-item {
|
||||
color: var(--primary-color) !important;
|
||||
background-color: var(--paper-card-background-color) !important;
|
||||
}
|
||||
|
||||
a.dropdown-list-item:hover {
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.actionlink {
|
||||
color: var(--primary-color) !important
|
||||
}
|
||||
|
||||
.uninstall {
|
||||
float: right;
|
||||
color: var(--google-red-500) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media all and (max-width: 550px) {
|
||||
select.hacs-select.custom_repository {
|
||||
width: 100%;
|
||||
}
|
||||
input#custom_url {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.actionlink {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
display: block;
|
||||
font-size: x-large;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.uninstall {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
.minigrid {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.card-action a:hover {
|
||||
opacity: 0.6 !important;
|
||||
}
|
||||
|
||||
.codeinfo {
|
||||
opacity: 0.6 !important;
|
||||
}
|
||||
|
||||
.hacs-select {
|
||||
display: initial !important;
|
||||
margin-left: 1%;
|
||||
width: 20%;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.hacs-badge {
|
||||
background-color: var(--hacs-badge-color);
|
||||
margin-right: 1%;
|
||||
font-size: 12px;
|
||||
padding: 0px 4px 2px 4px;
|
||||
color: var(--hacs-badge-text-color);
|
||||
-moz-border-radius: 10px 10px 10px 10px;
|
||||
border-radius: 10px 10px 10px 10px;
|
||||
}
|
||||
|
||||
tr.hacs-table-row:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* color vars*/
|
||||
:root {
|
||||
--hacs-status-installed: #126e15;
|
||||
--hacs-status-pending-restart: #a70000;
|
||||
--hacs-status-pending-update: #ffab40;
|
||||
--hacs-status-default: var(--primary-text-color);
|
||||
--hacs-badge-color: var(--primary-color);
|
||||
--hacs-badge-text-color: var(--primary-text-color);
|
||||
--primary-color: #03a9f4;
|
||||
--primary-text-color: #000000;
|
||||
--secondary-text-color: #424141;
|
||||
--primary-background-color: #fafafa;
|
||||
--paper-card-background-color: #ffffff;
|
||||
--dark-primary-color: #00405d;
|
||||
--google-red-500: #db4437;
|
||||
}
|
||||
|
||||
/* hug screens*/
|
||||
@media all and (min-width: 2000px) {
|
||||
.hacs-card-container {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* even bigger screens*/
|
||||
@media all and (min-width: 1500px) and (max-width: 1999px) {
|
||||
.hacs-card-container {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* bigger screens*/
|
||||
@media all and (min-width: 1000px) and (max-width: 1499px) {
|
||||
.hacs-card-container {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* big screens */
|
||||
@media all and (min-width: 550px) and (max-width: 999px) {
|
||||
.hacs-card-container {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
|
||||
#main::-webkit-scrollbar-track
|
||||
{
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#main::-webkit-scrollbar
|
||||
{
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
#main::-webkit-scrollbar-thumb
|
||||
{
|
||||
border-radius: 10px;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* smal screens */
|
||||
@media all and (max-width: 550px) {
|
||||
body {
|
||||
overflow: scroll !important;
|
||||
}
|
||||
|
||||
.minigrid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.smal-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hacs-content {
|
||||
overflow: scroll !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* laoder */
|
||||
|
||||
*.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.loading{
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(16, 16, 16, 0.5);
|
||||
}
|
||||
|
||||
@-webkit-keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes uil-ring-anim {
|
||||
0% {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.uil-ring-css {
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
.uil-ring-css > div {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
border-radius: 80px;
|
||||
box-shadow: 0 6px 0 0 var(--primary-color);
|
||||
-ms-animation: uil-ring-anim 1s linear infinite;
|
||||
-moz-animation: uil-ring-anim 1s linear infinite;
|
||||
-webkit-animation: uil-ring-anim 1s linear infinite;
|
||||
-o-animation: uil-ring-anim 1s linear infinite;
|
||||
animation: uil-ring-anim 1s linear infinite;
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copy active HA theme
|
||||
document.getElementsByTagName("html").item(0).setAttribute("style", parent.document.getElementsByTagName("html").item(0).style.cssText)
|
||||
|
||||
// Copy yhe content of the Lovelace example to the clipboard.
|
||||
function CopyToLovelaceExampleToClipboard() {
|
||||
window.getSelection().selectAllChildren(document.getElementById("LovelaceExample"));
|
||||
document.execCommand("copy");
|
||||
document.getSelection().empty()
|
||||
document.getElementById('lovelacecopy').style.color = 'forestgreen';
|
||||
}
|
||||
|
||||
// Show progress bar
|
||||
function ShowProgressBar() {
|
||||
document.getElementById('progressbar').style.display = 'block';
|
||||
}
|
||||
|
||||
// Hard reloading
|
||||
function HardReload() {
|
||||
parent.location.reload(true)
|
||||
}
|
||||
|
||||
// Searchbar
|
||||
function Search() {
|
||||
var input = document.getElementById("Search");
|
||||
if (input) {
|
||||
var filter = input.value.toLowerCase();
|
||||
var nodes = document.getElementsByClassName('hacs-card');
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].innerHTML.toLowerCase().includes(filter)) {
|
||||
nodes[i].style.display = "block";
|
||||
} else {
|
||||
nodes[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
var nodes = document.getElementsByClassName('hacs-table-row');
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].innerHTML.toLowerCase().includes(filter)) {
|
||||
nodes[i].style.display = "table-row";
|
||||
} else {
|
||||
nodes[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Dropdown
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var elems = document.querySelectorAll('.dropdown-trigger');
|
||||
M.Dropdown.init(elems, { hover: false, constrainWidth: false });
|
||||
});
|
||||
|
||||
// Modal
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var elems = document.querySelectorAll('.modal');
|
||||
M.Modal.init(elems, {});
|
||||
});
|
||||
|
||||
|
||||
// Loader
|
||||
function toggleLoading() {
|
||||
var loadingOverlay = document.querySelector('.loading');
|
||||
loadingOverlay.classList.remove('hidden')
|
||||
document.activeElement.blur();
|
||||
}
|
||||
|
||||
|
||||
// Check if we can reload
|
||||
function sleep(time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
function CheckIfWeCanReload() {
|
||||
var data = true;
|
||||
const hacsrequest = new XMLHttpRequest()
|
||||
hacsrequest.open('GET', '/hacs_task', true)
|
||||
hacsrequest.onload = function () {
|
||||
data = JSON.parse(this.response)
|
||||
data = data["task"]
|
||||
if (!data) {
|
||||
console.log("Background task is no longer running, reloading in 5s...")
|
||||
sleep(5000).then(() => {
|
||||
location.reload()
|
||||
});
|
||||
}
|
||||
}
|
||||
hacsrequest.send()
|
||||
}
|
||||
|
||||
function IsTaskRunning() {
|
||||
let retval = false;
|
||||
let disp = document.getElementsByClassName("progress")
|
||||
if (disp) {
|
||||
disp = disp.item(0).style.display;
|
||||
if (disp == "block") {
|
||||
retval = true
|
||||
} else {
|
||||
retval = false
|
||||
}
|
||||
}
|
||||
return retval
|
||||
}
|
||||
|
||||
window.setInterval(function () {
|
||||
var running = false;
|
||||
running = IsTaskRunning();
|
||||
if (running) {
|
||||
CheckIfWeCanReload();
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
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,51 +0,0 @@
|
||||
{% set progressbar = 'block' if hacs.system.status.background_task else 'none' %}
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/hacsweb/{{ hacs.token }}/static/materialize.min.css.gz">
|
||||
<link rel="stylesheet" href="/hacsweb/{{ hacs.token }}/static/all.min.css.gz">
|
||||
<script src="/hacsweb/{{ hacs.token }}/static/materialize.min.js.gz"></script>
|
||||
<link rel="stylesheet" href="/hacsweb/{{ hacs.token }}/static/static/hacs.css">
|
||||
<script src="/hacsweb/{{ hacs.token }}/static/hacs.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="navbar-fixed">
|
||||
<nav class="nav-extended hacs-nav">
|
||||
<div class="nav-content">
|
||||
<ul class="right tabs tabs-transparent">
|
||||
<li class="tab {{ 'active' if location == 'overview' }}"><a href="/hacsweb/{{ hacs.token }}/overview?timestamp={{ timestamp }}">Overview</a></li>
|
||||
<li class="tab {{ 'active' if location == 'store' }}"><a href="/hacsweb/{{ hacs.token }}/store?timestamp={{ timestamp }}">Store</a></li>
|
||||
<li class="tab right {{ 'active' if location == 'settings' }}"><a href="/hacsweb/{{ hacs.token }}/settings?timestamp={{ timestamp }}">Settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="main" class="hacs-content">
|
||||
<div class="progressbar" style="display: {{ progressbar }}">
|
||||
<p>Background task running, this page will reload when it's done.</p>
|
||||
<div class="progress hacs-bar-background" id="progressbar">
|
||||
<div class="indeterminate hacs-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading hidden">
|
||||
<div class='uil-ring-css' style='transform:scale(0.79);'>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
{% if message %}
|
||||
{% include 'message.html' with context %}
|
||||
{% endif %}
|
||||
{% set hacs_repo = hacs.get_by_name("hacs/integration") %}
|
||||
{% if hacs_repo.pending_restart %}
|
||||
{% set message = "The frontend of HACS has been disabled until you restart Home Assistant." %}
|
||||
{% include 'message.html' with context %}
|
||||
{% else %}
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,36 +0,0 @@
|
||||
<div class='hacs-overview-container'>
|
||||
<h5>{{ displaytype }}</h5>
|
||||
<div class='hacs-card-container'>
|
||||
{% for repository in repositories.repositories %}
|
||||
<a href="/hacsweb/{{ hacs.token }}/repository/{{ repository.information.uid }}?timestamp={{ timestamp }}"
|
||||
class="hacs-card" onclick="toggleLoading()">
|
||||
<div class="hacs-card overview">
|
||||
<div id="statusicon">
|
||||
|
||||
</div>
|
||||
<div id="title">
|
||||
<input type="hidden" name="author" value="{{repository.information.authors}}">
|
||||
<input type="hidden" name="topics" value="{{repository.information.topics}}">
|
||||
<input type="hidden" name="full_name" value="{{repository.information.full_name}}">
|
||||
<span class="hacs-card-title">
|
||||
{% if location == 'store' and repository.status.new %}
|
||||
<span title="{{ repository.display_status_description }}" class='hacs-badge'>NEW</span>
|
||||
{% else %}
|
||||
<i title="{{ repository.display_status_description }}"
|
||||
class='fas fa-cube card-status {{repository.display_status}}'></i>
|
||||
{% endif %}
|
||||
{{repository.display_name}}
|
||||
</span>
|
||||
</div>
|
||||
<div id="description">
|
||||
<span class="hacs-card-content">
|
||||
<p>{{repository.information.description}}</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</br>
|
||||
</br>
|
||||
@@ -1,47 +0,0 @@
|
||||
{% set repositories = namespace(repositories=[]) %}
|
||||
{% for repository in hacs.sorted_by_name %}
|
||||
{% if repository.status.new %}
|
||||
{% set repositories.repositories = repositories.repositories+[repository] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if repositories.repositories %}
|
||||
<div class='hacs-overview-container'>
|
||||
<h5>NEW REPOSITORIES (ALL)</h5>
|
||||
<form action="/hacsapi/{{ hacs.token }}/remove_new_flag" name="remove_new_flag" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<input type="hidden" name="return" value="store">
|
||||
<a class="waves-effect waves-light btn modal-trigger hacsbutton" href="#"
|
||||
onclick="toggleLoading();document.getElementsByName('remove_new_flag')[0].submit()" style="width: 95%;">
|
||||
Remove the NEW flag for all new items
|
||||
</a>
|
||||
</form>
|
||||
<div class='hacs-card-container'>
|
||||
{% for repository in repositories.repositories %}
|
||||
<a href="/hacsweb/{{ hacs.token }}/repository/{{ repository.information.uid }}?timestamp={{ timestamp }}"
|
||||
class="hacs-card" onclick="toggleLoading()">
|
||||
<div class="hacs-card overview">
|
||||
<div id="statusicon">
|
||||
|
||||
</div>
|
||||
<div id="title">
|
||||
<span class="hacs-card-title">
|
||||
<span class="hacs-badge">
|
||||
{{repository.information.category | upper}}
|
||||
</span>
|
||||
</br></br>
|
||||
{{repository.display_name}}
|
||||
</span>
|
||||
</div>
|
||||
<div id="description">
|
||||
<span class="hacs-card-content">
|
||||
<p>{{repository.information.description}}</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</br>
|
||||
</br>
|
||||
{% endif %}
|
||||
@@ -1,13 +0,0 @@
|
||||
<div class='container'>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card-panel orange darken-4">
|
||||
<div class="card-content white-text">
|
||||
<span>
|
||||
{{message}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,33 +0,0 @@
|
||||
<div id="modal_upgrade_all" class="modal hacscolor">
|
||||
<div class="modal-content">
|
||||
<h5>Pending Upgrades</h5>
|
||||
{% for repository in hacs.repositories %}
|
||||
{% if repository.pending_upgrade %}
|
||||
<p>- {{repository.information.name}} (
|
||||
{{repository.display_installed_version}}
|
||||
->
|
||||
{{repository.display_available_version}}
|
||||
)</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</br>
|
||||
<p>
|
||||
Be careful using this feature, elements may contain breaking changes,
|
||||
make sure you read the release notes for all the elements in the list above.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer hacscolor">
|
||||
{% if hacs.system.status.background_task %}
|
||||
<p>Background task is running, batch upgrade is disabled.</p>
|
||||
{% else %}
|
||||
<form action="/hacsapi/{{ hacs.token }}/repositories_upgrade_all" name="upgrade_all" method="post"
|
||||
accept-charset="utf-8" enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<a href="#" onclick="toggleLoading();document.getElementsByName('upgrade_all')[0].submit()"
|
||||
class='waves-effect waves-light btn hacsbutton'
|
||||
style="background-color: var(--google-red-500) !important; font-weight: bold;">
|
||||
UPGRADE ALL
|
||||
</a>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
<div id="haversion" class="modal hacscolor">
|
||||
<div class="modal-content">
|
||||
<h5>Unsupported Home Assistant version</h5>
|
||||
<p>
|
||||
You have version '{{hacs.system.ha_version}}' of Home Assistant, but version
|
||||
'{{repository.display_available_version}}' of '{{repository.display_name}}' require version
|
||||
'{{repository.repository_manifest.homeassistant}}' of Home Assistant, installation and upgrades are disabled
|
||||
for this
|
||||
integration untill you upgrade Home Assistant.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,77 +0,0 @@
|
||||
<!-- Used for both overview and store-->
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% if location == 'store' %}
|
||||
<div class='hacs-overview-container'>
|
||||
<input type="text" id="Search" onkeyup="Search()" placeholder="Please enter a search term.." title="Type in a name"
|
||||
autofocus style="color: var(--primary-text-color)">
|
||||
</div>
|
||||
{% if hacs.configuration.frontend_mode == "Grid" %}
|
||||
{% include 'grid_new.html' with context %}
|
||||
{% else %}
|
||||
{% include 'table_new.html' with context %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% for type in hacs.common.categories %}
|
||||
|
||||
{% if type == 'appdaemon' %}
|
||||
{% set displaytype = 'APPDAEMON APPS' %}
|
||||
{% elif type == 'python_script' %}
|
||||
{% set displaytype = 'PYTHON SCRIPTS' %}
|
||||
{% else %}
|
||||
{% set displaytype = (type + 's') | upper %}
|
||||
{% endif %}
|
||||
|
||||
{% set repositories = namespace(repositories=[]) %}
|
||||
|
||||
{% for repository in hacs.sorted_by_name %}
|
||||
{% set repo = namespace(include=True) %}
|
||||
|
||||
{% if repository.status.hide and repo.include %}
|
||||
{% set repo.include = False %}
|
||||
{% endif %}
|
||||
|
||||
{% if repository.information.category != type and repo.include %}
|
||||
{% set repo.include = False %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if location == 'overview' %}
|
||||
{% if not repository.status.installed and repo.include %}
|
||||
{% set repo.include = False %}
|
||||
{% if repository.information.uid == "172733314" %}
|
||||
{% set repo.include = True %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if repository.information.uid == "172733314" %}
|
||||
{% set repo.include = False %}
|
||||
{% endif %}
|
||||
{% if hacs.configuration.country and repo.include %}
|
||||
{% if repository.repository_manifest %}
|
||||
{% if repository.repository_manifest.country %}
|
||||
{% if hacs.configuration.country not in repository.repository_manifest.country %}
|
||||
{% set repo.include = False %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if repo.include %}
|
||||
{% set repositories.repositories = repositories.repositories+[repository] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if repositories.repositories %}
|
||||
{% if hacs.configuration.frontend_mode == "Grid" %}
|
||||
{% include 'grid.html' with context %}
|
||||
{% else %}
|
||||
{% include 'table.html' with context %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,93 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
{% if repository.pending_restart %}
|
||||
{% set message = "You need to restart (and potentially reconfigure) Home Assistant, for your last operation to be loaded." %}
|
||||
{% endif %}
|
||||
|
||||
{% if repository.information.category == "integration" and repository.status.first_install and repository.status.installed and repository.config_flow %}
|
||||
{% set message = "
|
||||
<b>Good news!</b></br>
|
||||
This integration supports config flows!</br>
|
||||
This means that you can go to 'Configuration' --> 'Integrations' --> '+' to configure it.</br>
|
||||
The best part if you do that, is that you do <b>not</b> need to restart Home Assistant.</br></br>
|
||||
<i>You may need to clear the cache on the integrations page before it shows in the list (+).</i>"%}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if message %}
|
||||
{% include 'message.html' with context %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card hacscolor">
|
||||
<div class="card-content">
|
||||
<span class="card-title">
|
||||
<b>{{repository.display_name}}</b>
|
||||
<a class='dropdown-trigger btn right' href='#' data-target='dropdown1'
|
||||
style="background-color: var(--primary-color); padding-top: 8px; height: 48px">
|
||||
<i class="fas fa-bars"></i>
|
||||
</a>
|
||||
|
||||
{% include 'repository/menu.html' with context %}
|
||||
</span>
|
||||
<p>{{repository.information.description}}</p></br>
|
||||
<table class="hacs-table">
|
||||
{% if repository.status.installed %}
|
||||
<tr class="hacs-table-row repository">
|
||||
<td class="repository">
|
||||
<b>Installed {{repository.display_version_or_commit}}:</b>
|
||||
</td>
|
||||
<td class="repository text">
|
||||
{{repository.display_installed_version}}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr class="hacs-table-row repository">
|
||||
{% if repository.releases.published_tags %}
|
||||
{% include 'repository/versionselect.html' with context %}
|
||||
{% else %}
|
||||
<td class="repository">
|
||||
<b>Available {{repository.display_version_or_commit}}:</b>
|
||||
</td>
|
||||
<td class="repository text">
|
||||
{{repository.display_available_version}}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% if repository.status.selected_tag == repository.information.default_branch %}
|
||||
<tr class="hacs-table-row repository">
|
||||
<td class="repository">
|
||||
<b>Last updated:</b>
|
||||
</td>
|
||||
<td class="repository text">
|
||||
{{repository.information.last_updated}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% include 'repository/authors.html' with context %}
|
||||
</table>
|
||||
</div>
|
||||
{% include 'repository/buttons.html' with context %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="card hacscolor">
|
||||
<div class="card-content" style="margin-top: -20px;">
|
||||
{{ repository.information.additional_info }}
|
||||
{% include 'repository/note.html' with context %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,27 +0,0 @@
|
||||
{% if repository.information.authors %}
|
||||
{% if repository.information.category == "integration" %}
|
||||
<tr class="hacs-table-row repository">
|
||||
<td class="repository">
|
||||
<b>Author(s):</b>
|
||||
</td>
|
||||
<td class="repository text">
|
||||
{% for author in repository.information.authors %}
|
||||
{% if "@" in author %}
|
||||
{% set author = author.split("@")[-1] %}
|
||||
{% endif %}
|
||||
<a rel='noreferrer' href='https://github.com/{{author}}' target='_blank'
|
||||
style='color: var(--primary-color) !important; margin: 2'> @{{author}}</a>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="hacs-table-row repository">
|
||||
<td class="repository">
|
||||
<b>Author:</b>
|
||||
</td>
|
||||
<td class="repository text">
|
||||
{{repository.information.authors}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -1,50 +0,0 @@
|
||||
{% if not repository.can_install %}
|
||||
{% include 'modal/wrong_ha_version.html' with context %}
|
||||
{% endif %}
|
||||
|
||||
<div class="card-action">
|
||||
{% if repository.can_install %}
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_install" name="install" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<a class="actionlink" href="#" onclick="toggleLoading();document.getElementsByName('install')[0].submit()">
|
||||
{{repository.main_action}}
|
||||
</a>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="modal-trigger actionlink" href="#haversion">
|
||||
{{repository.main_action}}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if repository.pending_upgrade and repository.display_version_or_commit == "version" %}
|
||||
<a class="actionlink" rel='noreferrer'
|
||||
href='https://github.com/{{repository.information.full_name}}/releases/{{repository.available_version}}'
|
||||
target='_blank'>
|
||||
CHANGELOG
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a class="actionlink" rel='noreferrer' href='https://github.com/{{repository.information.full_name}}' target='_blank'>
|
||||
repository
|
||||
</a>
|
||||
|
||||
{% if repository.status.installed and repository.information.category == "plugin" %}
|
||||
{% set name = repository.information.full_name.split("/")[-1] %}
|
||||
<a class="actionlink" href='/community_plugin/{{name}}/{{repository.information.file_name}}' target='_blank'>
|
||||
OPEN PLUGIN
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if repository.status.installed and repository.information.uid != "172733314" %}
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_uninstall" name="uninstall" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<a class="actionlink uninstall" href='#'
|
||||
onclick="toggleLoading();document.getElementsByName('uninstall')[0].submit()">
|
||||
UNINSTALL
|
||||
</a>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
@@ -1,61 +0,0 @@
|
||||
<ul id='dropdown1' class='dropdown-content'>
|
||||
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_update" name="update" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<li>
|
||||
<a class="dropdown-list-item" href='#' onclick="toggleLoading();document.getElementsByName('update')[0].submit()">
|
||||
Reload
|
||||
</a>
|
||||
</li>
|
||||
</form>
|
||||
|
||||
{% if repository.display_version_or_commit == "version" %}
|
||||
{% set hide_show = "hide" if repository.status.show_beta else "show" %}
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_beta_{{hide_show}}" name="beta" method="post"
|
||||
accept-charset="utf-8" enctype="application/x-www-form-urlencoded">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<li>
|
||||
<a class="dropdown-list-item" href='#' onclick="toggleLoading();document.getElementsByName('beta')[0].submit()">
|
||||
{{hide_show | title}} beta
|
||||
</a>
|
||||
</li>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if not repository.status.installed %}
|
||||
{% if not repository.custom %}
|
||||
{% if repository.information.uid != "172733314" %}
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_hide" name="repository_hide" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<li>
|
||||
<a class="dropdown-list-item" href='#'
|
||||
onclick="toggleLoading();document.getElementsByName('repository_hide')[0].submit()">
|
||||
Hide
|
||||
</a>
|
||||
</li>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<a class="dropdown-list-item" rel='noreferrer'
|
||||
href="https://github.com/{{repository.information.full_name}}/issues/" target="_blank">
|
||||
Open issue
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
{% if repository.information.uid != "172733314" %}
|
||||
<li>
|
||||
<a class="dropdown-list-item" rel='noreferrer'
|
||||
href="https://github.com/hacs/integration/issues/new?title={{repository.information.full_name}}&labels=flag&assignee=ludeeus&template=flag.md"
|
||||
target="_blank">
|
||||
Flag this
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
@@ -1,42 +0,0 @@
|
||||
{% if repository.additional_info %}
|
||||
</br>
|
||||
<hr></br>
|
||||
{% endif %}
|
||||
|
||||
{% if repository.information.category == "appdaemon" %}
|
||||
|
||||
<i>
|
||||
When installed, this will be located in '{{repository.content.path.local}}',
|
||||
you still need to add it to your 'apps.yaml' file.
|
||||
</i>
|
||||
|
||||
{% elif repository.information.category == "integration" %}
|
||||
|
||||
<i>
|
||||
When installed, this will be located in '{{repository.content.path.local}}',
|
||||
you still need to add it to your 'configuration.yaml' file.
|
||||
</i>
|
||||
|
||||
{% elif repository.information.category == "plugin" %}
|
||||
{% set name = repository.information.full_name.split("/")[-1] %}
|
||||
|
||||
<i>
|
||||
When installed, this will be located in '{{repository.content.path.local }}',
|
||||
you still need to add it to your lovelace configuration ('ui-lovelace.yaml' or the raw UI config editor).
|
||||
</i>
|
||||
</br></br>
|
||||
<i>
|
||||
When you add this to your configuration use this:
|
||||
</i></br>
|
||||
<pre id="LovelaceExample" class="yaml">
|
||||
- url: /community_plugin/{{name}}/{{repository.information.file_name}}
|
||||
{%- if repository.information.javascript_type %}
|
||||
type: {{repository.information.javascript_type}}
|
||||
{%- else %}
|
||||
type: Could not determine the type of this plugin, check the repository.
|
||||
{%- endif -%}
|
||||
</pre>
|
||||
<a title="Copy content to clipboard" id="lovelacecopy" onclick="CopyToLovelaceExampleToClipboard()">
|
||||
<i class="fa fa-copy"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -1,24 +0,0 @@
|
||||
{% set tags = repository.releases.published_tags | sort(True) %}
|
||||
{% set tags = tags + [repository.information.default_branch] %}
|
||||
|
||||
|
||||
{% if repository.status.selected_tag %}
|
||||
{% set selected_tag = repository.status.selected_tag %}
|
||||
{% else %}
|
||||
{% set selected_tag = repository.versions.available %}
|
||||
{% endif %}
|
||||
<td class="repository">
|
||||
<b>Available versions:</b>
|
||||
</td>
|
||||
<td class="repository">
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_select_tag" name="selected_tag" method="post"
|
||||
accept-charset="utf-8" enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<select name="selected_tag" class="hacs-select repository"
|
||||
onchange="toggleLoading();document.getElementsByName('selected_tag')[0].submit()">
|
||||
{% for tag in tags %}
|
||||
<option {{'selected' if selected_tag == tag else ''}} class='hacscolor' value='{{tag}}'>{{tag}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
@@ -1,20 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% if hacs.configuration.dev %}
|
||||
{% set message = "You have 'dev' enabled for HACS, this is not intended for regular use, no support will be given if you break something." %}
|
||||
{% include 'message.html' with context %}
|
||||
{% endif %}
|
||||
|
||||
{% include 'modal/upgrade_all.html' with context %}
|
||||
|
||||
|
||||
{% include 'settings/hacs_info.html' with context %}
|
||||
{% include 'settings/buttons.html' with context %}
|
||||
{% include 'settings/custom_repositories.html' with context %}
|
||||
{% include 'settings/hidden_repositories.html' with context %}
|
||||
|
||||
|
||||
{% if hacs.configuration.dev and not hacs.system.status.background_task %}
|
||||
{% include 'settings/developer.html' with context %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,26 +0,0 @@
|
||||
<div class='hacs-overview-container minigrid'>
|
||||
|
||||
<form action="/hacsapi/{{ hacs.token }}/repositories_reload" name="reload" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<a class="waves-effect waves-light btn modal-trigger hacsbutton" href="#"
|
||||
onclick="toggleLoading();document.getElementsByName('reload')[0].submit()">
|
||||
RELOAD DATA
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<a href="#modal_upgrade_all" class="waves-effect waves-light btn modal-trigger hacsbutton"
|
||||
style="background-color: var(--google-red-500) !important; font-weight: bold;">
|
||||
UPGRADE ALL
|
||||
</a>
|
||||
|
||||
<a href='https://github.com/hacs/integration' rel='noreferrer' class='waves-effect waves-light btn hacsbutton'
|
||||
target="_blank">
|
||||
HACS REPO
|
||||
</a>
|
||||
|
||||
<a href='https://github.com/hacs/integration/issues' rel='noreferrer'
|
||||
class='waves-effect waves-light btn hacsbutton' target="_blank">
|
||||
OPEN ISSUE
|
||||
</a>
|
||||
|
||||
</div>
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="row">
|
||||
<ul class="collection with-header hacslist">
|
||||
<li class="collection-header hacscolor hacslist">
|
||||
<h5>CUSTOM REPOSITORIES</h5>
|
||||
</li>
|
||||
{% if hacs.system.status.background_task %}
|
||||
<li class="collection-item hacscolor hacslist">
|
||||
Loading custom repositories...
|
||||
</li>
|
||||
{% else %}
|
||||
{% for repository in hacs.sorted_by_repository_name %}
|
||||
{% if repository.custom %}
|
||||
<li class="collection-item hacscolor hacslist">
|
||||
<div>
|
||||
<a href="/hacsweb/{{ hacs.token }}/repository/{{repository.information.uid}}">
|
||||
<span class="hacs-badge">
|
||||
{{repository.information.category | upper}}
|
||||
</span>
|
||||
{{repository.information.full_name }}
|
||||
</a>
|
||||
{% if repository.status.installed %}
|
||||
<i title="Remove is not possible when installed."
|
||||
class="secondary-content fas fa-trash-alt disabledaction"></i>
|
||||
{% else %}
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_remove" name="remove" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<a class="secondary-content" href="#"
|
||||
onclick="toggleLoading();document.getElementsByName('remove')[0].submit()"
|
||||
style=" color: var(--primary-color)">
|
||||
<i title="Remove." class="fas fa-trash-alt"></i>
|
||||
</a>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% if not hacs.system.status.background_task %}
|
||||
<form class="minigrid" action="/hacsapi/{{ hacs.token }}/repository_register" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded">
|
||||
<input class="custom_repository" id="custom_url" type="text" name="custom_url"
|
||||
placeholder=" ADD CUSTOM REPOSITORY" style="width: 70%; color: var(--primary-text-color)">
|
||||
<select name="repository_type" class="hacs-select custom_repository">
|
||||
<option disabled selected value>Category</option>
|
||||
{% for element_type in hacs.common.categories | sort %}
|
||||
<option class='hacscolor' value='{{element_type}}'>{{element_type | title}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<button class="btn waves-effect waves-light right" type="submit" name="add" onclick="toggleLoading()"
|
||||
style="background-color: var(--primary-color); height: 44px;">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="hacs-card-standalone">
|
||||
<form style="display: contents" action="/hacsapi/{{ hacs.token }}/remove_new_flag" method="post" accept-charset="utf-8" enctype="application/x-www-form-urlencoded">
|
||||
<button class="waves-effect waves-light btn hacsadminbutton" type="submit" onclick="toggleLoading()">
|
||||
REMOVE FLAGS
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% for repository in hacs.store.frontend|sort(attribute='name') %}
|
||||
<form style="display: contents" action="/hacsapi/{{ hacs.token }}/devrepository" method="post" accept-charset="utf-8" enctype="application/x-www-form-urlencoded">
|
||||
<input type="hidden" name="repository" value="{{repository.information.uid}}">
|
||||
<button class="waves-effect waves-light btn hacsadminbutton" type="submit" onclick="toggleLoading()">
|
||||
{{ repository.name }}
|
||||
</button>
|
||||
</form>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="hacs-card-standalone">
|
||||
<form style="display: contents" action="/hacsapi/{{ hacs.token }}/set_ha_version_action" method="post" accept-charset="utf-8" enctype="application/x-www-form-urlencoded">
|
||||
<input class="hacscolor" type="text" name="ha_version" value="{{hacs.store.ha_version}}">
|
||||
<button class="waves-effect waves-light btn hacsadminbutton" type="submit" onclick="toggleLoading()">
|
||||
SET
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="hacs-card-standalone">
|
||||
<form style="display: contents" action="/hacsapi/{{ hacs.token }}/dev_template" method="post" accept-charset="utf-8" enctype="application/x-www-form-urlencoded">
|
||||
<input class="hacscolor" type="text" name="repository_id" value="{{ hacs.dev_template_id }}">
|
||||
<textarea class="hacscolor" name="template" style="width:100%;height:250px;"></textarea></br>
|
||||
<button name="set" class="waves-effect waves-light btn hacsadminbutton" type="submit" onclick="toggleLoading()">
|
||||
TEST
|
||||
</button>
|
||||
<button name="clear" class="waves-effect waves-light btn hacsadminbutton" type="submit" onclick="toggleLoading()">
|
||||
CLEAR
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if hacs.dev_template %}
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="hacs-card-standalone">
|
||||
{{ hacs.dev_template }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,8 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="hacs-card-standalone">
|
||||
<label>{{hacs.token}}</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,19 +0,0 @@
|
||||
<div class="hacs-overview-container hacs-color minigrid" style="margin-top: 30px;">
|
||||
<div class="hacs-card-standalone">
|
||||
<h5 style="margin-left: 5px;">DEVELOPER TOOLS</h5>
|
||||
{% for button in [
|
||||
"remove_new_flag",
|
||||
"repositories",
|
||||
"set_ha_version",
|
||||
"token",
|
||||
"template_test"
|
||||
] %}
|
||||
<form style="display: contents" action="/hacsapi/{{ hacs.token }}/devview" method="post" accept-charset="utf-8" enctype="application/x-www-form-urlencoded">
|
||||
<input type="hidden" name="view" value="{{button}}">
|
||||
<button class="waves-effect waves-light btn hacsadminbutton" type="submit" onclick="toggleLoading()">
|
||||
{{ button.replace("_"," ")}}
|
||||
</button>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,38 +0,0 @@
|
||||
{% if hacs.get_by_id("172733314").pending_restart %}
|
||||
{% set pending_restart = " <b>(RESTART PENDING!)</b>" %}
|
||||
{% else %}
|
||||
{% set pending_restart = "" %}
|
||||
{% endif %}
|
||||
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="hacs-card-standalone">
|
||||
<h5>HACS (Home Assistant Community Store)</h5>
|
||||
<table class="hacs-table">
|
||||
<tr class="hacs-table-row repository">
|
||||
<td class="repository">
|
||||
<b>Version:</b>
|
||||
</td>
|
||||
<td class="repository text">
|
||||
{{hacs.version}}{{pending_restart}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hacs-table-row repository">
|
||||
<td class="repository">
|
||||
<b>Display:</b>
|
||||
</td>
|
||||
<td class="repository">
|
||||
<form action="/hacsapi/{{ hacs.token }}/frontend_mode" name="overview_display" method="post"
|
||||
accept-charset="utf-8" enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<select name="view_type" class="hacs-select repository"
|
||||
onchange="toggleLoading();document.getElementsByName('overview_display')[0].submit()">
|
||||
{% for displayoption in ["Grid", "Table"] %}
|
||||
<option {{"selected" if hacs.configuration.frontend_mode == displayoption else ""}} class="hacscolor"
|
||||
value="{{displayoption}}">{{displayoption}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
{% set repositories = namespace(hidden=False) %}
|
||||
{% for repository in hacs.repositories_list_repo %}
|
||||
{% if repository.hide %}
|
||||
{% set repositories.hidden = True %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if repositories.hidden %}
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="row">
|
||||
<ul class="collection with-header hacslist">
|
||||
<li class="collection-header hacscolor hacslist">
|
||||
<h5>HIDDEN REPOSITORIES</h5>
|
||||
</li>
|
||||
{% if hacs.system.status.background_task %}
|
||||
<li class="collection-item hacscolor hacslist">
|
||||
Loading hidden repositories...
|
||||
</li>
|
||||
{% else %}
|
||||
{% for repository in hacs.repositories_list_repo %}
|
||||
{% if repository.hide %}
|
||||
<li class="collection-item hacscolor hacslist">
|
||||
<div>
|
||||
<form action="/hacsapi/{{ hacs.token }}/repository_unhide" name="remove" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<input type="hidden" name="repository_id" value="{{repository.information.uid}}">
|
||||
<a href="#" onclick="toggleLoading();document.getElementsByName('remove')[0].submit()"
|
||||
style=" color: var(--primary-color)">
|
||||
<i title="Unhide" class="fas fa-plus-circle" style="padding-right: 8px"></i>
|
||||
</a>
|
||||
{{repository.repository_name}}
|
||||
<span class="hacs-badge">{{repository.repository_type}}</span>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,42 +0,0 @@
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="row">
|
||||
<h5>{{ displaytype }}</h5>
|
||||
<table class="hacs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Name</th>
|
||||
<th class="smal-hide">Description</th>
|
||||
{% if location == 'overview' %}
|
||||
<th class="smal-hide">Installed</th>
|
||||
<th class="smal-hide">Available</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for repository in repositories.repositories %}
|
||||
<tr class="hacs-table-row"
|
||||
onclick="toggleLoading();window.location='/hacsweb/{{ hacs.token }}/repository/{{ repository.information.uid }}?timestamp={{ timestamp }}';">
|
||||
<input type="hidden" name="author" value="{{repository.information.authors}}">
|
||||
<input type="hidden" name="topics" value="{{repository.information.topics}}">
|
||||
<input type="hidden" name="full_name" value="{{repository.information.full_name}}">
|
||||
<td title="{{ repository.display_status_description }}">
|
||||
{% if location == 'store' and repository.status.new %}
|
||||
<span style='margin-left: 25%' class='repository-list-badge hacs-badge'>NEW</span>
|
||||
{% else %}
|
||||
<i style='margin-left: 25%' class='fas fa-cube card-status {{repository.display_status}}'></i></td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{{repository.display_name}}
|
||||
</td>
|
||||
<td class="hacs-card-content smal-hide">{{repository.information.description}}</td>
|
||||
{% if location == 'overview' %}
|
||||
<td class="smal-hide">{{repository.display_installed_version}}</td>
|
||||
<td class="smal-hide">{{repository.display_available_version}}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,44 +0,0 @@
|
||||
{% set repositories = namespace(repositories=[]) %}
|
||||
{% for repository in hacs.sorted_by_name %}
|
||||
{% if repository.status.new %}
|
||||
{% set repositories.repositories = repositories.repositories+[repository] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if repositories.repositories %}
|
||||
<div class='hacs-overview-container'>
|
||||
<div class="row">
|
||||
<h5>NEW REPOSITORIES (ALL)</h5>
|
||||
<form action="/hacsapi/{{ hacs.token }}/remove_new_flag" name="remove_new_flag" method="post" accept-charset="utf-8"
|
||||
enctype="application/x-www-form-urlencoded" class="hacs-form">
|
||||
<input type="hidden" name="return" value="store">
|
||||
<a class="waves-effect waves-light btn modal-trigger hacsbutton" href="#"
|
||||
onclick="toggleLoading();document.getElementsByName('remove_new_flag')[0].submit()" style="width: 95%;">
|
||||
Remove the NEW flag for all new items
|
||||
</a>
|
||||
</form>
|
||||
<table class="hacs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Name</th>
|
||||
<th class="smal-hide">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for repository in repositories.repositories %}
|
||||
<tr class="hacs-table-row"
|
||||
onclick="toggleLoading();window.location='/hacsweb/{{ hacs.token }}/repository/{{ repository.information.uid }}?timestamp={{ timestamp }}';">
|
||||
<td>
|
||||
<span class="hacs-badge">{{repository.information.category | upper}}</span>
|
||||
</td>
|
||||
<td>
|
||||
{{repository.display_name}}
|
||||
</td>
|
||||
<td class="hacs-card-content smal-hide">{{repository.information.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,391 +0,0 @@
|
||||
"""Initialize the HACS base."""
|
||||
# pylint: disable=unused-argument, bad-continuation
|
||||
import json
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.helpers.event import async_call_later, async_track_time_interval
|
||||
|
||||
from aiogithubapi import AIOGitHubException, AIOGitHubRatelimit
|
||||
from integrationhelper import Logger
|
||||
|
||||
|
||||
from ..const import ELEMENT_TYPES
|
||||
from ..store import async_load_from_store, async_save_to_store
|
||||
|
||||
|
||||
class HacsStatus:
|
||||
"""HacsStatus."""
|
||||
|
||||
startup = False
|
||||
background_task = False
|
||||
reloading_data = False
|
||||
upgrading_all = False
|
||||
|
||||
|
||||
class HacsCommon:
|
||||
"""Common for HACS."""
|
||||
|
||||
categories = []
|
||||
blacklist = []
|
||||
default = []
|
||||
installed = []
|
||||
skip = []
|
||||
|
||||
|
||||
class System:
|
||||
"""System info."""
|
||||
|
||||
status = HacsStatus()
|
||||
config_path = None
|
||||
ha_version = None
|
||||
disabled = False
|
||||
|
||||
|
||||
class Developer:
|
||||
"""Developer settings/tools."""
|
||||
|
||||
template_id = "Repository ID"
|
||||
template_content = ""
|
||||
template_raw = ""
|
||||
|
||||
@property
|
||||
def devcontainer(self):
|
||||
"""Is it a devcontainer?"""
|
||||
import os
|
||||
|
||||
if "DEVCONTAINER" in os.environ:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Hacs:
|
||||
"""The base class of HACS, nested thoughout the project."""
|
||||
|
||||
token = f"{str(uuid.uuid4())}-{str(uuid.uuid4())}"
|
||||
hacsweb = f"/hacsweb/{token}"
|
||||
hacsapi = f"/hacsapi/{token}"
|
||||
repositories = []
|
||||
repo = None
|
||||
data_repo = None
|
||||
developer = Developer()
|
||||
data = None
|
||||
configuration = None
|
||||
logger = Logger("hacs")
|
||||
github = None
|
||||
hass = None
|
||||
version = None
|
||||
system = System()
|
||||
tasks = []
|
||||
common = HacsCommon()
|
||||
|
||||
@staticmethod
|
||||
def init(hass, github_token):
|
||||
"""Return a initialized HACS object."""
|
||||
return Hacs()
|
||||
|
||||
def get_by_id(self, repository_id):
|
||||
"""Get repository by ID."""
|
||||
try:
|
||||
for repository in self.repositories:
|
||||
if repository.information.uid == repository_id:
|
||||
return repository
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_by_name(self, repository_full_name):
|
||||
"""Get repository by full_name."""
|
||||
try:
|
||||
for repository in self.repositories:
|
||||
if repository.information.full_name == repository_full_name:
|
||||
return repository
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
return None
|
||||
|
||||
def is_known(self, repository_full_name):
|
||||
"""Return a bool if the repository is known."""
|
||||
for repository in self.repositories:
|
||||
if repository.information.full_name == repository_full_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def sorted_by_name(self):
|
||||
"""Return a sorted(by name) list of repository objects."""
|
||||
return sorted(self.repositories, key=lambda x: x.display_name)
|
||||
|
||||
@property
|
||||
def sorted_by_repository_name(self):
|
||||
"""Return a sorted(by repository_name) list of repository objects."""
|
||||
return sorted(self.repositories, key=lambda x: x.information.full_name)
|
||||
|
||||
async def register_repository(self, full_name, category, check=True):
|
||||
"""Register a repository."""
|
||||
from ..repositories.repository import RERPOSITORY_CLASSES
|
||||
|
||||
if full_name in self.common.skip:
|
||||
if full_name != "hacs/integration":
|
||||
self.logger.debug(f"Skipping {full_name}")
|
||||
return
|
||||
|
||||
if category not in RERPOSITORY_CLASSES:
|
||||
self.logger.error(f"{category} is not a valid repository category.")
|
||||
return False
|
||||
|
||||
repository = RERPOSITORY_CLASSES[category](full_name)
|
||||
if check:
|
||||
try:
|
||||
await repository.registration()
|
||||
if repository.validate.errors:
|
||||
self.common.skip.append(repository.information.full_name)
|
||||
if not self.system.status.startup:
|
||||
self.logger.error(f"Validation for {full_name} failed.")
|
||||
return repository.validate.errors
|
||||
repository.logger.info("Registration complete")
|
||||
except AIOGitHubException as exception:
|
||||
self.logger.debug(self.github.ratelimits.remaining)
|
||||
self.logger.debug(self.github.ratelimits.reset_utc)
|
||||
self.common.skip.append(repository.information.full_name)
|
||||
if not self.system.status.startup:
|
||||
self.logger.error(
|
||||
f"Validation for {full_name} failed with {exception}."
|
||||
)
|
||||
return exception
|
||||
self.hass.bus.async_fire(
|
||||
"hacs/repository",
|
||||
{
|
||||
"id": 1337,
|
||||
"action": "registration",
|
||||
"repository": repository.information.full_name,
|
||||
"repository_id": repository.information.uid,
|
||||
},
|
||||
)
|
||||
self.repositories.append(repository)
|
||||
|
||||
async def startup_tasks(self):
|
||||
"""Tasks tha are started after startup."""
|
||||
self.system.status.background_task = True
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
self.logger.debug(self.github.ratelimits.remaining)
|
||||
self.logger.debug(self.github.ratelimits.reset_utc)
|
||||
|
||||
await self.handle_critical_repositories_startup()
|
||||
await self.handle_critical_repositories()
|
||||
await self.load_known_repositories()
|
||||
await self.clear_out_blacklisted_repositories()
|
||||
|
||||
self.tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.recuring_tasks_installed, timedelta(minutes=30)
|
||||
)
|
||||
)
|
||||
self.tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.recuring_tasks_all, timedelta(minutes=800)
|
||||
)
|
||||
)
|
||||
|
||||
self.system.status.startup = False
|
||||
self.system.status.background_task = False
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
await self.data.async_write()
|
||||
|
||||
async def handle_critical_repositories_startup(self):
|
||||
"""Handled critical repositories during startup."""
|
||||
alert = False
|
||||
critical = await async_load_from_store(self.hass, "critical")
|
||||
if not critical:
|
||||
return
|
||||
for repo in critical:
|
||||
if not repo["acknowledged"]:
|
||||
alert = True
|
||||
if alert:
|
||||
self.logger.critical("URGENT!: Check the HACS panel!")
|
||||
self.hass.components.persistent_notification.create(
|
||||
title="URGENT!", message="**Check the HACS panel!**"
|
||||
)
|
||||
|
||||
async def handle_critical_repositories(self):
|
||||
"""Handled critical repositories during runtime."""
|
||||
# Get critical repositories
|
||||
instored = []
|
||||
critical = []
|
||||
was_installed = False
|
||||
|
||||
try:
|
||||
critical = await self.data_repo.get_contents("critical")
|
||||
critical = json.loads(critical.content)
|
||||
except AIOGitHubException:
|
||||
pass
|
||||
|
||||
if not critical:
|
||||
self.logger.debug("No critical repositories")
|
||||
return
|
||||
|
||||
stored_critical = await async_load_from_store(self.hass, "critical")
|
||||
|
||||
for stored in stored_critical or []:
|
||||
instored.append(stored["repository"])
|
||||
|
||||
stored_critical = []
|
||||
|
||||
for repository in critical:
|
||||
self.common.blacklist.append(repository["repository"])
|
||||
repo = self.get_by_name(repository["repository"])
|
||||
|
||||
stored = {
|
||||
"repository": repository["repository"],
|
||||
"reason": repository["reason"],
|
||||
"link": repository["link"],
|
||||
"acknowledged": True,
|
||||
}
|
||||
|
||||
if repository["repository"] not in instored:
|
||||
if repo is not None and repo.installed:
|
||||
self.logger.critical(
|
||||
f"Removing repository {repository['repository']}, it is marked as critical"
|
||||
)
|
||||
was_installed = True
|
||||
stored["acknowledged"] = False
|
||||
# Uninstall from HACS
|
||||
repo.remove()
|
||||
await repo.uninstall()
|
||||
stored_critical.append(stored)
|
||||
|
||||
# Save to FS
|
||||
await async_save_to_store(self.hass, "critical", stored_critical)
|
||||
|
||||
# Resart HASS
|
||||
if was_installed:
|
||||
self.logger.critical("Resarting Home Assistant")
|
||||
self.hass.async_create_task(self.hass.async_stop(100))
|
||||
|
||||
async def recuring_tasks_installed(self, notarealarg=None):
|
||||
"""Recuring tasks for installed repositories."""
|
||||
self.logger.debug(
|
||||
"Starting recuring background task for installed repositories"
|
||||
)
|
||||
self.system.status.background_task = True
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
self.logger.debug(self.github.ratelimits.remaining)
|
||||
self.logger.debug(self.github.ratelimits.reset_utc)
|
||||
for repository in self.repositories:
|
||||
if repository.status.installed:
|
||||
try:
|
||||
await repository.update_repository()
|
||||
repository.logger.debug("Information update done.")
|
||||
except AIOGitHubException:
|
||||
self.system.status.background_task = False
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
await self.data.async_write()
|
||||
self.logger.debug(
|
||||
"Recuring background task for installed repositories done"
|
||||
)
|
||||
return
|
||||
await self.handle_critical_repositories()
|
||||
self.system.status.background_task = False
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
await self.data.async_write()
|
||||
self.logger.debug("Recuring background task for installed repositories done")
|
||||
|
||||
async def recuring_tasks_all(self, notarealarg=None):
|
||||
"""Recuring tasks for all repositories."""
|
||||
self.logger.debug("Starting recuring background task for all repositories")
|
||||
self.system.status.background_task = True
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
self.logger.debug(self.github.ratelimits.remaining)
|
||||
self.logger.debug(self.github.ratelimits.reset_utc)
|
||||
for repository in self.repositories:
|
||||
try:
|
||||
await repository.update_repository()
|
||||
repository.logger.debug("Information update done.")
|
||||
except AIOGitHubException:
|
||||
self.system.status.background_task = False
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
await self.data.async_write()
|
||||
self.logger.debug("Recuring background task for all repositories done")
|
||||
return
|
||||
await self.load_known_repositories()
|
||||
await self.clear_out_blacklisted_repositories()
|
||||
self.system.status.background_task = False
|
||||
await self.data.async_write()
|
||||
self.hass.bus.async_fire("hacs/status", {})
|
||||
self.hass.bus.async_fire("hacs/repository", {"action": "reload"})
|
||||
self.logger.debug("Recuring background task for all repositories done")
|
||||
|
||||
async def clear_out_blacklisted_repositories(self):
|
||||
"""Clear out blaclisted repositories."""
|
||||
need_to_save = False
|
||||
for repository in self.common.blacklist:
|
||||
if self.is_known(repository):
|
||||
repository = self.get_by_name(repository)
|
||||
if repository.status.installed:
|
||||
self.logger.error(
|
||||
f"You have {repository.information.full_name} installed with HACS, this repositroy has been blacklisted, please consider removing it."
|
||||
)
|
||||
else:
|
||||
need_to_save = True
|
||||
repository.remove()
|
||||
|
||||
if need_to_save:
|
||||
await self.data.async_write()
|
||||
|
||||
async def get_repositories(self):
|
||||
"""Return a list of repositories."""
|
||||
repositories = {}
|
||||
if self.configuration.dev:
|
||||
if self.developer.devcontainer:
|
||||
repositories = {
|
||||
"appdaemon": ["ludeeus/ad-hacs"],
|
||||
"integration": ["ludeeus/integration-hacs"],
|
||||
"plugin": ["maykar/compact-custom-header"],
|
||||
"python_script": ["ludeeus/ps-hacs"],
|
||||
"theme": ["ludeeus/theme-hacs"],
|
||||
}
|
||||
else:
|
||||
for category in self.common.categories:
|
||||
remote = await self.data_repo.get_contents(category)
|
||||
repositories[category] = json.loads(remote.content)
|
||||
if category == "plugin":
|
||||
org = await self.github.get_org_repos("custom-cards")
|
||||
for repo in org:
|
||||
repositories[category].append(repo.full_name)
|
||||
if category == "integration":
|
||||
org = await self.github.get_org_repos("custom-components")
|
||||
for repo in org:
|
||||
repositories[category].append(repo.full_name)
|
||||
if category == "theme":
|
||||
org = await self.github.get_org_repos(
|
||||
"home-assistant-community-themes"
|
||||
)
|
||||
for repo in org:
|
||||
repositories[category].append(repo.full_name)
|
||||
|
||||
for category in repositories:
|
||||
for repo in repositories[category]:
|
||||
if repo not in self.common.default:
|
||||
self.common.default.append(repo)
|
||||
return repositories
|
||||
|
||||
async def load_known_repositories(self):
|
||||
"""Load known repositories."""
|
||||
self.logger.info("Loading known repositories")
|
||||
blacklist = await self.data_repo.get_contents("blacklist")
|
||||
repositories = await self.get_repositories()
|
||||
|
||||
for item in json.loads(blacklist.content):
|
||||
if item not in self.common.blacklist:
|
||||
self.common.blacklist.append(item)
|
||||
|
||||
for category in repositories:
|
||||
for repo in repositories[category]:
|
||||
if repo in self.common.blacklist:
|
||||
continue
|
||||
if self.is_known(repo):
|
||||
continue
|
||||
try:
|
||||
await self.register_repository(repo, category)
|
||||
except (AIOGitHubException, AIOGitHubRatelimit):
|
||||
pass
|
||||
@@ -1,72 +0,0 @@
|
||||
"""Backup."""
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from time import sleep
|
||||
|
||||
from integrationhelper import Logger
|
||||
|
||||
BACKUP_PATH = tempfile.gettempdir() + "/hacs_backup/"
|
||||
|
||||
|
||||
class Backup:
|
||||
"""Backup."""
|
||||
|
||||
def __init__(self, local_path, backup_path=BACKUP_PATH):
|
||||
"""initialize."""
|
||||
self.logger = Logger("hacs.backup")
|
||||
self.local_path = local_path
|
||||
self.backup_path = backup_path
|
||||
self.backup_path_full = f"{self.backup_path}{self.local_path.split('/')[-1]}"
|
||||
|
||||
def create(self):
|
||||
"""Create a backup in /tmp"""
|
||||
if not os.path.exists(self.local_path):
|
||||
return
|
||||
if os.path.exists(self.backup_path):
|
||||
shutil.rmtree(self.backup_path)
|
||||
while os.path.exists(self.backup_path):
|
||||
sleep(0.1)
|
||||
os.makedirs(self.backup_path, exist_ok=True)
|
||||
|
||||
try:
|
||||
if os.path.isfile(self.local_path):
|
||||
shutil.copyfile(self.local_path, self.backup_path_full)
|
||||
os.remove(self.local_path)
|
||||
else:
|
||||
shutil.copytree(self.local_path, self.backup_path_full)
|
||||
shutil.rmtree(self.local_path)
|
||||
while os.path.exists(self.local_path):
|
||||
sleep(0.1)
|
||||
self.logger.debug(
|
||||
f"Backup for {self.local_path}, created in {self.backup_path_full}"
|
||||
)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
def restore(self):
|
||||
"""Restore from backup."""
|
||||
if not os.path.exists(self.backup_path_full):
|
||||
return
|
||||
|
||||
if os.path.isfile(self.backup_path_full):
|
||||
if os.path.exists(self.local_path):
|
||||
os.remove(self.local_path)
|
||||
shutil.copyfile(self.backup_path_full, self.local_path)
|
||||
else:
|
||||
if os.path.exists(self.local_path):
|
||||
shutil.rmtree(self.local_path)
|
||||
while os.path.exists(self.local_path):
|
||||
sleep(0.1)
|
||||
shutil.copytree(self.backup_path_full, self.local_path)
|
||||
self.logger.debug(
|
||||
f"Restored {self.local_path}, from backup {self.backup_path_full}"
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup backup files."""
|
||||
if os.path.exists(self.backup_path):
|
||||
shutil.rmtree(self.backup_path)
|
||||
while os.path.exists(self.backup_path):
|
||||
sleep(0.1)
|
||||
self.logger.debug(f"Backup dir {self.backup_path} cleared")
|
||||
@@ -1,55 +0,0 @@
|
||||
"""HACS Configuration."""
|
||||
import attr
|
||||
|
||||
from custom_components.hacs.hacsbase.exceptions import HacsUserScrewupException
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class Configuration:
|
||||
"""Configuration class."""
|
||||
|
||||
# Main configuration:
|
||||
appdaemon_path: str = "appdaemon/apps/"
|
||||
appdaemon: bool = False
|
||||
config: dict = {}
|
||||
config_entry: dict = {}
|
||||
config_type: str = None
|
||||
dev: bool = False
|
||||
frontend_mode: str = "Grid"
|
||||
options: dict = {}
|
||||
plugin_path: str = "www/community/"
|
||||
python_script_path: str = "python_scripts/"
|
||||
python_script: bool = False
|
||||
sidepanel_icon: str = "mdi:alpha-c-box"
|
||||
sidepanel_title: str = "Community"
|
||||
theme_path: str = "themes/"
|
||||
theme: bool = False
|
||||
token: str = None
|
||||
|
||||
# Config options:
|
||||
country: str = "ALL"
|
||||
experimental: bool = False
|
||||
release_limit: int = 5
|
||||
|
||||
@staticmethod
|
||||
def from_dict(configuration: dict, options: dict):
|
||||
"""Set attributes from dicts."""
|
||||
if isinstance(options, bool) or isinstance(configuration.get("options"), bool):
|
||||
raise HacsUserScrewupException("Configuration is not valid.")
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
if not configuration:
|
||||
raise HacsUserScrewupException("Configuration is not valid.")
|
||||
|
||||
config = Configuration()
|
||||
|
||||
config.config = configuration
|
||||
config.options = options
|
||||
|
||||
for conf_type in [configuration, options]:
|
||||
for key in conf_type:
|
||||
setattr(config, key, conf_type[key])
|
||||
|
||||
return config
|
||||
@@ -1,10 +0,0 @@
|
||||
"""Constants for HACS"""
|
||||
# pylint: disable=unused-import
|
||||
STORAGE_VERSION = "6"
|
||||
STORENAME = "hacs"
|
||||
|
||||
# Messages
|
||||
NOT_SUPPORTED_HA_VERSION = "You have version '{}' of Home Assistant, but version '{}' of '{}' require version '{}' of Home Assistant, install and upgrades are disabled for this integration untill you upgrade Home Assistant."
|
||||
|
||||
|
||||
NO_ELEMENTS = "No elements to show, open the store to install some awesome stuff."
|
||||
@@ -1,215 +0,0 @@
|
||||
"""Data handler for HACS."""
|
||||
import os
|
||||
import json
|
||||
from integrationhelper import Logger
|
||||
from . import Hacs
|
||||
from .const import STORAGE_VERSION
|
||||
from ..const import VERSION
|
||||
from ..repositories.manifest import HacsManifest
|
||||
from ..store import async_save_to_store, async_load_from_store
|
||||
|
||||
|
||||
STORES = {
|
||||
"old": "hacs",
|
||||
"hacs": "hacs.hacs",
|
||||
"installed": "hacs.installed",
|
||||
"repositories": "hacs.repositories",
|
||||
}
|
||||
|
||||
|
||||
class HacsData(Hacs):
|
||||
"""HacsData class."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
self.logger = Logger("hacs.data")
|
||||
|
||||
def check_corrupted_files(self):
|
||||
"""Return True if one (or more) of the files are corrupted."""
|
||||
for store in STORES:
|
||||
path = f"{self.system.config_path}/.storage/{STORES[store]}"
|
||||
if os.path.exists(path):
|
||||
if os.stat(path).st_size == 0:
|
||||
# File is empty (corrupted)
|
||||
return True
|
||||
return False
|
||||
|
||||
def read(self, store):
|
||||
"""Return data from a store."""
|
||||
path = f"{self.system.config_path}/.storage/{STORES[store]}"
|
||||
content = None
|
||||
if os.path.exists(path):
|
||||
with open(path, "r", encoding="utf-8") as storefile:
|
||||
content = storefile.read()
|
||||
content = json.loads(content)
|
||||
return content
|
||||
|
||||
async def async_write(self):
|
||||
"""Write content to the store files."""
|
||||
if self.system.status.background_task:
|
||||
return
|
||||
|
||||
self.logger.debug("Saving data")
|
||||
|
||||
# Hacs
|
||||
await async_save_to_store(
|
||||
self.hass, "hacs", {"view": self.configuration.frontend_mode}
|
||||
)
|
||||
|
||||
# Repositories
|
||||
content = {}
|
||||
for repository in self.repositories:
|
||||
if repository.repository_manifest is not None:
|
||||
repository_manifest = repository.repository_manifest.manifest
|
||||
else:
|
||||
repository_manifest = None
|
||||
content[repository.information.uid] = {
|
||||
"authors": repository.information.authors,
|
||||
"topics": repository.information.topics,
|
||||
"category": repository.information.category,
|
||||
"description": repository.information.description,
|
||||
"full_name": repository.information.full_name,
|
||||
"hide": repository.status.hide,
|
||||
"installed_commit": repository.versions.installed_commit,
|
||||
"installed": repository.status.installed,
|
||||
"last_commit": repository.versions.available_commit,
|
||||
"last_release_tag": repository.versions.available,
|
||||
"repository_manifest": repository_manifest,
|
||||
"name": repository.information.name,
|
||||
"new": repository.status.new,
|
||||
"selected_tag": repository.status.selected_tag,
|
||||
"show_beta": repository.status.show_beta,
|
||||
"version_installed": repository.versions.installed,
|
||||
}
|
||||
|
||||
await async_save_to_store(self.hass, "repositories", content)
|
||||
self.hass.bus.async_fire("hacs/repository", {})
|
||||
self.hass.bus.fire("hacs/config", {})
|
||||
|
||||
async def restore(self):
|
||||
"""Restore saved data."""
|
||||
hacs = {}
|
||||
repositories = {}
|
||||
|
||||
try:
|
||||
hacs = await async_load_from_store(self.hass, "hacs")
|
||||
except KeyError:
|
||||
await async_save_to_store(self.hass, "hacs", self.data.read("hacs")["data"])
|
||||
hacs = await async_load_from_store(self.hass, "hacs")
|
||||
|
||||
try:
|
||||
repositories = await async_load_from_store(self.hass, "repositories")
|
||||
except KeyError:
|
||||
await async_save_to_store(
|
||||
self.hass, "repositories", self.data.read("repositories")["data"]
|
||||
)
|
||||
repositories = await async_load_from_store(self.hass, "repositories")
|
||||
|
||||
try:
|
||||
if self.check_corrupted_files():
|
||||
# Coruptted installation
|
||||
self.logger.critical("Restore failed one or more files are corrupted!")
|
||||
return False
|
||||
if hacs is None and repositories is None:
|
||||
# Assume new install
|
||||
return True
|
||||
|
||||
self.logger.info("Restore started")
|
||||
|
||||
# Hacs
|
||||
self.configuration.frontend_mode = hacs.get("view", "Grid")
|
||||
|
||||
# Repositories
|
||||
repositories = repositories
|
||||
for entry in repositories:
|
||||
repo = repositories[entry]
|
||||
if repo["full_name"] == "custom-components/hacs":
|
||||
# Skip the old repo location
|
||||
continue
|
||||
if not self.is_known(repo["full_name"]):
|
||||
await self.register_repository(
|
||||
repo["full_name"], repo["category"], False
|
||||
)
|
||||
repository = self.get_by_name(repo["full_name"])
|
||||
if repository is None:
|
||||
self.logger.error(f"Did not find {repo['full_name']}")
|
||||
continue
|
||||
|
||||
# Restore repository attributes
|
||||
if repo.get("authors") is not None:
|
||||
repository.information.authors = repo["authors"]
|
||||
|
||||
if repo.get("topics", []):
|
||||
repository.information.topics = repo["topics"]
|
||||
|
||||
if repo.get("description") is not None:
|
||||
repository.information.description = repo["description"]
|
||||
|
||||
if repo.get("name") is not None:
|
||||
repository.information.name = repo["name"]
|
||||
|
||||
if repo.get("hide") is not None:
|
||||
repository.status.hide = repo["hide"]
|
||||
|
||||
if repo.get("installed") is not None:
|
||||
repository.status.installed = repo["installed"]
|
||||
if repository.status.installed:
|
||||
repository.status.first_install = False
|
||||
|
||||
if repo.get("selected_tag") is not None:
|
||||
repository.status.selected_tag = repo["selected_tag"]
|
||||
|
||||
if repo.get("repository_manifest") is not None:
|
||||
repository.repository_manifest = HacsManifest.from_dict(
|
||||
repo["repository_manifest"]
|
||||
)
|
||||
|
||||
if repo.get("show_beta") is not None:
|
||||
repository.status.show_beta = repo["show_beta"]
|
||||
|
||||
if repo.get("last_commit") is not None:
|
||||
repository.versions.available_commit = repo["last_commit"]
|
||||
|
||||
repository.information.uid = entry
|
||||
|
||||
if repo.get("last_release_tag") is not None:
|
||||
repository.releases.last_release = repo["last_release_tag"]
|
||||
repository.versions.available = repo["last_release_tag"]
|
||||
|
||||
if repo.get("new") is not None:
|
||||
repository.status.new = repo["new"]
|
||||
|
||||
if repo["full_name"] == "hacs/integration":
|
||||
repository.versions.installed = VERSION
|
||||
repository.status.installed = True
|
||||
if "b" in VERSION:
|
||||
repository.status.show_beta = True
|
||||
elif repo.get("version_installed") is not None:
|
||||
repository.versions.installed = repo["version_installed"]
|
||||
|
||||
if repo.get("installed_commit") is not None:
|
||||
repository.versions.installed_commit = repo["installed_commit"]
|
||||
|
||||
self.logger.info("Restore done")
|
||||
except Exception as exception:
|
||||
self.logger.critical(
|
||||
f"[{exception}] Restore Failed! see https://github.com/hacs/integration/issues/639 for more details."
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def save(logger, path, content):
|
||||
"""Save file."""
|
||||
from .backup import Backup
|
||||
|
||||
backup = Backup(path)
|
||||
backup.create()
|
||||
try:
|
||||
content = {"data": content, "schema": STORAGE_VERSION}
|
||||
with open(path, "w", encoding="utf-8") as storefile:
|
||||
json.dump(content, storefile, indent=4)
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
logger.warning(f"Saving {path} failed - {exception}")
|
||||
backup.restore()
|
||||
backup.cleanup()
|
||||
@@ -1,45 +0,0 @@
|
||||
"""Custom Exceptions."""
|
||||
|
||||
|
||||
class HacsBaseException(Exception):
|
||||
"""Super basic."""
|
||||
|
||||
|
||||
class HacsUserScrewupException(HacsBaseException):
|
||||
"""Raise this when the user does something they should not do."""
|
||||
|
||||
|
||||
class HacsNotSoBasicException(HacsBaseException):
|
||||
"""Not that basic."""
|
||||
|
||||
|
||||
class HacsDataFileMissing(HacsBaseException):
|
||||
"""Raise this storage datafile is missing."""
|
||||
|
||||
|
||||
class HacsDataNotExpected(HacsBaseException):
|
||||
"""Raise this when data returned from storage is not ok."""
|
||||
|
||||
|
||||
class HacsRepositoryInfo(HacsBaseException):
|
||||
"""Raise this when repository info is missing/wrong."""
|
||||
|
||||
|
||||
class HacsRequirement(HacsBaseException):
|
||||
"""Raise this when repository is missing a requirement."""
|
||||
|
||||
|
||||
class HacsMissingManifest(HacsBaseException):
|
||||
"""Raise this when manifest is missing."""
|
||||
|
||||
def __init__(self, message="The manifest file is missing in the repository."):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class HacsBlacklistException(HacsBaseException):
|
||||
"""Raise this when the repository is currently in the blacklist."""
|
||||
|
||||
def __init__(self, message="The repository is currently in the blacklist."):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
@@ -1,129 +0,0 @@
|
||||
"""HACS Migration logic."""
|
||||
# pylint: disable=broad-except,no-member
|
||||
from shutil import copy2
|
||||
|
||||
from integrationhelper import Logger, Validate
|
||||
|
||||
from . import Hacs
|
||||
from .data import save, STORES
|
||||
from .const import STORAGE_VERSION
|
||||
|
||||
|
||||
MIGRATIONS = {}
|
||||
|
||||
|
||||
def register(cls):
|
||||
"""Register steps."""
|
||||
MIGRATIONS[cls.from_version] = cls
|
||||
return cls
|
||||
|
||||
|
||||
class ValidateData(Hacs):
|
||||
"""Validate."""
|
||||
|
||||
def validate_local_data_file(self):
|
||||
"""Validate content."""
|
||||
validate = Validate()
|
||||
old_data = self.data.read("old")
|
||||
|
||||
if old_data is None:
|
||||
# new install.
|
||||
return True
|
||||
|
||||
if old_data.get("hacs", {}).get("schema") is None:
|
||||
return True
|
||||
|
||||
if old_data.get("hacs", {}).get("schema") == STORAGE_VERSION:
|
||||
# Newest version, no need to do anything.
|
||||
return
|
||||
|
||||
current = old_data.get("hacs", {}).get("schema")
|
||||
|
||||
for version in range(int(current), int(STORAGE_VERSION)):
|
||||
if current in MIGRATIONS:
|
||||
MIGRATIONS[current](old_data).migrate()
|
||||
else:
|
||||
validate.errors.append(f"Missing migration step for {version}")
|
||||
|
||||
if validate.errors:
|
||||
for error in validate.errors:
|
||||
self.logger.critical(error)
|
||||
return validate.success
|
||||
|
||||
|
||||
class Migration(Hacs):
|
||||
"""Hacs migrations"""
|
||||
|
||||
def __init__(self, old_data=None):
|
||||
"""initialize migration."""
|
||||
self.old_data = old_data
|
||||
self.logger = Logger("hacs.migration")
|
||||
self.source = f"{self.system.config_path}/.storage/hacs"
|
||||
self.cleanup()
|
||||
self.backup()
|
||||
|
||||
def cleanup(self):
|
||||
"""Remove files no longer in use."""
|
||||
|
||||
def backup(self):
|
||||
"""Back up old file."""
|
||||
destination = f"{self.source}.{self.from_version}"
|
||||
self.logger.info(f"Backing up current file to '{destination}'")
|
||||
copy2(self.source, destination)
|
||||
|
||||
|
||||
@register
|
||||
class FromVersion4(Migration):
|
||||
"""Migrate from version 4"""
|
||||
|
||||
from_version = "4"
|
||||
|
||||
def migrate(self):
|
||||
"""Start migration."""
|
||||
self.logger.info(f"Starting migration from {self.from_version}")
|
||||
hacs = self.old_data["hacs"]
|
||||
repositories = self.old_data["repositories"]
|
||||
installed = {}
|
||||
|
||||
for repository in repositories:
|
||||
repository = repositories[repository]
|
||||
repository["full_name"] = repository["repository_name"]
|
||||
repository["category"] = repository["repository_type"]
|
||||
if not repository["installed"]:
|
||||
repository["show_beta"] = False
|
||||
else:
|
||||
if repository["version_installed"] is not None:
|
||||
version_type = "version"
|
||||
version_installed = repository["version_installed"]
|
||||
version_available = repository["last_release_tag"]
|
||||
else:
|
||||
version_type = "commit"
|
||||
version_installed = repository["installed_commit"]
|
||||
version_available = repository["last_commit"]
|
||||
if repository["full_name"] != "hacs/integration":
|
||||
installed[repository["repository_name"]] = {
|
||||
"version_type": str(version_type),
|
||||
"version_installed": str(version_installed),
|
||||
"version_available": str(version_available),
|
||||
}
|
||||
|
||||
path = f"{self.system.config_path}/.storage/{STORES['hacs']}"
|
||||
save(self.logger, path, hacs)
|
||||
|
||||
path = f"{self.system.config_path}/.storage/{STORES['repositories']}"
|
||||
save(self.logger, path, repositories)
|
||||
|
||||
path = f"{self.system.config_path}/.storage/{STORES['installed']}"
|
||||
save(self.logger, path, installed)
|
||||
self.logger.info("Migration done")
|
||||
|
||||
|
||||
@register
|
||||
class FromVersion5(Migration):
|
||||
"""Migrate from version 5"""
|
||||
|
||||
from_version = "5"
|
||||
|
||||
def migrate(self):
|
||||
"""Start migration."""
|
||||
self.logger.info("Migration done")
|
||||
@@ -1,16 +0,0 @@
|
||||
"""HACS Startup."""
|
||||
from . import Hacs
|
||||
|
||||
|
||||
class HacsStartup(Hacs):
|
||||
"""Startup class."""
|
||||
|
||||
async def run_startup(self):
|
||||
"""Run startup tasks for HACS."""
|
||||
self.logger.critical("Startup!")
|
||||
if self.configuration.dev:
|
||||
self.logger.critical("Running in DEV mode!")
|
||||
if self.developer.devcontainer:
|
||||
self.logger.critical("Running inside a devcontainer")
|
||||
self.logger.critical("Some features have been disabled")
|
||||
self.logger.critical("It will only fetch one repository pr category")
|
||||
@@ -1,6 +0,0 @@
|
||||
"""HACS Updater."""
|
||||
from . import Hacs
|
||||
|
||||
|
||||
class HacsUpdate(Hacs):
|
||||
"""Update class."""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user