Configuration

psLens reads a config.yaml file from the same directory as the binary.

Configuration

psLens reads a config.yaml file from the same directory as the binary. There is no database to set up; persistent data (report results, alert state) is stored in an embedded NATS data store.

Full Configuration Examples

psLens supports configuration in either YAML (config.yaml) or JSON (config.json) format. The application detects the format automatically on boot.

YAML Configuration Example (config.yaml)

# Schema validation pointer (optional, for IDE support)
# yaml-language-server: $schema=http://localhost:8080/static/config-schema.json

server:
  port: 8080
  host: "0.0.0.0"
  natsStoreDir: "./data/nats"
  projectStoreDir: "./data/projects"
  dmsStoreDir: "./data/dms"
  appBaseURL: "http://localhost:8080"
  recentlyViewed:
    maxItems: 20

databases:
  - name: "PROD"
    description: "Production PeopleSoft HR"
    baseURL: "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_SWS_PSOFTQL/"
    username: "PSLENS_API"
    password: "your-api-password"
    piaURL: "https://psft.example.com/psp/ps/"
    timezone: "America/Chicago"
    production: true
    alerts:
      enabled: true
      intervalMinutes: 10

  - name: "DEV"
    description: "Development Environment"
    baseURL: "https://psftdev.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_SWS_PSOFTQL/"
    username: "PSLENS_API"
    password: "dev-api-password"
    timezone: "America/Chicago"
    production: false
    downtimes:
      - name: "Nightly Downtime"
        enabled: true
        startTime: "22:00"
        endTime: "06:00"
        daysOfWeek: [1, 2, 3, 4, 5]
      - name: "Weekend Maintenance"
        enabled: true
        startAt: "2026-07-04T00:00:00Z"
        endAt: "2026-07-05T23:59:59Z"

alerts:
  enabled: true
  intervalMinutes: 5
  checks:
    long_running_processes:
      enabled: true
      thresholdMinutes: 20
      anomalyMultiplier: 4.0
      anomalyMinBaselineMinutes: 10
    process_errors:
      enabled: true
      lookbackHours: 24
    ib_operation_errors:
      enabled: true
      lookbackHours: 24
    ib_pub_contract_errors:
      enabled: true
      lookbackHours: 24
    ib_sub_contract_errors:
      enabled: true
      lookbackHours: 24
    ib_operation_stalled:
      enabled: true
      thresholdMinutes: 30
    ib_pub_contract_stalled:
      enabled: true
      thresholdMinutes: 30
    ib_sub_contract_stalled:
      enabled: true
      thresholdMinutes: 30
  genericSWSAlerts:
    - id: "stale_users"
      name: "Stale User Accounts"
      enabled: true
      severity: "warning"
      alertOn: "row_found"
      message: "Warning: Stale user accounts detected"
      query:
        records:
          - recordName: "PSOPRDEFN"
            sqlWhereClause: "LASTUPDDTTM < CAST('2026-01-01' AS TIMESTAMP) AND ACCTLOCK = 0"
        rowLimit: 5

auth:
  enabled: true
  authorizedUsers:
    - "admin@example.com"
    - "auditor@example.com"

smtp:
  host: "smtp.mailtrap.io"
  port: "2525"
  username: "smtp-user"
  password: "smtp-password"
  fromName: "psLens Alerts"
  fromEmail: "alerts@pslens.example.com"

notifications:
  subscriptions:
    - id: "team-email"
      enabled: true
      alertTypes: ["*"]
      databases: ["PROD"]
      severityMin: "warning"
      type: "email"
      target: "psoft-alerts@example.com"
    - id: "slack-webhook"
      enabled: true
      alertTypes: ["process_errors", "ib_operation_errors"]
      databases: ["*"]
      type: "webhook"
      target: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
    - id: "teams-webhook"
      enabled: true
      alertTypes: ["*"]
      databases: ["*"]
      type: "webhook"
      target: "https://example.webhook.office.com/webhookb2/..."

reports:
  - id: "security-full-access-permlists"
    enabled: true
    dbNames: ["PROD"]
    schedule:
      interval: "daily"
      timeOfDay: "02:00"
    emailTarget: "psoft-alerts@example.com"

JSON Configuration Example (config.json)

{
  "$schema": "http://localhost:8080/static/config-schema.json",
  "server": {
    "port": 8080,
    "host": "0.0.0.0",
    "natsStoreDir": "./data/nats",
    "projectStoreDir": "./data/projects",
    "dmsStoreDir": "./data/dms",
    "appBaseURL": "http://localhost:8080",
    "recentlyViewed": {
      "maxItems": 20
    }
  },
  "databases": [
    {
      "name": "PROD",
      "description": "Production PeopleSoft HR",
      "baseURL": "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_SWS_PSOFTQL/",
      "username": "PSLENS_API",
      "password": "your-api-password",
      "piaURL": "https://psft.example.com/psp/ps/",
      "timezone": "America/Chicago",
      "production": true,
      "alerts": {
        "enabled": true,
        "intervalMinutes": 10
      }
    },
    {
      "name": "DEV",
      "description": "Development Environment",
      "baseURL": "https://psftdev.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_SWS_PSOFTQL/",
      "username": "PSLENS_API",
      "password": "dev-api-password",
      "timezone": "America/Chicago",
      "production": false
    }
  ],
  "alerts": {
    "enabled": true,
    "intervalMinutes": 5,
    "checks": {
      "long_running_processes": {
        "enabled": true,
        "thresholdMinutes": 20,
        "anomalyMultiplier": 4.0,
        "anomalyMinBaselineMinutes": 10
      },
      "process_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_operation_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_pub_contract_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_sub_contract_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_operation_stalled": {
        "enabled": true,
        "thresholdMinutes": 30
      },
      "ib_pub_contract_stalled": {
        "enabled": true,
        "thresholdMinutes": 30
      },
      "ib_sub_contract_stalled": {
        "enabled": true,
        "thresholdMinutes": 30
      }
    },
    "genericSWSAlerts": [
      {
        "id": "stale_users",
        "name": "Stale User Accounts",
        "enabled": true,
        "severity": "warning",
        "alertOn": "row_found",
        "message": "Warning: Stale user accounts detected",
        "query": {
          "records": [
            {
              "recordName": "PSOPRDEFN",
              "sqlWhereClause": "LASTUPDDTTM < CAST('2026-01-01' AS TIMESTAMP) and ACCTLOCK = 0"
            }
          ],
          "rowLimit": 5
        }
      }
    ]
  },
  "auth": {
    "enabled": true,
    "authorizedUsers": [
      "admin@example.com",
      "auditor@example.com"
    ]
  },
  "smtp": {
    "host": "smtp.mailtrap.io",
    "port": "2525",
    "username": "smtp-user",
    "password": "smtp-password",
    "fromName": "psLens Alerts",
    "fromEmail": "alerts@pslens.example.com"
  },
  "notifications": {
    "subscriptions": [
      {
        "id": "team-email",
        "enabled": true,
        "alertTypes": ["*"],
        "databases": ["PROD"],
        "severityMin": "warning",
        "type": "email",
        "target": "psoft-alerts@example.com"
      },
      {
        "id": "slack-webhook",
        "enabled": true,
        "alertTypes": ["process_errors", "ib_operation_errors"],
        "databases": ["*"],
        "type": "webhook",
        "target": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
      },
      {
        "id": "teams-webhook",
        "enabled": true,
        "alertTypes": ["*"],
        "databases": ["*"],
        "type": "webhook",
        "target": "https://example.webhook.office.com/webhookb2/..."
      }
    ]
  },
  "reports": [
    {
      "id": "security-full-access-permlists",
      "enabled": true,
      "dbNames": ["PROD"],
      "schedule": {
        "interval": "daily",
        "timeOfDay": "02:00"
      },
      "emailTarget": "psoft-alerts@example.com"
    }
  ]
}


Server Settings

The server section controls how psLens listens for incoming connections and where it stores data.

SettingDefaultDescription
port8080TCP port psLens listens on
host0.0.0.0Network interface to bind (use 127.0.0.1 to restrict to localhost)
natsStoreDir./data/natsDirectory for persistent NATS data (report results, alert history)

Tip: The natsStoreDir should be on a persistent volume. If psLens restarts, report results and alert history stored here are preserved.


Database Connections

You can configure one or more PeopleSoft databases under the databases list. psLens monitors the health of each connection and shows status on the dashboard.

SettingRequiredDescription
nameYesShort identifier shown in the UI (e.g., PROD, DEV)
descriptionYesHuman-readable label for the database
baseURLYesFull URL to the SWS psoftQL endpoint, including the service name
usernameYesPeopleSoft operator ID for API authentication
passwordYesPassword for the operator ID
piaURLNoBase URL for PeopleSoft Internet Architecture (used for deep links to PeopleSoft pages, if supported)
timezoneNoIANA timezone name for the database server (e.g., America/Chicago). Defaults to UTC if not set. Used to interpret timestamps correctly.
downtimesNoList of scheduled downtime windows to suppress alerts and monitoring.
notificationsNoDatabase-specific notification delivery settings (bypasses global subscriptions).

Scheduled Downtimes

Downtimes allow silencing alerts and pausing connection health checks for non-production environments that go offline regularly (e.g., overnight or on weekends).

Each entry in the downtimes list supports the following settings:

SettingRequiredDescription
nameYesIdentifier for the downtime window.
enabledYesWhether the downtime rule is active.
startTimeNoStart time of day in HH:MM format (e.g., 22:00). Set together with endTime.
endTimeNoEnd time of day in HH:MM format (e.g., 06:00). An end time earlier than the start crosses midnight. 24:00 means end of day, so an all-day window is 00:0024:00.
daysOfWeekNoArray of integers for days of week. 0 is Sunday, 1 is Monday, …, 6 is Saturday. Omit to apply every day. An overnight window belongs to the day it starts.
startAtNoSpecific start timestamp in RFC3339 format (e.g., 2026-07-04T00:00:00Z) for one-off maintenance.
endAtNoSpecific end timestamp in RFC3339 format (e.g., 2026-07-05T23:59:59Z) for one-off maintenance.

startTime/endTime are wall-clock times in the connection’s timezone (UTC if unset). Rules are validated when the configuration is saved; a rule must define a recurring window (startTime + endTime) and/or a one-off range (startAt/endAt).

When a database is in a scheduled downtime, the connection manager sets its status to Downtime on the dashboard, background alert checks are skipped (including the Integration Broker down check), email/webhook notifications are suppressed, and the database does not count against overall health. When the window ends, psLens rechecks the connection immediately and treats the first failed check as a real outage — size windows to cover the system’s boot time.

A weekend window that spans from Saturday evening to Monday morning is composed of two rules, since an overnight window belongs to its start day:

downtimes:
  - name: "Weekend nights"
    enabled: true
    startTime: "22:00"
    endTime: "06:00"
    daysOfWeek: [6, 0]   # Sat night -> Sun morning, Sun night -> Mon morning
  - name: "Sunday all day"
    enabled: true
    startTime: "00:00"
    endTime: "24:00"
    daysOfWeek: [0]

The baseURL Format

The baseURL is the Integration Broker REST endpoint for the SWS service. It follows this pattern:

https://{igw-host}:{port}/PSIGW/RESTListeningConnector/{database-name}/CHG_SWS_PSOFTQL/

The trailing slash is required.

Environment Variable Overrides

Sensitive settings like passwords can be overridden with environment variables to avoid storing them in config.yaml. The override format is:

PSLENS_DB_{NAME}_{FIELD}

Where {NAME} matches the name field of the database in config.yaml (case-sensitive).

For example, to override the password for a database named PROD:

export PSLENS_DB_PROD_PASSWORD="my-secure-password"

Database-Specific Notifications

By default, alert notifications for all databases are routed using global subscription rules (see Notifications & Webhooks Settings).

However, you can configure database-specific notifications under the notifications property of a database entry. Doing so acts as a complete override: all alerts (regardless of severity level) and resolutions for that specific database are routed directly to the database-level targets, bypassing global subscriptions entirely.

Each entry in the notifications block supports the following settings:

SettingTypeDescription
emailEnabledBooleanActivates email notifications for this database.
emailTargetStringComma-separated list of recipient email addresses.
webhookEnabledBooleanActivates webhook notifications for this database.
webhookTargetStringWebhook destination URL (e.g., Slack, MS Teams, or a generic endpoint).

Example database connection configuration with inline notifications:

databases:
  - name: "PROD"
    description: "Production Environment"
    baseURL: "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_SWS_PSOFTQL/"
    username: "PSLENS_API"
    password: "securepassword"
    notifications:
      emailEnabled: true
      emailTarget: "prod-alerts@example.com"
      webhookEnabled: true
      webhookTarget: "https://hooks.slack.com/services/..."

Alerts Configuration

The alerts section controls the background alert checking system. See the Alerts section for details on what each alert detects.

Top-Level Alert Settings

SettingDefaultDescription
enabledfalseWhether to run background alert checks
intervalMinutes5How often (in minutes) to run all alert checks
genericSWSAlerts[]List of queryable SWS alerts. See Generic SWS Alerts for details.

Alert Check Settings

Each alert type under checks supports some or all of the following settings:

SettingDescription
enabledWhether this check is active
thresholdMinutesFor stalled/long-running checks: how many minutes before flagging (default varies by check). For long_running_processes, this is the fallback static threshold.
lookbackHoursFor error checks: how many hours back to look for failures (default varies by check)
excludeProcessesList of process names to skip (for process-related checks)
excludeOperationsList of IB operation names to skip (for Integration Broker checks)
anomalyMultiplierMultiplier applied to the rolling median duration to calculate the dynamic threshold (default: 4.0)
anomalyMinBaselineMinutesMinimum baseline duration in minutes. Dynamic thresholds are capped to be at least this value to prevent false alerts on very fast processes (default: 10)

Available Alert Checks

Check KeyNameDescription
long_running_processesLong-Running ProcessesFlags processes running longer than their rolling median-based expected runtime (or thresholdMinutes fallback)
process_errorsProcess ErrorsFinds processes that failed within lookbackHours (default: 24 hours)
ib_operation_errorsIB Operation ErrorsFinds async IB operations in Error or Timeout status within lookbackHours (default: 24 hours)
ib_pub_contract_errorsIB Publication Contract ErrorsFinds pub contracts in Error or Timeout status within lookbackHours (default: 24 hours)
ib_sub_contract_errorsIB Subscription Contract ErrorsFinds sub contracts in Error or Timeout status within lookbackHours (default: 24 hours)
ib_operation_stalledIB Operations StalledFinds async IB operations stuck in New or Working status longer than thresholdMinutes (default: 30 min)
ib_pub_contract_stalledIB Publication Contracts StalledFinds pub contracts stuck in New or Working status longer than thresholdMinutes (default: 30 min)
ib_sub_contract_stalledIB Subscription Contracts StalledFinds sub contracts stuck in New or Working status longer than thresholdMinutes (default: 30 min)
locked_oprid_processesLocked OPRID Scheduled ProcessesFinds queued or scheduled processes whose submitting OPRID has a locked account
backlogged_processesBacklogged ProcessesDetects processes currently stuck in a Queued or Blocked state longer than thresholdMinutes (default: 30 min)
queue_latencyQueue LatencyDetects processes that experienced a start delay (BEGINDTTM - RUNDTTM) greater than thresholdMinutes (default: 15 min)
failed_loginsFailed LoginsDetects users with excessive failed login attempts in PSPTLOGINAUDIT (defaults to > thresholdCount of 5)
process_run_checkProcess Run CheckMonitors configured critical processes and alerts when they haven’t run successfully within their configured time window
ib_operation_volumeAbnormal IB Operation VolumeDetects when IB operation instance volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_pub_contract_volumeAbnormal IB Pub Contract VolumeDetects when IB publication contract volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_sub_contract_volumeAbnormal IB Sub Contract VolumeDetects when IB subscription contract volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_sync_exceptionsIB Sync Operation ExceptionsDetects synchronous service operations with errors in PSIBLOGHDR within lookbackHours (default: 24 hours). Disabled by default.
no_process_completedNo Process CompletedFires when no process has successfully completed within the lookbackHours (default: 1 hour)
ib_downIntegration Broker DownAlerts when SWS REST endpoint connection failures indicate the Integration Broker is down
weblib_downWeb Server / WebLib DownAlerts when PeopleSoft Web Server is down or configured WebLib URLs fail to respond
ib_no_active_domainIB No Active DomainAlerts when there is no active domain found in PSAPMSGDOMSTAT
ib_dispatcher_downIB Dispatcher DownAlerts when an Integration Broker dispatcher process is inactive or has not updated status within thresholdMinutes (default: 10 min)
ib_nodes_downIB Nodes DownAlerts when there are entries in PSNODESDOWN indicating message nodes are down

Authentication Settings

The auth section configures native email-based magic link authentication. When enabled, users must log in using an email verification code sent to an address on the authorized allowlist.

SettingDefaultDescription
enabledfalseSet to true to require magic link authentication for all pages
authorizedUsers[]List of email addresses allowed to log in (case-insensitive)

SMTP Settings

The email server is edited in the app under Settings → Notifications (Email Server card). Changes apply to alert and report emails immediately; login (magic link) emails pick them up after the next server restart. A blank password on save keeps the stored password.

The smtp block in config.yaml seeds the email server on first boot only; after that, the settings saved in the UI are authoritative. PSLENS_SMTP_* environment variables override both and make the card read-only in the UI.

SettingDefaultDescription
host-SMTP server hostname/IP
port-SMTP port (e.g. 25, 465, 587, 2525)
username-Username for SMTP authentication
password-Password for SMTP authentication (encrypted at rest if master key is set)
fromNamepsLensSender name shown in emails
fromEmail-Sender email address for SMTP

Outbound email can be tested on the same card: enter a recipient address under Send Test Email and click Send Test. The result is recorded in Alert Delivery History.


Notifications & Webhooks Settings

Notification rules decide where alert messages are dispatched. They are managed in the app under Settings → Notifications (Notification Rules card), where each rule can be added, edited, tested, and deleted. The notifications section in config.yaml defines the same rules in file form (the file calls them subscriptions).

Subscription Sub-settings

Under notifications.subscriptions, define a list of targets:

PropertyTypeDescription
idStringUnique identifier for the subscription
enabledBooleanActivates or silences the subscription
alertTypesList of StringsAlert check keys to match (e.g. ["*"] for all, or ["process_errors"])
databasesList of StringsDatabase names to match (e.g. ["*"] or ["PROD"])
severityMinStringMinimum alert severity ("info", "warning", "critical")
typeStringDispatch protocol: "email" or "webhook"
targetStringTarget destination: email address, Slack Webhook URL, or Microsoft Teams Incoming Webhook URL

Note: psLens detects Slack and MS Teams webhook URLs from the URL pattern and formats the payload for each.


Reports Scheduling Settings

The reports section allows you to define static scheduling configs and completion notifications directly in the configuration file.

Report Properties

Under reports, define a list of scheduled report objects:

PropertyTypeDescription
idStringThe unique report definition ID (e.g. security-full-access-permlists)
enabledBooleanEnables or disables the scheduled execution of this report (defaults to true)
dbNamesList of StringsDatabase names to target (e.g., ["PROD"]). If omitted, targets all active databases.
scheduleObjectTiming recurrence properties (see below)
emailTargetStringOptional email address to send report completion notifications to
webhookTargetStringOptional Slack, MS Teams, or JSON webhook endpoint to send notifications to

Schedule Recurrence Properties

Under reports.schedule, define when the job should run:

PropertyTypeDescription
intervalStringRecurrence frequency: "daily", "weekly", or "monthly"
timeOfDayString24-hour format execution time ("HH:MM", e.g., "02:00") in server local time
dayOfWeekIntegerOptional. Day of the week for weekly runs (0 for Sunday, 1 for Monday, …, 6 for Saturday)
dayOfMonthIntegerOptional. Day of the month for monthly runs (1 to 28)

JSON Schema Validation

To enable autocompletion, tooltips, and real-time schema validation within your IDE (such as VS Code), use the built-in JSON schema:

Option A: Live URL (Online)

If your psLens server is running locally (e.g., on port 8080), you can add the $schema parameter to the top of your JSON config file:

{
  "$schema": "http://localhost:8080/static/config-schema.json",
  "server": {
    "port": 8080
  }
}

Option B: Local File Reference (Offline)

To configure your workspace offline, reference the schema file directly. In VS Code, add this to your .vscode/settings.json:

{
  "json.schemas": [
    {
      "fileMatch": ["config.json"],
      "url": "./static/config-schema.json"
    }
  ],
  "yaml.schemas": {
    "./static/config-schema.json": ["config.yaml"]
  }
}

Securing psLens

For production deployments, you should restrict access to the psLens interface.

  1. Enable Built-In Magic Link Auth: Turn on auth.enabled and configure SMTP credentials and authorizedUsers to require code validation on login.
  2. Setup a Master Key (PSLENS_MASTER_KEY): Provide a 32-byte (64 hex characters) key in the PSLENS_MASTER_KEY environment variable. All database and SMTP passwords entered in the UI or configuration are then encrypted at rest using AES-256-GCM.
  3. Reverse Proxy / VPN: Place psLens behind a reverse proxy (e.g., Cloudflare Access, oauth2-proxy, nginx, Tailscale) to delegate authentication to your company’s Identity Provider (SAML/OIDC). When using an external SSO proxy, you can keep auth.enabled disabled and restrict the psLens binary to bind only on 127.0.0.1 or internal networks.

Warning: Never expose psLens to the public internet without either turning on the built-in magic-link auth or placing an authenticated reverse proxy in front of it. Doing so exposes read access to PeopleSoft system metadata.


Active Configuration and Hot-Reloading

psLens keeps its settings in two places:

  1. Active configuration (embedded key-value store): when you edit connections, credentials, email settings, alert schedules, or notification rules in the web interface, changes are saved here and take effect immediately without a server restart. Every change is kept as a numbered revision under Settings → Change History, where any revision can be viewed or restored.
  2. config.yaml on disk: the bootstrap file that seeds the active configuration on first boot. Saved changes are also written back to it as a backup on the persistent storage volume.

Configuration Seeding

On initial boot, or when the environment variable PSLENS_FORCE_SEED_CONFIG=true is set, the application seeds the active configuration from config.yaml.

If the active configuration drifts from config.yaml, the Settings → Advanced page shows a “Settings differ from the config.yaml file on disk” notice. To resolve it:

  • Click Load settings from config.yaml on the Advanced page to overwrite the active configuration with the file contents (this discards changes made in the UI).
  • If the file path is writable, click Overwrite config.yaml on disk to write the active configuration to the file. On containerized or ephemeral deployment models (like fly.io without persistent storage volumes), these local file changes will be lost when the container restarts or is re-deployed.
  • Alternatively, copy the Active Configuration YAML from the Advanced page into your config.yaml file to bring the disk file in sync.

Secret Key Management (Encryption at Rest)

When the PSLENS_MASTER_KEY environment variable is set with a 32-byte hex-encoded key, all database and SMTP passwords entered in the UI are encrypted at rest. If the master key is not configured, passwords are saved in plaintext.

Rotating the Master Key

To rotate the cryptographic key used for credential encryption:

  1. Enter a new 32-byte hex key in the Rotate Encryption Key field.
  2. Click Rotate Master Key. The server decrypts all stored credentials using the old key and re-encrypts them with the new key in NATS KV.
  3. Update the PSLENS_MASTER_KEY environment variable in your deployment configuration (e.g., Fly.io secrets or .env file) to match the new key before restarting the container. If the container restarts with a mismatched key, it cannot decrypt the configuration.