Skip to content

Commit

Permalink
added manual interlock/door lock/unlock buttons (in addition to door …
Browse files Browse the repository at this point in the history
…bump)
  • Loading branch information
jabelone committed Oct 28, 2023
1 parent 969dd5c commit 183a1b6
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 40 deletions.
92 changes: 53 additions & 39 deletions memberportal/access/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,58 +141,72 @@ def log_force_bump(self):
description=f"Device manually bumped.",
)

def log_force_lock(self):
self.log_event(
description=f"Device manually locked.",
)

def log_force_unlock(self):
self.log_event(
description=f"Device manually unlocked.",
)

def sync(self, request=None):
if self.type != "door":
logger.debug(
"Cannot sync device that is not a door (for {})!".format(self.name)
)
return True
logger.info("Sending device sync to channels for {}".format(self.serial_number))

if self.serial_number:
logger.info(
"Sending device sync to channels for {}".format(self.serial_number)
)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
self.serial_number, {"type": "sync_users"}
)

channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
self.serial_number, {"type": "sync_users"}
if request:
request.user.log_event(
f"Sent a sync request to the {self.name} {self._meta.verbose_name}.",
"admin",
)

if request:
request.user.log_event(
f"Sent a sync request to the {self.name} {self._meta.verbose_name}.",
"admin",
)
return True

return True
def lock(self, request=None):
logger.info(f"Sending device lock to channels for {self.name}")

else:
logger.error(
"Cannot sync device without websocket support (for {})!".format(
self.name
)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
self.serial_number, {"type": "device_lock"}
)

if request:
request.user.log_event(
f"Sent a lock request to the {self.name} {self._meta.verbose_name}.",
"admin",
)

def reboot(self, request=None):
if self.serial_number:
logger.info(f"Sending door reboot to channels for {self.name}")
def unlock(self, request=None):
logger.info(f"Sending device unlock to channels for {self.name}")

channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
self.serial_number, {"type": "device_reboot"}
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
self.serial_number, {"type": "device_unlock"}
)

if request:
request.user.log_event(
f"Sent an unlock request to the {self.name} {self._meta.verbose_name}.",
"admin",
)

if request:
request.user.log_event(
f"Sent a reboot request to the {self.name} {self._meta.verbose_name}.",
"admin",
)
def reboot(self, request=None):
logger.info(f"Sending door reboot to channels for {self.name}")

else:
logger.error(
"Cannot reboot device without websocket support (for {})!".format(
self.name
)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
self.serial_number, {"type": "device_reboot"}
)

if request:
request.user.log_event(
f"Sent a reboot request to the {self.name} {self._meta.verbose_name}.",
"admin",
)

def get_tags(self):
Expand Down
10 changes: 10 additions & 0 deletions memberportal/api_access/consumers.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ def device_reboot(self, event=None):
logger.info("Rebooting device for " + self.device.serial_number)
self.send_json({"command": "reboot"})

def device_lock(self, event=None):
# Handles the "device_lock" event when it's sent to us.
logger.info("Locking device for " + self.device.serial_number)
self.send_json({"command": "lock"})

def device_unlock(self, event=None):
# Handles the "device_unlock" event when it's sent to us.
logger.info("Unlocking device for " + self.device.serial_number)
self.send_json({"command": "unlock"})

def update_device_locked_out(self, event=None):
self.check_authorised()

Expand Down
20 changes: 20 additions & 0 deletions memberportal/api_access/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,24 @@
views.BumpDoor.as_view(),
name="BumpDoor",
),
path(
"api/access/doors/<int:door_id>/lock/",
views.LockDevice.as_view(),
name="LockDoor",
),
path(
"api/access/doors/<int:door_id>/unlock/",
views.UnlockDevice.as_view(),
name="UnlockDoor",
),
path(
"api/access/interlocks/<int:interlock_id>/lock/",
views.LockDevice.as_view(),
name="LockInterlock",
),
path(
"api/access/interlocks/<int:interlock_id>/unlock/",
views.UnlockDevice.as_view(),
name="UnlockInterlock",
),
]
52 changes: 52 additions & 0 deletions memberportal/api_access/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,55 @@ def post(self, request, door_id):
{"success": False, "error": "This API is disabled in the config."},
status=status.HTTP_403_FORBIDDEN,
)


class LockDevice(APIView):
"""
post: This method will 'lock' the specified device. Note this MAY be called externally with an API key.
"""

permission_classes = (HasExternalAccessControlAPIKey | permissions.IsAdminUser,)

def post(self, request, door_id=None, interlock_id=None):
# at this point the credentials been authorised by the permissions classes above
# BUT we still need to check if the API is enabled or it's a user making the request
if config.ENABLE_DOOR_BUMP_API or request.user.is_authenticated:
device = (
Doors.objects.get(pk=door_id)
if door_id
else Interlock.objects.get(pk=interlock_id)
)
locked = device.lock(request)
device.log_force_lock()
return Response({"success": locked})
else:
return Response(
{"success": False, "error": "This API is disabled in the config."},
status=status.HTTP_403_FORBIDDEN,
)


class UnlockDevice(APIView):
"""
post: This method will 'unlock' the specified device. Note this MAY be called externally with an API key.
"""

permission_classes = (HasExternalAccessControlAPIKey | permissions.IsAdminUser,)

def post(self, request, door_id=None, interlock_id=None):
# at this point the credentials been authorised by the permissions classes above
# BUT we still need to check if the API is enabled or it's a user making the request
if config.ENABLE_DOOR_BUMP_API or request.user.is_authenticated:
device = (
Doors.objects.get(pk=door_id)
if door_id
else Interlock.objects.get(pk=interlock_id)
)
unlocked = device.unlock(request)
device.log_force_unlock()
return Response({"success": unlocked})
else:
return Response(
{"success": False, "error": "This API is disabled in the config."},
status=status.HTTP_403_FORBIDDEN,
)
69 changes: 68 additions & 1 deletion src-frontend/src/components/AdminTools/DeviceDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,34 @@
</div>

<div class="row">
<q-btn
:disable="unlockLoading"
:loading="unlockLoading"
class="q-mr-sm"
size="sm"
color="accent"
@click.stop="unlockDevice()"
>
<q-icon :name="icons.unlock" />
<q-tooltip>
{{ $t('device.unlock') }}
</q-tooltip>
</q-btn>

<q-btn
:disable="lockLoading"
:loading="lockLoading"
class="q-mr-sm"
size="sm"
color="accent"
@click.stop="lockDevice()"
>
<q-icon :name="icons.lock" />
<q-tooltip>
{{ $t('device.lock') }}
</q-tooltip>
</q-btn>

<q-btn
:disable="rebootLoading"
:loading="rebootLoading"
Expand Down Expand Up @@ -281,6 +309,8 @@ export default {
removeLoading: false,
syncLoading: false,
rebootLoading: false,
lockLoading: false,
unlockLoading: false,
loading: false,
errorLoading: false,
updateInterval: null,
Expand Down Expand Up @@ -346,7 +376,44 @@ export default {
initForm() {
this.device = this.currentDevice;
},
unlockDevice() {
this.unlockLoading = true;
this.$axios
.post(`/api/access/${this.deviceType}/${this.deviceId}/unlock/`)
.then(() => {
this.$q.notify({
message: this.$t('device.unlocked'),
});
})
.catch(() => {
this.$q.dialog({
title: this.$t('error.error'),
message: this.$t('device.requestFailed'),
});
})
.finally(() => {
this.unlockLoading = false;
});
},
lockDevice() {
this.lockLoading = true;
this.$axios
.post(`/api/access/${this.deviceType}/${this.deviceId}/lock/`)
.then(() => {
this.$q.notify({
message: this.$t('device.locked'),
});
})
.catch(() => {
this.$q.dialog({
title: this.$t('error.error'),
message: this.$t('device.requestFailed'),
});
})
.finally(() => {
this.lockLoading = false;
});
},
rebootDevice() {
this.rebootLoading = true;
this.$axios
Expand Down
4 changes: 4 additions & 0 deletions src-frontend/src/i18n/en-AU/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,9 +460,13 @@ export default {
device: {
sync: 'Sync Device',
reboot: 'Reboot Device',
lock: 'Lock Device',
unlock: 'Unlock Device',
synced: 'Sent sync request',
bumped: 'Sent bump request',
rebooted: 'Sent reboot request',
locked: 'Sent lock request',
unlocked: 'Sent unlock request',
requestFailed: 'Failed to send request to device',
},
paymentPlans: {
Expand Down
2 changes: 2 additions & 0 deletions src-frontend/src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ export default {
accessCard: 'mdi-card-account-details',
doorOpen: 'mdi-door-open',
interlock: 'mdi-tools',
lock: 'mdi-lock-outline',
unlock: 'mdi-lock-open-variant-outline',
};

0 comments on commit 183a1b6

Please sign in to comment.