257 lines
14 KiB
PHP
257 lines
14 KiB
PHP
<?php // phpcs:ignoreFile
|
|
|
|
require __DIR__ . '/session.php';
|
|
require __DIR__ . '/userAccess.php';
|
|
|
|
if (($_SESSION['SESS_MEMBER_LEVEL'] ?? '') !== 'controls') {
|
|
header('Location: access-denied.php');
|
|
exit;
|
|
}
|
|
|
|
require __DIR__ . '/includes/control-tags.php';
|
|
|
|
$pageTitle = 'Tag Overrides';
|
|
$pageSubtitle = 'Adjust live values with safety guardrails';
|
|
$pageDescription = 'Tags are defined in config/control-tags.php and write straight through the Kepware IoT Gateway.';
|
|
|
|
if (empty($_SESSION['control_settings_csrf'] ?? '')) {
|
|
$_SESSION['control_settings_csrf'] = bin2hex(random_bytes(32));
|
|
}
|
|
$controlCsrfToken = $_SESSION['control_settings_csrf'];
|
|
|
|
$tagOptions = [];
|
|
$tagTypes = [];
|
|
$configError = null;
|
|
$configErrorDetail = null;
|
|
|
|
try {
|
|
$normalisedTags = control_tags_normalize();
|
|
|
|
foreach ($normalisedTags as $tag) {
|
|
$tagOptions[] = $tag;
|
|
|
|
$typeKey = $tag['type_key'];
|
|
if (!isset($tagTypes[$typeKey])) {
|
|
$tagTypes[$typeKey] = [
|
|
'label' => $tag['type_label'],
|
|
'count' => 0,
|
|
];
|
|
}
|
|
|
|
$tagTypes[$typeKey]['count']++;
|
|
}
|
|
|
|
uasort(
|
|
$tagTypes,
|
|
static function (array $left, array $right): int {
|
|
return strcasecmp($left['label'], $right['label']);
|
|
}
|
|
);
|
|
} catch (Throwable $exception) {
|
|
$configError = 'Unable to load tag configuration.';
|
|
$configErrorDetail = $exception->getMessage();
|
|
error_log('Control tag configuration error: ' . $configErrorDetail);
|
|
}
|
|
|
|
$hasTags = $configError === null && count($tagOptions) > 0;
|
|
|
|
require __DIR__ . '/includes/layout/header.php';
|
|
require __DIR__ . '/menuinclude.php';
|
|
?>
|
|
|
|
<div class="app-content">
|
|
<section class="data-panel">
|
|
<div
|
|
class="control-app"
|
|
data-control-app
|
|
data-get-url="data/tag-control-get.php"
|
|
data-update-url="data/tag-control-update.php"
|
|
data-csrf="<?php echo htmlspecialchars($controlCsrfToken); ?>"
|
|
>
|
|
<header class="control-app__header">
|
|
<div>
|
|
<h2>Manage live tag overrides</h2>
|
|
<p>Select a tag, review the live value, then stage a tweak or toggle the state. Writes commit instantly through Kepware.</p>
|
|
</div>
|
|
<div class="control-app__selector">
|
|
<div class="control-app__selector-grid">
|
|
<div class="control-app__selector-field">
|
|
<label for="control-type-select">Tag type</label>
|
|
<select
|
|
id="control-type-select"
|
|
name="tag_type"
|
|
data-control-type-filter
|
|
<?php echo $hasTags ? '' : 'disabled'; ?>
|
|
>
|
|
<option value="">All types<?php echo $hasTags ? '' : ' (0)'; ?></option>
|
|
<?php foreach ($tagTypes as $typeKey => $typeMeta) : ?>
|
|
<option value="<?php echo htmlspecialchars($typeKey); ?>">
|
|
<?php echo htmlspecialchars($typeMeta['label']); ?> (<?php echo (int) $typeMeta['count']; ?>)
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="control-app__selector-field">
|
|
<label for="control-tag-select">Tag</label>
|
|
<select
|
|
id="control-tag-select"
|
|
name="tag"
|
|
data-control-select
|
|
<?php echo $hasTags ? '' : 'disabled'; ?>
|
|
>
|
|
<?php if (!$hasTags) : ?>
|
|
<option value="" data-type-key="">No tags available</option>
|
|
<?php else : ?>
|
|
<option value="" data-type-key="">Select a tag…</option>
|
|
<?php foreach ($tagOptions as $option) :
|
|
$config = $option['config'] ?? [];
|
|
$sourceId = $config['source'] ?? $config['idnumber'] ?? $option['id'];
|
|
$typeRaw = isset($config['type']) && is_string($config['type']) ? $config['type'] : null;
|
|
$stepAttr = isset($option['step']) && $option['step'] !== null ? (string) $option['step'] : '';
|
|
$minAttr = isset($option['min']) && $option['min'] !== null ? (string) $option['min'] : '';
|
|
$maxAttr = isset($option['max']) && $option['max'] !== null ? (string) $option['max'] : '';
|
|
$precisionAttr = isset($option['precision']) && $option['precision'] !== null ? (string) $option['precision'] : '';
|
|
$unitsAttr = isset($option['units']) && $option['units'] !== null ? $option['units'] : '';
|
|
$descriptionAttr = isset($option['description']) && $option['description'] !== null ? $option['description'] : '';
|
|
?>
|
|
<option
|
|
value="<?php echo htmlspecialchars($option['id']); ?>"
|
|
data-type-key="<?php echo htmlspecialchars($option['type_key']); ?>"
|
|
data-type-label="<?php echo htmlspecialchars($option['type_label']); ?>"
|
|
data-control-flag="1"
|
|
data-source-idnumber="<?php echo htmlspecialchars((string) $sourceId); ?>"
|
|
data-type-raw="<?php echo htmlspecialchars((string) $typeRaw); ?>"
|
|
data-control-mode="<?php echo htmlspecialchars($option['control_mode']); ?>"
|
|
data-step-size="<?php echo htmlspecialchars($stepAttr); ?>"
|
|
data-min-value="<?php echo htmlspecialchars($minAttr); ?>"
|
|
data-max-value="<?php echo htmlspecialchars($maxAttr); ?>"
|
|
data-precision="<?php echo htmlspecialchars($precisionAttr); ?>"
|
|
data-units="<?php echo htmlspecialchars($unitsAttr); ?>"
|
|
data-description="<?php echo htmlspecialchars($descriptionAttr); ?>"
|
|
>
|
|
<?php echo htmlspecialchars($option['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<?php if ($configError !== null) : ?>
|
|
<div class="control-app__status" data-status data-status-type="error" role="alert">
|
|
<?php echo htmlspecialchars($configError); ?>
|
|
<?php if ($configErrorDetail !== null) : ?>
|
|
<details class="control-app__status-details">
|
|
<summary>Technical details</summary>
|
|
<pre><?php echo htmlspecialchars($configErrorDetail); ?></pre>
|
|
</details>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php elseif (!$hasTags) : ?>
|
|
<div class="control-app__status" data-status-type="info" role="status">
|
|
<strong>No control-enabled tags found.</strong>
|
|
<p>
|
|
Add entries to <code>config/control-tags.php</code> to expose Kepware tags for override access. Each entry
|
|
should define a friendly <code>name</code>, a Kepware <code>tag</code> (or array key), and optional guardrails
|
|
such as <code>step</code>, <code>min</code>, or <code>max</code>.
|
|
</p>
|
|
</div>
|
|
<?php else : ?>
|
|
<div class="control-app__body">
|
|
<div class="control-app__primary">
|
|
<div class="control-app__value">
|
|
<span class="control-app__value-label" data-value-label>Current value</span>
|
|
<span class="control-app__value-reading" data-current-value>—</span>
|
|
<span class="control-app__timestamp" data-current-timestamp></span>
|
|
<span class="control-app__value-pending" data-pending-indicator hidden>Pending</span>
|
|
</div>
|
|
|
|
<div class="control-app__controls">
|
|
<div class="control-app__numeric" data-numeric-controls>
|
|
<div class="control-app__controls-row">
|
|
<label for="control-step-input">Step adjustment</label>
|
|
<div class="control-app__step">
|
|
<input
|
|
id="control-step-input"
|
|
class="control-app__set-input"
|
|
type="number"
|
|
name="step"
|
|
value="1"
|
|
min="0"
|
|
step="0.1"
|
|
data-step-input
|
|
>
|
|
<div class="control-app__step-actions">
|
|
<button type="button" class="button button--danger" data-action="decrease">
|
|
− Step
|
|
</button>
|
|
<button type="button" class="button button--success" data-action="increase">
|
|
+ Step
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-app__controls-row">
|
|
<label for="control-set-input">Pending value</label>
|
|
<div class="control-app__set" data-set-form>
|
|
<input
|
|
id="control-set-input"
|
|
class="control-app__set-input"
|
|
type="number"
|
|
step="0.1"
|
|
data-set-input
|
|
>
|
|
<button type="button" class="button button--ghost" data-action="apply">
|
|
Apply
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-app__boolean" data-boolean-controls hidden>
|
|
<div class="control-app__controls-row">
|
|
<span class="control-app__hint">Toggle state</span>
|
|
<div class="control-app__boolean-actions">
|
|
<button type="button" class="button button--danger" data-boolean="off">
|
|
Off
|
|
</button>
|
|
<button type="button" class="button button--success" data-boolean="on">
|
|
On
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-app__controls-row control-app__controls-row--aux">
|
|
<button type="button" class="button button--ghost" data-action="refresh">
|
|
Refresh value
|
|
</button>
|
|
<span class="control-app__hint">Use refresh after out-of-band changes to confirm the live reading.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="control-app__aside">
|
|
<h3>Control checklist</h3>
|
|
<ul>
|
|
<li>Tag metadata lives in <code>config/control-tags.php</code>—update that file to curate what appears here.</li>
|
|
<li>Writes go directly to Kepware via the IoT Gateway REST API; results return in real time.</li>
|
|
<li>Every change is logged to the <code>control_write_log</code> table for traceability.</li>
|
|
<li>Numeric edits stage first; press <em>Apply</em> to commit. Boolean tags toggle immediately.</li>
|
|
</ul>
|
|
</aside>
|
|
</div>
|
|
|
|
<div class="control-app__status" data-status hidden></div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<script src="assets/js/tag-controls.js?v=<?php echo rawurlencode($assetVersion ?? '2025.10.06.2'); ?>"></script>
|
|
|
|
<?php require __DIR__ . '/includes/layout/footer.php'; ?>
|