diff --git a/aw-server/aw-ru-patch.js b/aw-server/aw-ru-patch.js
index 2491d05..92a5669 100755
--- a/aw-server/aw-ru-patch.js
+++ b/aw-server/aw-ru-patch.js
@@ -1,6 +1,6 @@
(function () {
- window.__awRuPatchVersion = "template-v12-activity-heading-ru";
- document.documentElement.setAttribute("data-aw-ru-patch", "template-v12-activity-heading-ru");
+ window.__awRuPatchVersion = "template-v13-pve-audit-day-filter";
+ document.documentElement.setAttribute("data-aw-ru-patch", "template-v13-pve-audit-day-filter");
const exact = new Map([
["ActivityWatch", "АктивВотч"],
@@ -333,6 +333,11 @@
'.aw-ru-host-item-title { font-weight: 600; margin-bottom: 6px; }',
'.aw-ru-host-links { display: flex; flex-wrap: wrap; gap: 6px; }',
'.aw-ru-host-links a { display: inline-block; padding: 4px 8px; border-radius: 999px; background: rgba(90,140,255,.15); text-decoration: none; }',
+ '.aw-ru-worktime-center { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(20,20,20,.03); }',
+ '.aw-ru-worktime-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; margin-bottom: 10px; }',
+ '.aw-ru-worktime-controls label { font-size: 12px; display:flex; flex-direction:column; gap:4px; }',
+ '.aw-ru-worktime-table { width: 100%; border-collapse: collapse; font-size: 13px; }',
+ '.aw-ru-worktime-table th, .aw-ru-worktime-table td { border: 1px solid rgba(120,120,120,.25); padding: 6px 8px; text-align: left; }',
'.aw-ru-pve-audit { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(10,20,40,.04); }',
'.aw-ru-pve-audit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin: 12px 0 16px; }',
'.aw-ru-pve-audit-card { border: 1px solid rgba(120,120,120,.22); border-radius: 8px; padding: 12px; background: rgba(255,255,255,.02); }',
@@ -735,6 +740,23 @@
return !!(match && isPveLikeHost(decodeURIComponent(match[1] || "")));
}
+ function getSelectedDayFromHash() {
+ const hash = window.location.hash || "";
+ const match = hash.match(/^#\/activity\/[^/]+\/day\/([^/?#]+)/i);
+ return match && match[1] ? decodeURIComponent(match[1]) : "";
+ }
+
+ function filterEventsBySelectedDay(events) {
+ const day = getSelectedDayFromHash();
+ if (!day) return Array.isArray(events) ? events : [];
+ const start = new Date(day + "T00:00:00");
+ const end = new Date(day + "T23:59:59.999");
+ return (Array.isArray(events) ? events : []).filter(function (event) {
+ const timestamp = event && event.timestamp ? new Date(event.timestamp) : null;
+ return timestamp && !isNaN(timestamp.getTime()) && timestamp >= start && timestamp <= end;
+ });
+ }
+
function extractHostFromBucket(bucketId, bucketMeta) {
if (bucketMeta && bucketMeta.hostname) return String(bucketMeta.hostname);
const prefixes = [
@@ -1306,10 +1328,10 @@
loadBucketEvents("aw-console-commands_" + host, 50).catch(function () { return []; })
]);
const data = {
- web: webEvents || [],
- tasks: taskEvents || [],
- ssh: sshEvents || [],
- cmd: cmdEvents || []
+ web: filterEventsBySelectedDay(webEvents || []),
+ tasks: filterEventsBySelectedDay(taskEvents || []),
+ ssh: filterEventsBySelectedDay(sshEvents || []),
+ cmd: filterEventsBySelectedDay(cmdEvents || [])
};
center.querySelector("[data-aw-ru-pve-web-count]").textContent = String(data.web.length);
center.querySelector("[data-aw-ru-pve-task-count]").textContent = String(data.tasks.length);
@@ -1584,6 +1606,124 @@
});
}
+ function ms(v) {
+ return new Date(v).getTime();
+ }
+
+ function fmtHours(sec) {
+ return (Math.round((sec / 3600) * 100) / 100).toFixed(2);
+ }
+
+ function rangesIntersect(a0, a1, b0, b1) {
+ return Math.max(a0, b0) < Math.min(a1, b1);
+ }
+
+ function intersectDurationSeconds(a0, a1, b0, b1) {
+ return Math.max(0, (Math.min(a1, b1) - Math.max(a0, b0)) / 1000);
+ }
+
+ async function renderPersonalWorktime(host, fromIso, toIso, target) {
+ const sessionBucket = "aw-worktime-sessions_" + host;
+ const windowBucket = "aw-watcher-window_" + host;
+ const afkBucket = "aw-watcher-afk_" + host;
+ const [sessionEvents, windowEvents, afkEvents] = await Promise.all([
+ loadBucketEvents(sessionBucket, 5000).catch(function () { return []; }),
+ loadBucketEvents(windowBucket, 10000).catch(function () { return []; }),
+ loadBucketEvents(afkBucket, 10000).catch(function () { return []; })
+ ]);
+
+ const fromMs = ms(fromIso);
+ const toMs = ms(toIso);
+ const userRanges = {};
+ (sessionEvents || []).forEach(function (e) {
+ const d = e && e.data ? e.data : {};
+ const u = String(d.username || "").trim();
+ if (!u) return;
+ const t0 = ms(e.timestamp);
+ const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
+ if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
+ userRanges[u] = userRanges[u] || [];
+ userRanges[u].push([t0, t1]);
+ });
+
+ const activeRanges = [];
+ const afkByTs = new Map();
+ (afkEvents || []).forEach(function (e) { afkByTs.set(String(e.timestamp || ""), e); });
+ (windowEvents || []).forEach(function (e) {
+ const t0 = ms(e.timestamp);
+ const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
+ if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
+ const afk = afkByTs.get(String(e.timestamp || ""));
+ const status = String(afk && afk.data && afk.data.status || "");
+ if (status && status.toLowerCase() === "afk") return;
+ activeRanges.push([t0, t1]);
+ });
+
+ const rows = Object.keys(userRanges).sort().map(function (u) {
+ let presentSec = 0;
+ let activeSec = 0;
+ userRanges[u].forEach(function (r) {
+ presentSec += intersectDurationSeconds(r[0], r[1], fromMs, toMs);
+ activeRanges.forEach(function (a) {
+ activeSec += intersectDurationSeconds(r[0], r[1], a[0], a[1]);
+ });
+ });
+ const idleSec = Math.max(0, presentSec - activeSec);
+ return { user: u, presentSec: presentSec, activeSec: activeSec, idleSec: idleSec };
+ });
+
+ if (!rows.length) {
+ target.innerHTML = '
Нет данных в aw-worktime-sessions_' + host + ' за выбранный интервал.
';
+ return;
+ }
+
+ target.innerHTML = '| Пользователь | Активно, ч | В сессии, ч | Простой, ч |
' +
+ rows.map(function (r) {
+ return '| ' + r.user + ' | ' + fmtHours(r.activeSec) + ' | ' + fmtHours(r.presentSec) + ' | ' + fmtHours(r.idleSec) + ' |
';
+ }).join("") +
+ '
';
+ }
+
+ async function injectPersonalWorktimeCenter(root) {
+ const isActivity = /^#\/activity\//i.test(window.location.hash || "");
+ if (!isActivity) return;
+ if (root.querySelector(".aw-ru-worktime-center")) return;
+ const host = getCurrentHostFromHash();
+ if (!host) return;
+
+ const container = document.createElement("div");
+ container.className = "aw-ru-worktime-center";
+ const now = new Date();
+ const day = now.toISOString().slice(0, 10);
+ container.innerHTML =
+ 'Персональный учёт рабочего времени
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ 'Нажмите "Рассчитать".
';
+
+ root.insertBefore(container, root.firstChild);
+
+ const run = async function () {
+ const date = container.querySelector("[data-aw-wt-date]").value;
+ const tFrom = container.querySelector("[data-aw-wt-from]").value || "09:00";
+ const tTo = container.querySelector("[data-aw-wt-to]").value || "18:00";
+ const result = container.querySelector("[data-aw-wt-result]");
+ result.textContent = "Расчёт...";
+ const fromIso = new Date(date + "T" + tFrom + ":00").toISOString();
+ const toIso = new Date(date + "T" + tTo + ":00").toISOString();
+ try {
+ await renderPersonalWorktime(host, fromIso, toIso, result);
+ } catch (e) {
+ result.textContent = "Ошибка расчёта: " + String(e.message || e);
+ }
+ };
+ container.querySelector("[data-aw-wt-run]").addEventListener("click", function () { run(); });
+ }
+
function applyPatch() {
enforceSafeActivityViewForPveHost();
ensureSettingsHost();
@@ -1599,6 +1739,7 @@
injectDlpNavigation(document.body);
injectDlpReviewCenter(document.body);
injectDlpAlertsCenter(document.body);
+ injectPersonalWorktimeCenter(document.body).catch(function () {});
injectHostGroupsCenter(document.body).catch(function () {});
redirectBareTrendsRoute();
}
diff --git a/windows/ActivityWatch.Windows.Common.psm1 b/windows/ActivityWatch.Windows.Common.psm1
index 4bca474..fb36dca 100755
--- a/windows/ActivityWatch.Windows.Common.psm1
+++ b/windows/ActivityWatch.Windows.Common.psm1
@@ -243,6 +243,8 @@ function Copy-ActivityWatchCollectorAssets {
[Parameter(Mandatory = $true)]
[string]$EndpointCollectorScriptSource,
[Parameter(Mandatory = $true)]
+ [string]$SessionCollectorScriptSource,
+ [Parameter(Mandatory = $true)]
[string]$ExampleRulesSource,
[Parameter(Mandatory = $true)]
[string]$ExamplePolicySource,
@@ -256,6 +258,7 @@ function Copy-ActivityWatchCollectorAssets {
$collectorTarget = Join-Path $StateRoot 'browser-domains-native-collector.ps1'
$endpointCollectorTarget = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1'
+ $sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-collector.ps1'
$exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json'
$rulesTarget = Join-Path $StateRoot 'web-category-rules.json'
$examplePolicyTarget = Join-Path $StateRoot 'dlp-policy.example.json'
@@ -263,6 +266,7 @@ function Copy-ActivityWatchCollectorAssets {
Copy-Item -LiteralPath $CollectorScriptSource -Destination $collectorTarget -Force
Copy-Item -LiteralPath $EndpointCollectorScriptSource -Destination $endpointCollectorTarget -Force
+ Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force
Copy-Item -LiteralPath $ExampleRulesSource -Destination $exampleRulesTarget -Force
Copy-Item -LiteralPath $ExamplePolicySource -Destination $examplePolicyTarget -Force
@@ -282,6 +286,7 @@ function Copy-ActivityWatchCollectorAssets {
return [pscustomobject]@{
CollectorScript = $collectorTarget
EndpointCollectorScript = $endpointCollectorTarget
+ SessionCollectorScript = $sessionCollectorTarget
ExampleRules = $exampleRulesTarget
ActiveRules = $rulesTarget
ExamplePolicy = $examplePolicyTarget
@@ -308,6 +313,8 @@ function New-ActivityWatchDeploymentConfig {
[Parameter(Mandatory = $true)]
[string]$EndpointCollectorScript,
[Parameter(Mandatory = $true)]
+ [string]$SessionCollectorScript,
+ [Parameter(Mandatory = $true)]
[string]$RulesPath,
[Parameter(Mandatory = $true)]
[string]$PolicyPath,
@@ -349,6 +356,7 @@ function New-ActivityWatchDeploymentConfig {
logsRoot = $LogsRoot
collectorScript = $CollectorScript
endpointCollectorScript = $EndpointCollectorScript
+ sessionCollectorScript = $SessionCollectorScript
rulesPath = $RulesPath
policyPath = $PolicyPath
launchScript = $LaunchScriptPath
@@ -631,6 +639,7 @@ function Start-CollectorScriptIfNeeded {
`$script:KnownBuckets = @{}
`$collectorScript = [string]`$config.paths.collectorScript
`$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { '' }
+`$sessionCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]`$config.paths.sessionCollectorScript } else { '' }
`$afkExe = Join-Path `$installRoot 'aw-watcher-afk\aw-watcher-afk.exe'
`$windowExe = Join-Path `$installRoot 'aw-watcher-window\aw-watcher-window.exe'
`$serverArgs = @('--host', [string]`$config.server.host, '--port', [string]`$config.server.port)
@@ -661,6 +670,7 @@ catch {
}
Start-CollectorScriptIfNeeded -ScriptPath `$collectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
Start-CollectorScriptIfNeeded -ScriptPath `$endpointCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
+Start-CollectorScriptIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
"@
Set-Content -LiteralPath $Path -Value $content -Encoding UTF8
diff --git a/windows/deploy-domain-users.ps1 b/windows/deploy-domain-users.ps1
index 936ce3c..c6ca412 100755
--- a/windows/deploy-domain-users.ps1
+++ b/windows/deploy-domain-users.ps1
@@ -44,6 +44,7 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1'
$recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1'
$collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1'
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
+$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
$exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json'
$examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json'
@@ -57,6 +58,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null
$assetResult = Copy-ActivityWatchCollectorAssets `
-CollectorScriptSource $collectorSource `
-EndpointCollectorScriptSource $endpointCollectorSource `
+ -SessionCollectorScriptSource $sessionCollectorSource `
-ExampleRulesSource $exampleRulesSource `
-ExamplePolicySource $examplePolicySource `
-StateRoot $StateRoot `
@@ -76,6 +78,7 @@ $config = New-ActivityWatchDeploymentConfig `
-LogsRoot $logsRoot `
-CollectorScript $assetResult.CollectorScript `
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
+ -SessionCollectorScript $assetResult.SessionCollectorScript `
-RulesPath $assetResult.ActiveRules `
-PolicyPath $assetResult.ActivePolicy `
-PollSeconds $PollSeconds `
diff --git a/windows/deploy-single-user.ps1 b/windows/deploy-single-user.ps1
index d3eafef..dfd24b4 100755
--- a/windows/deploy-single-user.ps1
+++ b/windows/deploy-single-user.ps1
@@ -42,6 +42,7 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1'
$recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1'
$collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1'
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
+$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
$exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json'
$examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json'
@@ -55,6 +56,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null
$assetResult = Copy-ActivityWatchCollectorAssets `
-CollectorScriptSource $collectorSource `
-EndpointCollectorScriptSource $endpointCollectorSource `
+ -SessionCollectorScriptSource $sessionCollectorSource `
-ExampleRulesSource $exampleRulesSource `
-ExamplePolicySource $examplePolicySource `
-StateRoot $StateRoot `
@@ -74,6 +76,7 @@ $config = New-ActivityWatchDeploymentConfig `
-LogsRoot $logsRoot `
-CollectorScript $assetResult.CollectorScript `
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
+ -SessionCollectorScript $assetResult.SessionCollectorScript `
-RulesPath $assetResult.ActiveRules `
-PolicyPath $assetResult.ActivePolicy `
-PollSeconds $PollSeconds `
diff --git a/windows/hardening-recovery.ps1 b/windows/hardening-recovery.ps1
index c908b2f..1ab1f74 100755
--- a/windows/hardening-recovery.ps1
+++ b/windows/hardening-recovery.ps1
@@ -53,6 +53,7 @@ $effectiveLaunchScript = Join-Path $effectiveStateRoot 'launch-watchers.ps1'
$effectiveRecoveryScript = Join-Path $effectiveStateRoot 'recovery-loop.ps1'
$effectiveCollector = Join-Path $effectiveStateRoot 'browser-domains-native-collector.ps1'
$effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$existingConfig.paths.endpointCollectorScript } else { Join-Path $effectiveStateRoot 'dlp-endpoint-signals-collector.ps1' }
+$effectiveSessionCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$existingConfig.paths.sessionCollectorScript } else { Join-Path $effectiveStateRoot 'worktime-session-collector.ps1' }
$effectiveRules = Join-Path $effectiveStateRoot 'web-category-rules.json'
$effectivePolicy = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$existingConfig.paths.policyPath } else { Join-Path $effectiveStateRoot 'dlp-policy.json' }
@@ -96,6 +97,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $effectiveInstallRoot | Out-Null
$assetResult = Copy-ActivityWatchCollectorAssets `
-CollectorScriptSource (Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1') `
-EndpointCollectorScriptSource (Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1') `
+ -SessionCollectorScriptSource (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') `
-ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') `
-ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') `
-StateRoot $effectiveStateRoot `
@@ -115,6 +117,7 @@ $config = New-ActivityWatchDeploymentConfig `
-LogsRoot $effectiveLogsRoot `
-CollectorScript $effectiveCollector `
-EndpointCollectorScript $effectiveEndpointCollector `
+ -SessionCollectorScript $effectiveSessionCollector `
-RulesPath $effectiveRules `
-PolicyPath $effectivePolicy `
-PollSeconds $effectivePollSeconds `
diff --git a/windows/worktime-session-collector.ps1 b/windows/worktime-session-collector.ps1
new file mode 100644
index 0000000..8575a9f
--- /dev/null
+++ b/windows/worktime-session-collector.ps1
@@ -0,0 +1,120 @@
+param(
+ [string]$ConfigPath = 'C:\ProgramData\ActivityWatch\deployment-config.json',
+ [string]$Hostname,
+ [int]$PollSeconds = 30
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+function Get-Config {
+ param([string]$Path)
+ if (-not (Test-Path -LiteralPath $Path)) { throw "Config not found: $Path" }
+ return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
+}
+
+function Invoke-AwJsonPost {
+ param(
+ [Parameter(Mandatory = $true)][string]$Uri,
+ [Parameter(Mandatory = $true)][string]$Json
+ )
+ $bytes = [Text.Encoding]::UTF8.GetBytes($Json)
+ Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null
+}
+
+function Ensure-Bucket {
+ param(
+ [Parameter(Mandatory = $true)][string]$ApiBase,
+ [Parameter(Mandatory = $true)][string]$BucketId,
+ [Parameter(Mandatory = $true)][string]$HostnameValue
+ )
+ try {
+ Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
+ return
+ }
+ catch {
+ }
+
+ $body = @{
+ client = 'aw-worktime-session-collector'
+ type = 'aw.worktime.session'
+ hostname = $HostnameValue
+ } | ConvertTo-Json -Compress
+ Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body
+}
+
+function Get-SessionRecords {
+ $records = @()
+ try {
+ $lines = (quser 2>$null)
+ if (-not $lines) { return @() }
+ foreach ($line in $lines | Select-Object -Skip 1) {
+ $clean = ($line -replace '^\s*>?', '').Trim()
+ if (-not $clean) { continue }
+ $parts = $clean -split '\s+'
+ if ($parts.Count -lt 4) { continue }
+ $user = $parts[0]
+ $sessionName = $parts[1]
+ $sessionId = 0
+ if ($parts[2] -match '^\d+$') {
+ $sessionId = [int]$parts[2]
+ }
+ $state = $parts[3]
+ $records += [pscustomobject]@{
+ username = $user
+ sessionName = $sessionName
+ sessionId = $sessionId
+ state = $state
+ }
+ }
+ }
+ catch {
+ }
+ return $records
+}
+
+$cfg = Get-Config -Path $ConfigPath
+$hostValue = if ($Hostname) { $Hostname } else { [string]$env:COMPUTERNAME }
+$apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port
+$bucketId = 'aw-worktime-sessions_' + $hostValue
+$pulse = 120
+$sleepSec = if ($PollSeconds -gt 0) { $PollSeconds } elseif ($cfg.collector -and $cfg.collector.pollSeconds) { [int]$cfg.collector.pollSeconds } else { 30 }
+
+Ensure-Bucket -ApiBase $apiBase -BucketId $bucketId -HostnameValue $hostValue
+
+while ($true) {
+ $now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
+ $records = Get-SessionRecords
+ if (-not $records -or $records.Count -eq 0) {
+ $records = @([pscustomobject]@{
+ username = $env:USERNAME
+ sessionName = ''
+ sessionId = (Get-Process -Id $PID).SessionId
+ state = 'Unknown'
+ })
+ }
+
+ foreach ($rec in $records) {
+ $payload = @{
+ timestamp = $now
+ duration = 0
+ data = @{
+ username = [string]$rec.username
+ userId = "$($env:USERDOMAIN)\$($rec.username)"
+ sessionId = [int]$rec.sessionId
+ sessionName = [string]$rec.sessionName
+ state = [string]$rec.state
+ active = ($rec.state -match 'Active')
+ hostname = $hostValue
+ source = 'worktime-session-collector'
+ }
+ } | ConvertTo-Json -Depth 6 -Compress
+ try {
+ Invoke-AwJsonPost -Uri "$apiBase/buckets/$bucketId/heartbeat?pulsetime=$pulse" -Json $payload
+ }
+ catch {
+ }
+ }
+
+ Start-Sleep -Seconds $sleepSec
+}