diff --git a/backend/src/handler.rs b/backend/src/handler.rs index 44fe947..fb8b64d 100644 --- a/backend/src/handler.rs +++ b/backend/src/handler.rs @@ -49,7 +49,7 @@ pub async fn batch_edit_value_handler( lock.dmx.colors[i.address] = i.value; match db.static_state.change_channel.send(DMXAtom::Color(*i)) { Ok(_) => (), - Err(_) => (), + Err(e) => tracing::error!("Channel error: {e:?}"), } } lock.ratelimit_info.insert(user, Instant::now()); @@ -63,7 +63,11 @@ pub async fn get_value_handler( ) -> Result { let lock = db.mut_state.read().await; check_id(id, &lock.dmx.colors)?; - Ok(Json(lock.dmx.colors[id])) + if lock.dmx.motor.black_button { + Ok(Json(lock.dmx.motor.bump_color)) + } else { + Ok(Json(lock.dmx.colors[id])) + } } #[debug_handler] @@ -72,6 +76,7 @@ pub async fn edit_value_handler( State(db): State, Json(body): Json, ) -> Result<(), StatusCode> { + tracing::trace!("Edit color {body:?}"); let mut lock = db.mut_state.write().await; check_id(id, &lock.dmx.colors)?; lock.dmx.colors[id] = body; @@ -81,7 +86,7 @@ pub async fn edit_value_handler( .send(DMXAtom::Color(DMXColorAtom::new(id, body))) { Ok(_) => (), - Err(_) => (), + Err(e) => tracing::error!("Channel error: {e:?}"), }; Ok(()) @@ -101,15 +106,45 @@ pub async fn edit_motor_value_handler( Json(body): Json, ) -> Result<(), StatusCode> { let mut lock = db.mut_state.write().await; + tracing::trace!("Edit motor {body:?}"); + if let Some(new) = body.black_button { + if new != lock.dmx.motor.black_button { + for (i, x) in lock + .dmx + .colors + .clone() + .into_iter() + .enumerate() + { + match db + .static_state + .change_channel + .send(DMXAtom::Color(DMXColorAtom { + address: i, + value: if new { lock.dmx.motor.bump_color } else { x }, + })) { + Ok(_) => (), + Err(e) => tracing::error!("Channel error: {e:?}"), + } + }; + } + } lock.dmx.motor = DMXBeam { pan: body.pan.unwrap_or(lock.dmx.motor.pan), tilt: body.tilt.unwrap_or(lock.dmx.motor.tilt), focus: body.focus.unwrap_or(lock.dmx.motor.focus), + white_button: body.white_button.unwrap_or(lock.dmx.motor.white_button), + black_button: body.black_button.unwrap_or(lock.dmx.motor.black_button), + bump_color: body.bump_color.unwrap_or(lock.dmx.motor.bump_color), }; - let _ = db + match db .static_state .change_channel - .send(DMXAtom::Motor(lock.dmx.motor)); + .send(DMXAtom::Motor(lock.dmx.motor)) + { + Ok(_) => (), + Err(e) => tracing::error!("Channel error: {e:?}"), + } Ok(()) } @@ -127,10 +162,14 @@ pub async fn sse_handler(State(db): State) -> impl IntoResponse { let init_data = data .into_iter() .enumerate() - .map(|(i, x)| { + .map(move |(i, x)| { Ok(DMXAtom::Color(DMXColorAtom { address: i, - value: x, + value: if motor.black_button { + motor.bump_color + } else { + x + }, })) }) .chain(std::iter::once(Ok(DMXAtom::Motor(motor)))); diff --git a/backend/src/model.rs b/backend/src/model.rs index 0f4e3df..91f159d 100644 --- a/backend/src/model.rs +++ b/backend/src/model.rs @@ -22,6 +22,9 @@ pub struct DMXBeam { pub pan: u8, pub tilt: u8, pub focus: u8, + pub white_button: u8, + pub black_button: bool, + pub bump_color: DMXColor, } #[derive(Debug, Default, Deserialize, Serialize, Copy, Clone)] @@ -29,6 +32,9 @@ pub struct DMXBeamChange { pub pan: Option, pub tilt: Option, pub focus: Option, + pub white_button: Option, + pub black_button: Option, + pub bump_color: Option, } pub type ColorArray = Vec; @@ -114,7 +120,7 @@ pub type DB = Arc; pub fn make_db() -> DB { let state_size: usize = - usize::from_str(&dotenvy::var("NB_DMX_VALUES").unwrap_or(String::from("100"))).unwrap(); + usize::from_str(&dotenvy::var("NB_DMX_VALUES").unwrap_or(String::from("130"))).unwrap(); let save_path = dotenvy::var("BK_FILE").unwrap_or(String::from("data.json")); let mut_state = RwLock::new(AppState::new(state_size, &save_path)); Arc::new(SharedState { diff --git a/frontend/frontend/mixins.py b/frontend/frontend/mixins.py new file mode 100644 index 0000000..ef237a5 --- /dev/null +++ b/frontend/frontend/mixins.py @@ -0,0 +1,20 @@ +from django.core.exceptions import PermissionDenied + + +class GroupRequiredMixin(object): + """ + group_required - list of strings, required param + """ + + group_required = None + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + raise PermissionDenied + else: + user_groups = [] + for group in request.user.groups.values_list("name", flat=True): + user_groups.append(group) + if len(set(user_groups).intersection(self.group_required)) <= 0: + raise PermissionDenied + return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs) diff --git a/frontend/frontend/static/js/update-color.js b/frontend/frontend/static/js/update-color.js index ae84628..5802087 100644 --- a/frontend/frontend/static/js/update-color.js +++ b/frontend/frontend/static/js/update-color.js @@ -21,13 +21,23 @@ let startSocket = () => { socket.addEventListener("message", (event) => { const message = JSON.parse(event.data); - const color = rgbToHex(message.value); - console.log(color, `light${message.address}`); - const element = document.getElementById(`light${message.address}`); + let color; + let address; + if(message.type === "Motor") { + console.log(message); + color = rgbToHex(message.bump_color); + address = 300; + + } else { + color = rgbToHex(message.value); + address = message.address; + } + const element = document.getElementById(`light${address}`); + const inputElement = document.getElementById(`input-light${address}`); + console.log(color, `light${address}`); if(element !== null) { element.style.fill = color; } - const inputElement = document.getElementById(`input-light${message.address}`); if(inputElement !== null) { inputElement.value = color; } @@ -113,4 +123,36 @@ document.addEventListener('DOMContentLoaded', () => { }); }); }); + + // Add a click event to save colors + (document.querySelectorAll('.save-bump-color') || []).forEach(($trigger) => { + const lightId = $trigger.dataset.target; + const $input = document.getElementById(`input-light${lightId}`); + const url = `${WEBSOCKET_ENDPOINT}/control-box`; + + $trigger.addEventListener('click', async (e) => { + e.preventDefault(); + const color = hexToRgb($input.value); + openModal($loadingModal) + await fetch(url, { + method: 'POST', + //mode: "no-cors", + headers: { + 'Accept': "application/json", + 'Content-Type': "application/json", + 'Authorization': `Bearer ${JWT}` + }, + body: JSON.stringify({ "bump_color": {"red": color.r, "green": color.g, "blue": color.b}}), + }).then((resp) => { + if(!resp.ok) { + alert(`Request failed. Err code:${resp.status}`); + } + }).catch((e) => { + alert(`Request failed. Err: ${e}`); + console.log(e); + }).finally(() => { + closeAllModals(); + }); + }); + }); }); diff --git a/frontend/frontend/templates/frontend/base.html b/frontend/frontend/templates/frontend/base.html index 9406907..f1c7bcc 100644 --- a/frontend/frontend/templates/frontend/base.html +++ b/frontend/frontend/templates/frontend/base.html @@ -1,4 +1,5 @@ {% load static %} +{% load has_group %} @@ -27,6 +28,11 @@
  • API
  • + {% if user|has_group:"cof" %} +
  • + Cof only +
  • + {% endif %} {% if user.is_staff %}
  • Admin diff --git a/frontend/frontend/templates/frontend/bump.html b/frontend/frontend/templates/frontend/bump.html new file mode 100644 index 0000000..6a10435 --- /dev/null +++ b/frontend/frontend/templates/frontend/bump.html @@ -0,0 +1,17 @@ +{% extends "frontend/base.html" %} +{% load static %} +{% block content %} +
    +

    Couleur du bump

    +
    + + +
    +
    + +
    +
    + + + +{% endblock %} diff --git a/frontend/frontend/templates/frontend/home.html b/frontend/frontend/templates/frontend/home.html index 915ee4b..8264cd0 100644 --- a/frontend/frontend/templates/frontend/home.html +++ b/frontend/frontend/templates/frontend/home.html @@ -1877,7 +1877,7 @@ xlink:href="/light/blinder"> + transform="matrix(0.22165795,0,0,0.22165795,46.4358,25.843397)"> + + + + + + + + + + + + + + + + + + diff --git a/frontend/frontend/templatetags/__init__.py b/frontend/frontend/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/frontend/templatetags/has_group.py b/frontend/frontend/templatetags/has_group.py new file mode 100644 index 0000000..a41b4b1 --- /dev/null +++ b/frontend/frontend/templatetags/has_group.py @@ -0,0 +1,8 @@ +from django import template + +register = template.Library() + + +@register.filter(name="has_group") +def has_group(user, group_name): + return user.groups.filter(name=group_name).exists() diff --git a/frontend/frontend/urls.py b/frontend/frontend/urls.py index 3e0018e..36a5c66 100644 --- a/frontend/frontend/urls.py +++ b/frontend/frontend/urls.py @@ -1,10 +1,11 @@ from django.urls import path -from .views import HomeView, LightView, TokenView +from .views import BumpView, HomeView, LightView, TokenView app_name = "frontend" urlpatterns = [ path("", HomeView.as_view(), name="home"), path("light//", LightView.as_view()), + path("bump", BumpView.as_view(), name="bump"), path("token", TokenView.as_view()), ] diff --git a/frontend/frontend/views.py b/frontend/frontend/views.py index 95c7062..5776447 100644 --- a/frontend/frontend/views.py +++ b/frontend/frontend/views.py @@ -5,6 +5,7 @@ from django.http import Http404, JsonResponse from django.views import View from django.views.generic.base import TemplateView +from .mixins import GroupRequiredMixin from .utils import craft_token @@ -39,10 +40,15 @@ def get_context_from_proj(kind, chans): class TokenView(LoginRequiredMixin, View): def get(self, request, *arg, **kwargs): - return JsonResponse(craft_token(self.request.user.username, self.request.user.groups.filter(name="cof").exists())) + return JsonResponse( + craft_token( + self.request.user.username, + self.request.user.groups.filter(name="cof").exists(), + ) + ) -class LightView(TemplateView): +class LightView(LoginRequiredMixin, TemplateView): def get_template_names(self): lights = settings.LIGHTS["lights"][self.kwargs["light"]] return [f"frontend/{lights['kind']}.html"] @@ -50,7 +56,10 @@ class LightView(TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.user.is_authenticated: - context["jwt"] = craft_token(self.request.user.username, self.request.user.groups.filter(name="cof").exists())["token"] + context["jwt"] = craft_token( + self.request.user.username, + self.request.user.groups.filter(name="cof").exists(), + )["token"] context["websocket_endpoint"] = settings.WEBSOCKET_ENDPOINT light = self.kwargs["light"] if light not in settings.LIGHTS["lights"]: @@ -62,6 +71,21 @@ class LightView(TemplateView): return context +class BumpView(GroupRequiredMixin, TemplateView): + template_name = "frontend/bump.html" + group_required = ["cof"] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if self.request.user.is_authenticated: + context["jwt"] = craft_token( + self.request.user.username, + self.request.user.groups.filter(name="cof").exists(), + )["token"] + context["websocket_endpoint"] = settings.WEBSOCKET_ENDPOINT + return context + + class HomeView(TemplateView): template_name = "frontend/home.html" diff --git a/frontend/patch.json b/frontend/patch.json index ea47e72..6e56e02 100644 --- a/frontend/patch.json +++ b/frontend/patch.json @@ -44,6 +44,36 @@ "name": "Barre led 4", "kind": "led_tub", "channels" : [68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83] + }, + "barre_led_4": { + "name": "Barre led 5", + "kind": "led_tub", + "channels" : [84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99] + }, + "barre_led_5": { + "name": "Barre led 6", + "kind": "led_tub", + "channels" : [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115] + }, + "spot_pont_0": { + "name": "Ecran-gauche", + "kind": "spot", + "channels": [ 116 ] + }, + "spot_pont_1": { + "name": "Ecran-gauche", + "kind": "spot", + "channels": [ 117 ] + }, + "spot_pont_2": { + "name": "Ecran-gauche", + "kind": "spot", + "channels": [ 118 ] + }, + "spot_pont_3": { + "name": "Ecran-gauche", + "kind": "spot", + "channels": [ 119 ] } } } diff --git a/frontend/ragb/settings.py b/frontend/ragb/settings.py index d164ece..ade9365 100644 --- a/frontend/ragb/settings.py +++ b/frontend/ragb/settings.py @@ -33,7 +33,7 @@ DEV_KEY = "supersecret" WEBSOCKET_PORT = 9999 WEBSOCKET_HOST = "127.0.0.1" -WEBSOCKET_ENDPOINT = f"http://{WEBSOCKET_HOST}:{WEBSOCKET_PORT}/api" +WEBSOCKET_ENDPOINT = "https://agb.hackens.org/api" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True