diff --git a/modular_skyrat/modules/blueshield/code/devices/crew.dm b/modular_skyrat/modules/blueshield/code/devices/crew.dm
new file mode 100644
index 00000000000..fe15297f8f2
--- /dev/null
+++ b/modular_skyrat/modules/blueshield/code/devices/crew.dm
@@ -0,0 +1,133 @@
+/*
+* Trimmed and modified copy of ".../machinery/computer/crew.dm"
+* for the sake of modularity. (Blueshield Monitor Console soon?)
+*/
+
+#define SENSORS_UPDATE_PERIOD (10 SECONDS)
+
+GLOBAL_DATUM_INIT(blueshield_crewmonitor, /datum/crewmonitor/blueshield, new)
+
+//list of all Command/CC jobs
+/datum/crewmonitor/blueshield
+ var/list/jobs_command = list(
+ JOB_CAPTAIN = 00,
+ JOB_HEAD_OF_SECURITY = 10,
+ JOB_CHIEF_MEDICAL_OFFICER = 20,
+ JOB_RESEARCH_DIRECTOR = 30,
+ JOB_CHIEF_ENGINEER = 40,
+ JOB_QUARTERMASTER = 50,
+ JOB_HEAD_OF_PERSONNEL = 60,
+ JOB_CENTCOM_ADMIRAL = 200,
+ JOB_CENTCOM = 201,
+ JOB_CENTCOM_OFFICIAL = 210,
+ JOB_CENTCOM_COMMANDER = 211,
+ JOB_CENTCOM_BARTENDER = 212,
+ JOB_CENTCOM_CUSTODIAN = 213,
+ JOB_CENTCOM_MEDICAL_DOCTOR = 214,
+ JOB_CENTCOM_RESEARCH_OFFICER = 215,
+ JOB_ERT_COMMANDER = 220,
+ JOB_ERT_OFFICER = 221,
+ JOB_ERT_ENGINEER = 222,
+ JOB_ERT_MEDICAL_DOCTOR = 223,
+ JOB_ERT_CLOWN = 224,
+ JOB_ERT_CHAPLAIN = 225,
+ JOB_ERT_JANITOR = 226,
+ JOB_ERT_DEATHSQUAD = 227,
+ JOB_NT_REP = 230,
+ JOB_BLUESHIELD = 231,
+ )
+
+/datum/crewmonitor/blueshield/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "CrewConsoleSkyratBlueshield")
+ ui.open()
+
+/*
+* Override of crewmonitor/update_data(z)
+* - "trim_assignment" is now iterated for command-only jobs
+* - "if (id_card)" now encapsulates all the remaining checks to avoid showing unknowns
+*/
+/datum/crewmonitor/blueshield/update_data(z)
+ if(data_by_z["[z]"] && last_update["[z]"] && world.time <= last_update["[z]"] + SENSORS_UPDATE_PERIOD)
+ return data_by_z["[z]"]
+
+ var/list/results = list()
+ for(var/tracked_mob in GLOB.suit_sensors_list)
+ if(!tracked_mob)
+ stack_trace("Null entry in suit sensors list.")
+ continue
+
+ var/mob/living/tracked_living_mob = tracked_mob
+
+ var/turf/pos = get_turf(tracked_living_mob)
+
+ if(!pos)
+ stack_trace("Tracked mob has no loc and is likely in nullspace: [tracked_living_mob] ([tracked_living_mob.type])")
+ continue
+
+ if(pos.z != z && (!is_station_level(pos.z) || !is_station_level(z)) && !HAS_TRAIT(tracked_living_mob, TRAIT_MULTIZ_SUIT_SENSORS))
+ continue
+
+ var/mob/living/carbon/human/tracked_human = tracked_living_mob
+
+ if(!ishuman(tracked_human))
+ stack_trace("Non-human mob is in suit_sensors_list: [tracked_living_mob] ([tracked_living_mob.type])")
+ continue
+
+ var/obj/item/clothing/under/uniform = tracked_human.w_uniform
+ if (!istype(uniform))
+ stack_trace("Human without a suit sensors compatible uniform is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform?.type])")
+ continue
+
+ if((uniform.has_sensor <= NO_SENSORS) || !uniform.sensor_mode)
+ stack_trace("Human without active suit sensors is in suit_sensors_list: [tracked_human] ([tracked_human.type]) ([uniform.type])")
+ continue
+
+ var/sensor_mode = uniform.sensor_mode
+ var/list/entry = list()
+
+ var/obj/item/card/id/id_card = tracked_living_mob.get_idcard(hand_first = FALSE)
+ if (id_card)
+
+ entry["name"] = id_card.registered_name
+ entry["assignment"] = id_card.assignment
+ var/trim_assignment = id_card.get_trim_assignment()
+
+ //Check if they are command
+ if (jobs_command[trim_assignment] != null)
+ entry["ijob"] = jobs_command[trim_assignment]
+ else
+ continue
+
+ if (issynthetic(tracked_human))
+ entry["is_robot"] = TRUE
+
+ if (sensor_mode >= SENSOR_LIVING)
+ entry["life_status"] = (tracked_living_mob.stat != DEAD)
+
+ if (sensor_mode >= SENSOR_VITALS)
+ entry += list(
+ "oxydam" = round(tracked_living_mob.getOxyLoss(), 1),
+ "toxdam" = round(tracked_living_mob.getToxLoss(), 1),
+ "burndam" = round(tracked_living_mob.getFireLoss(), 1),
+ "brutedam" = round(tracked_living_mob.getBruteLoss(), 1),
+ "health" = round(tracked_living_mob.health, 1),
+ )
+
+ if (sensor_mode >= SENSOR_COORDS)
+ entry["area"] = get_area_name(tracked_living_mob, format_text = TRUE)
+
+ entry["can_track"] = tracked_living_mob.can_track()
+
+ else
+ continue
+
+ results[++results.len] = entry
+
+ data_by_z["[z]"] = results
+ last_update["[z]"] = world.time
+
+ return results
+
+#undef SENSORS_UPDATE_PERIOD
diff --git a/modular_skyrat/modules/blueshield/code/devices/sensor_device.dm b/modular_skyrat/modules/blueshield/code/devices/sensor_device.dm
new file mode 100644
index 00000000000..f9fffbc1d5b
--- /dev/null
+++ b/modular_skyrat/modules/blueshield/code/devices/sensor_device.dm
@@ -0,0 +1,8 @@
+/obj/item/sensor_device/blueshield
+ name = "blueshield's handheld monitor"
+ desc = "A unique model of handheld crew monitor that seems to have been customized for Executive Protection purposes."
+ icon = 'modular_skyrat/modules/blueshield/icons/device.dmi'
+ icon_state = "blueshield_scanner"
+
+/obj/item/sensor_device/blueshield/attack_self(mob/user)
+ GLOB.blueshield_crewmonitor.show(user,src)
diff --git a/modular_skyrat/modules/blueshield/code/medkit.dm b/modular_skyrat/modules/blueshield/code/medkit.dm
index bf50bdbf8c4..3939188f699 100644
--- a/modular_skyrat/modules/blueshield/code/medkit.dm
+++ b/modular_skyrat/modules/blueshield/code/medkit.dm
@@ -2,6 +2,7 @@
name = "blueshield combat medical kit"
desc = "Blue boy to the rescue!"
color = "#AAAAFF"
+
/obj/item/storage/medkit/tactical/blueshield/PopulateContents()
if(empty)
return
@@ -9,4 +10,4 @@
new /obj/item/stack/medical/bruise_pack(src)
new /obj/item/stack/medical/ointment(src)
new /obj/item/healthanalyzer(src)
- new /obj/item/pinpointer/crew(src)
+ new /obj/item/sensor_device/blueshield(src)
diff --git a/modular_skyrat/modules/blueshield/icons/device.dmi b/modular_skyrat/modules/blueshield/icons/device.dmi
new file mode 100644
index 00000000000..e079de6c160
Binary files /dev/null and b/modular_skyrat/modules/blueshield/icons/device.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 4c1b5dd2387..f7d96d9b48d 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -5698,6 +5698,8 @@
#include "modular_skyrat\modules\blueshield\code\landmarks.dm"
#include "modular_skyrat\modules\blueshield\code\medkit.dm"
#include "modular_skyrat\modules\blueshield\code\weapons.dm"
+#include "modular_skyrat\modules\blueshield\code\devices\crew.dm"
+#include "modular_skyrat\modules\blueshield\code\devices\sensor_device.dm"
#include "modular_skyrat\modules\bluespace_miner\code\bluespace_miner.dm"
#include "modular_skyrat\modules\bodyparts\code\_mutant_bodyparts.dm"
#include "modular_skyrat\modules\bodyparts\code\akula_bodyparts.dm"
diff --git a/tgui/packages/tgui/interfaces/CrewConsoleSkyratBlueshield.js b/tgui/packages/tgui/interfaces/CrewConsoleSkyratBlueshield.js
new file mode 100644
index 00000000000..2d13e59d150
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CrewConsoleSkyratBlueshield.js
@@ -0,0 +1,199 @@
+import { sortBy } from 'common/collections';
+import { useBackend } from '../backend';
+import { Box, Button, Section, Table, Icon } from '../components';
+import { COLORS } from '../constants';
+import { Window } from '../layouts';
+
+const HEALTH_COLOR_BY_LEVEL = [
+ '#17d568',
+ '#c4cf2d',
+ '#e67e22',
+ '#ed5100',
+ '#e74c3c',
+ '#801308',
+];
+const HEALTH_ICON_BY_LEVEL = [
+ 'heart',
+ 'heart',
+ 'heart',
+ 'heart',
+ 'heartbeat',
+ 'skull',
+];
+const jobIsHead = (jobId) => jobId % 10 === 0;
+
+const jobToColor = (jobId) => {
+ if (jobId === 0) {
+ return COLORS.department.captain;
+ }
+ if (jobId >= 10 && jobId < 20) {
+ return COLORS.department.security;
+ }
+ if (jobId >= 20 && jobId < 30) {
+ return COLORS.department.medbay;
+ }
+ if (jobId >= 30 && jobId < 40) {
+ return COLORS.department.science;
+ }
+ if (jobId >= 40 && jobId < 50) {
+ return COLORS.department.engineering;
+ }
+ if (jobId >= 50 && jobId < 60) {
+ return COLORS.department.cargo;
+ }
+ if (jobId >= 60 && jobId < 80) {
+ return COLORS.department.service;
+ }
+ if (jobId >= 200 && jobId < 240) {
+ return COLORS.department.centcom;
+ }
+ return COLORS.department.other;
+};
+
+const healthToAttribute = (oxy, tox, burn, brute, attributeList) => {
+ const healthSum = oxy + tox + burn + brute;
+ const level = Math.min(Math.max(Math.ceil(healthSum / 50), 0), 5);
+ // 200 Default Health, Sum Divided by 50, 6 Health States
+ return attributeList[level];
+};
+
+const HealthStat = (props) => {
+ const { type, value } = props;
+ return (
+