This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Getting Started

Get started with psLens: install the SWS framework, deploy psLens, and connect it to your PeopleSoft environment in minutes.

Getting Started with psLens

Install SWS in PeopleSoft, run the psLens container, point it at SWS, log in. The pages in order:

Overview

psLens is a single self-contained application: a Go binary that serves the web interface and stores report data locally. There is no separate database to manage. It connects to your PeopleSoft environment through the SWS (Secure Web Services) framework, which must be installed and configured in PeopleSoft first.

psLens Dashboard showing active alerts and database connections

The psLens dashboard — your starting point for monitoring PeopleSoft environments

Hosting psLens in production? Read Deployment Options for HTTPS, version pinning, backups, and upgrade paths.

Setup Steps

  1. Read the Architecture Overview to see how the pieces fit together
  2. Install the SWS Framework in your PeopleSoft environment
  3. Install psLens on your server
  4. Configure psLens with your database connection details
  5. Start psLens and open the dashboard in your browser

Quick Start

Once the SWS framework is installed in PeopleSoft:

# Create a directory for psLens
mkdir pslens && cd pslens

# Create config.yaml with your PeopleSoft connection details
# (see Configuration for full details)

# Start psLens with Docker
docker compose up -d

# Open http://localhost:8080 in your browser

1 - Installation

Installation

psLens has two parts to install: the SWS framework in your PeopleSoft environment, and the psLens application on a server.

Hosting psLens yourself in production? This page walks through the basic “Docker on one host” install. For client-hosted deployments that need HTTPS, version pinning, backups, and a clear upgrade story, read Deployment Options after this page.

Step 1: Install the SWS Framework in PeopleSoft

psLens reads data from PeopleSoft through the SWS (Secure Web Services) framework, developed by Cedar Hills Group. SWS exposes a REST API; psLens calls it instead of talking to the database directly.

What SWS Provides

  • A PeopleSoft Integration Broker service (CHG_SWS_PSOFTQL) that psLens calls
  • A psoftQL query language for structured data access
  • Role-based API authentication using PeopleSoft operator IDs

SWS Installation Steps

Full SWS installation instructions are available in the SWS documentation. At a high level:

  1. Import the SWS PeopleSoft project into your environment using App Designer
  2. Activate the Integration Broker service and configure the listening connector
  3. Create a dedicated PeopleSoft operator ID for psLens API access
  4. Grant the operator ID the required permissions to read the tables psLens uses
  5. Test the API endpoint with a sample query

Tip: Create a dedicated operator ID for psLens (for example, PSLENS_API) rather than using an existing account. This makes it easy to audit API activity and control access.

Whitelisting Tables

SWS controls which PeopleSoft tables can be queried through a whitelist. You need to whitelist every table that psLens reads.

The full inserts live on a dedicated page so this install procedure stays readable top to bottom:

Run all of the inserts on Whitelist Tables against each PeopleSoft database you plan to connect.

After running the inserts, restart psLens (or wait for the next whitelist cache refresh) and confirm the Settings > Database Connections page shows the database as fully connected with no missing-table warnings.


Step 2: Install psLens

psLens is distributed as a Docker image. Cedar Hills Group also offers a managed hosted option on Fly.io. Contact us for details.

System Requirements

  • Docker: Docker Engine 20.10+ with Docker Compose
  • Memory: 512 MB minimum, 1 GB recommended
  • Disk: 1 GB for data storage (NATS report store)
  • Network: Must be able to reach the PeopleSoft Integration Gateway on port 8000 (or your configured port)

Docker Installation

  1. Authenticate with the GitHub Container Registry using the token provided by Cedar Hills Group:

    echo "YOUR_TOKEN" | docker login ghcr.io -u USERNAME --password-stdin
    
  2. Create a directory for psLens and add your configuration:

    mkdir pslens && cd pslens
    
  3. Create a config.yaml file (see Configuration for all options):

    server:
      port: 8080
      host: "0.0.0.0"
      appBaseURL: "https://pslens.yourcompany.com"
      natsStoreDir: "/data/nats"
    
    databases:
      - name: "PROD"
        description: "Production HCM"
        baseURL: "https://peoplesoft.example.com/PSIGW/RESTListeningConnector/PSFT_HR/CHG_SWS_PSOFTQL/"
        username: "PSLENS_API_USER"
        password: "placeholder"
        timezone: "America/Chicago"
        production: true   # red "PROD" badge + typed-name confirmation on Edit/Delete
    

    Production flag (production: true) — When set, psLens displays a red “PROD” badge next to the database name in the top nav and gates destructive settings actions (Edit, Delete) behind a typed-name confirmation prompt on both the client and the server. The flag is read from config.yaml only — the value stored in the encrypted KV snapshot is ignored on every boot and re-overlaid from the file, so a production connection can never be silently un-flagged from the settings UI. To change the flag, edit config.yaml and restart psLens.

  4. Create a docker-compose.yml file:

    services:
      pslens:
        image: ghcr.io/cedarhillsgroup/pslens:latest
        ports:
          - "8080:8080"
        volumes:
          - ./config.yaml:/app/config.yaml:ro
          - pslens_data:/data
        environment:
          - PSLENS_SERVER_HOST=0.0.0.0
          - PSLENS_NATS_STORE_DIR=/data/nats
          - PSLENS_DB_PROD_PASSWORD=your-secure-password
        restart: unless-stopped
    
    volumes:
      pslens_data:
    

    Tip: Use environment variables for passwords instead of putting them in config.yaml. The PSLENS_DB_{NAME}_PASSWORD variable overrides the password for each database.

  5. Start psLens:

    docker compose up -d
    

Upgrading

Pull the latest image and restart:

docker compose pull pslens
docker compose up -d pslens

Air-Gapped Environments

For environments without internet access from the Docker host:

# On a machine with internet access:
docker pull ghcr.io/cedarhillsgroup/pslens:latest
docker save ghcr.io/cedarhillsgroup/pslens:latest | gzip > pslens.tar.gz

# Transfer file to the target host, then:
docker load < pslens.tar.gz

Bare-Metal Installation

psLens can also run as a standalone binary without Docker. Contact Cedar Hills Group for the binary distribution.

Run psLens as a system service so it starts automatically and restarts on failure.

systemd example (/etc/systemd/system/pslens.service):

[Unit]
Description=psLens PeopleSoft Dashboard
After=network.target

[Service]
Type=simple
User=pslens
WorkingDirectory=/opt/pslens
ExecStart=/opt/pslens/pslens
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable pslens
sudo systemctl start pslens

Verifying the Installation

Once psLens is running, open http://your-server:8080 in a browser. You should see the psLens dashboard. If your config.yaml is correct, the database connection indicator in the top navigation will show your configured databases as connected.

Database connections page showing connected and disconnected databases with diagnostics

The Settings > Database Connections page shows connection status and diagnostics for each configured database


Next Steps

Once psLens is running, see Configuration for details on all the available settings, including how to configure multiple databases, enable alerts, and set up the data storage directory.

2 - Whitelist Tables

Whitelist Tables

SWS controls which PeopleSoft tables can be queried through a whitelist. You need to whitelist every table that psLens reads. The full list of tables (organized by feature area) is documented in the Reference section.

This page contains the SQL inserts you run once during installation. After running them, restart psLens (or wait for the next whitelist cache refresh) and confirm the Settings > Database Connections page shows the database as fully connected with no missing-table warnings.

Note: You may have configured the SWS client to use a different Permission List (CLASSID) other than CHG_PSOFTLENS_API_USER. If so, replace that value in every statement below.

Note: The LASTUPDDTTM column uses Oracle’s SYSDATE function. On SQL Server, replace SYSDATE with GETDATE().

Allow access to the whitelist record itself

C_SWS_REC_WL is the SWS-provided record psLens reads to discover the active whitelist. Granting access to it is required before any other whitelisted table can be queried.

INSERT INTO PS_C_SWS_COMP_WL (COMPNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID)
VALUES ('C_SWS_REC_WL', 'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');

Whitelist every PeopleSoft table psLens queries

Run all of the inserts below. Each block matches a category in the Reference page. If you add new functionality to psLens that queries a new record, add it here too.

-- Security tables

INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAUTHAS',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAUTHBUSCOMP',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAUTHITEM',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAUTHPRCS',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAUTHWS',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSCLASSDEFN',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMENUITEM',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPRDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSROLECLASS',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSROLEDEFN',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSROLEUSER',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('SCRTY_ACC_GRP',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('SCRTY_QUERY',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PTACM_ACCESSTBL',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPTSCRTY_ADS_A',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');

-- Metadata tables
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSBCDEFN',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSBCITEM',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSDBFIELD',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSDBFLDLABL',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSKEYDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMENUDEFN',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSXFERITEM',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPNLDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPNLFIELD',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPNLGROUP',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPNLGRPDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPROJECTDEFN',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPROJECTITEM',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPRSMATTRVAL',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPRSMDEFN',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPRSMPERM',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPRSMSYSATTR',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPRSMSYSATTRVL',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSRECDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSRECFIELD',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSXLATITEM',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');

-- Integration Broker tables
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAPMSGPUBCON',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAPMSGPUBHDR',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAPMSGSUBCON',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAPMSGDOMSTAT',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAPMSGDSPSTAT',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSIBLOGHDR',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSIBRTNGDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGNODEDEFN',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSNODECONPROP',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSNODEURITEXT',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSNODESDOWN',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPERATION',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPERATIONAC',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPERATIONURI',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPRHDLR',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPRVERDFN',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPRVERDFNPARM',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSQUEUEDEFN',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSQUEUEPART',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSRTNGDFNPARM',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSRTNGDFNPROP',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSSERVICE',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSSERVICEOPR',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');

-- Process Scheduler tables
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSDEFN',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSDEFNGRP',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSDEFNPNL',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSJOBDEFN',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSJOBGRP',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSJOBITEM',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSJOBPNL',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSMUTUALEXCL',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSRECUR',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSRECURDATE',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSRECUREXEMPT',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPRCSRQST',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSDEFNNOTIFY',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSDEFNCNTDIST', 'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSJOBNOTIFY',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PRCSJOBCNTDIST',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('SERVERDEFN',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSSERVERSTAT',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('SERVERCATEGORY',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('SERVERCLASS',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('SERVERNOTIFY',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('SERVEROPRTN',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('DAEMONGROUP',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('DAEMONGROUP_VW',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');

-- Developer tables
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAEAPPLDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAEAPPLSTATE',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAEAPPLTEMPTBL',  'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAESECTDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAESTEPDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSAESTMTDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSCONTDEFN',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSCONTENT',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGATTR',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGCATDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGFLDOVR',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGPARTS',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGREC',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGSETDEFN',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSMSGVER',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPACKAGEDEFN',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPCMNAME',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPCMPROG',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPCMTXT',         'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSQRYDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSQRYFIELD',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSQRYRECORD',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSQRYSTATS',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSQRYEXECLOG',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSSQLDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSSQLTEXTDEFN',    'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSTREEDEFN',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSTREENODE',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSURLDEFN',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PT_URL_PROPS',     'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');

-- Audit & user profile tables
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPTLOGINAUDIT',   'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSPRUFDEFN',       'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSUSEREMAIL',      'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');
INSERT INTO PS_C_SWS_REC_WL (RECNAME, CLASSID, LASTUPDDTTM, LASTUPDOPRID) VALUES ('PSOPTIONS',        'CHG_PSOFTLENS_API_USER', SYSDATE, 'psLens installation');

COMMIT;

Adding new tables later

When psLens gains a feature that queries a new PeopleSoft table, two things have to happen:

  1. Add the table to the matching block above, then re-run that single INSERT against each PeopleSoft database.
  2. Document the table on the Reference page so the broader documentation stays in sync.

3 - Configuration

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

alerts:
  enabled: true
  intervalMinutes: 5
  checks:
    long_running_processes:
      enabled: true
      thresholdMinutes: 20
    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/..."

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
      },
      "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/..."
      }
    ]
  }
}


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.

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"

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)
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)

Available Alert Checks

Check KeyNameDescription
long_running_processesLong-Running ProcessesFlags processes running longer than thresholdMinutes (default: 20 min)
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 queued/blocked processes whose scheduled run time is more than thresholdMinutes in the past (default: 30 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)
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 smtp section holds global SMTP credentials. It is a root-level block used both for sending magic link authentication emails and dispatching email notifications.

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

Notifications & Webhooks Settings

The notifications section defines where alert messages are dispatched. You can configure individual subscriptions for email or webhook endpoints.

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.


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.

4 - Deployment Options

Deployment Options

This page is for clients who want to host psLens themselves in a Docker container. It covers three questions in order:

  1. How do I get the image? Distribution and authentication.
  2. How do I upgrade without losing my config? Volumes, env vars, and the master key.
  3. How do I do HTTPS? Six TLS options compared on the same axes.

If you just want a 5-minute install on a private network, the Installation page is enough. Come back here when you’re ready to put psLens in front of real users.


1. Image Distribution

psLens is published to the GitHub Container Registry (GHCR) as a private package. Cedar Hills Group issues a read-only token to each client.

Authenticating

  1. Cedar Hills Group sends you a GitHub fine-grained personal access token (PAT) scoped to read:packages on the pslens package only.

  2. On the Docker host:

    echo "YOUR_TOKEN" | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
    

    The credentials are stored in ~/.docker/config.json. They persist across host reboots.

  3. Verify the pull works:

    docker pull ghcr.io/cedarhillsgroup/pslens:latest
    

Image Tags

The release pipeline publishes four tag flavors for every release:

TagExampleUse when
latestghcr.io/cedarhillsgroup/pslens:latestDev/test only — never pin production here
vMAJOR.MINOR.PATCH:v1.4.2Production — exact reproducibility
vMAJOR.MINOR:v1.4Production — auto-pickup of patch releases
Git SHA:a3f8c12Pinning to a pre-release build

Recommended: Pin production to vMAJOR.MINOR. You’ll automatically pick up patch fixes when you re-run docker compose pull, but never get an unexpected breaking change from a minor or major version bump.

When You Can’t Reach ghcr.io

If the Docker host can’t make outbound HTTPS to ghcr.io (common in segmented enterprise networks), use the air-gapped flow documented in Installation:

# On a machine with internet access:
docker pull ghcr.io/cedarhillsgroup/pslens:v1.4.2
docker save ghcr.io/cedarhillsgroup/pslens:v1.4.2 | gzip > pslens-v1.4.2.tar.gz

# Transfer the .tar.gz to the target host (USB, internal artifact repo, etc.), then:
docker load < pslens-v1.4.2.tar.gz

You can also mirror the image into your own private registry (Harbor, AWS ECR, Azure ACR, GitLab Registry). Pull it once, retag, push, and reference the mirrored image in your docker-compose.yml. Cedar Hills Group is happy to provide a one-time pull script if you need to automate this.

Troubleshooting Pull Failures

ErrorCauseFix
denied: deniedToken expired or revokedRenew the PAT with Cedar Hills Group
unauthorizedToken has the wrong scopePAT needs read:packages on the pslens package
no basic auth credentialsdocker login wasn’t run, or ~/.docker/config.json was lostRe-run docker login ghcr.io
manifest unknownThe tag you asked for doesn’t exist yetCheck the release notes for available tags

2. Configuration and Secrets

The most failure-prone part of self-hosted deployment is preserving configuration and secrets through upgrades. This section is explicit about what survives docker compose pull && docker compose up -d and what doesn’t.

What Persists, What Doesn’t

Persistent (must be on a volume):

  • /data/nats — NATS JetStream store. Contains the recently-viewed objects KV, the report store (generated markdown reports), alert state, and, if you use the in-app config UI, the AES-256-encrypted database passwords KV.
  • /data/projects — project store for uploaded .zip project archives.
  • /app/config.yaml — bind-mounted from the host filesystem.

Ephemeral (re-created on every container start):

  • Whitelist cache (re-fetched from PeopleSoft on startup).
  • PIA URL discovery cache.
  • In-memory session state.

The default docker-compose.yml in Installation already wires the persistent items correctly: a named volume pslens_data mounted at /data, and config.yaml bind-mounted at /app/config.yaml:ro. As long as you don’t docker volume rm pslens_data, your data survives any number of image upgrades.

Three Configuration Modes

There are three ways to source configuration. Pick one based on how many people will administer the system and how you manage secrets.

ModeWhere config livesWhere secrets liveBest for
A. File-onlyconfig.yaml bind-mounted from hostPlaintext in config.yamlInternal-only dev/test
B. File + env overrideconfig.yaml for non-secretsPSLENS_DB_{NAME}_PASSWORD env vars, sourced from .env or a secrets managerRecommended default for client-hosted
C. KV-encryptedMinimal config.yaml; full config in NATS KV bucket, AES-256 encrypted at restEncrypted blob in /data/nats, unlocked by PSLENS_MASTER_KEYMulti-admin setups where you use the in-app config UI

config.yaml:

server:
  port: 8080
  host: "0.0.0.0"
  appBaseURL: "https://pslens.example.com"
  natsStoreDir: "/data/nats"

databases:
  - name: "PROD"
    description: "Production HCM"
    baseURL: "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_SWS_PSOFTQL/"
    username: "PSLENS_API"
    password: "placeholder"      # Overridden by PSLENS_DB_PROD_PASSWORD
    timezone: "America/Chicago"

.env (sibling of docker-compose.yml, chmod 600, gitignored):

PSLENS_DB_PROD_PASSWORD=actual-password-here
PSLENS_MASTER_KEY=base64-encoded-32-byte-key

docker-compose.yml references env_file: .env; Docker injects every variable into the container at startup.

Tip: The env-var override convention is PSLENS_DB_{NAME}_PASSWORD where {NAME} is the database name from config.yaml, uppercased. For a database named DEV_HR, the variable is PSLENS_DB_DEV_HR_PASSWORD.

About PSLENS_MASTER_KEY

In production, psLens requires PSLENS_MASTER_KEY to be set. It’s used to encrypt database passwords stored in the NATS KV bucket. Generate one once:

openssl rand -base64 32

Critical: back this key up out-of-band in your password manager, AWS Secrets Manager, HashiCorp Vault, or wherever you keep root-of-trust secrets. If you lose the master key, the encrypted password blob in /data/nats becomes unrecoverable and you’ll have to re-enter every database password.

Backups

Daily backup of the data volume is one line:

docker run --rm \
  -v pslens_data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/pslens-data-$(date +%F).tar.gz -C / data

What to back up where:

  • Data volume (pslens_data) — daily tarball, retain 14-30 days. Captures reports, alert state, and encrypted passwords KV.
  • config.yaml — check into your infrastructure-as-code repo (gitignore the password fields, or use the placeholder pattern from Mode B).
  • .env — store in your secrets manager. Never check this into git.

To restore: stop psLens, docker volume create pslens_data, untar into the volume, restart.


3. TLS / HTTPS Options

psLens does not terminate TLS in the binary by default. It listens on plain HTTP and expects either a reverse proxy, an in-binary TLS configuration, or a tunnel to provide HTTPS.

There are six viable options. They’re compared below on the same axes: certificate source, automation, operational complexity, and the scenario each fits best.

Quick recommendation

Your scenarioRecommended option
Default — most clientsOption 3: Caddy sidecar
Corporate PKI with certs-as-codeOption 4: nginx sidecar (or Option 1 if minimalist)
Internal-only, small team, already using TailscaleOption 6: Tailscale Serve / Funnel
Already running TraefikOption 5: Traefik
Public internet, single host, no proxy wantedOption 2: in-binary autocert

Details on each option follow.

Option 1: Go-native TLS via crypto/tls (cert files)

psLens loads a PEM cert + key from disk and serves TLS directly. No reverse proxy, no extra container, no external dependencies.

Status: This requires a small code change to psLens (currently the binary only listens on plain HTTP). Contact Cedar Hills Group if you need this option — it’s roughly 30 lines of Go and a config block. Tracked in the backlog.

How it works once implemented:

server:
  tls:
    enabled: true
    certFile: "/certs/pslens.crt"
    keyFile: "/certs/pslens.key"

Mount /certs as a read-only bind from the host where your PKI tooling drops renewed certs.

AxisDetail
Certificate sourceCustomer-provided — corporate CA, commercial CA, or self-signed
RenewalCustomer’s responsibility (cron + cert rotation tool)
Hot reloadNot supported; container restart picks up new certs
ProsZero external dependencies, single container, familiar to enterprise teams with existing PKI
ConsYou manage the cert lifecycle; an expired cert means an outage
Best forEnterprises with internal PKI tooling (Venafi, AWS ACM Private CA, Vault PKI)

Option 2: Go-native TLS via acme/autocert (Let’s Encrypt)

psLens fetches and renews Let’s Encrypt certs in-process using the golang.org/x/crypto/acme/autocert library.

Status: Like Option 1, this requires a small code addition to psLens. Contact Cedar Hills Group.

How it works once implemented:

server:
  tls:
    autocert:
      enabled: true
      hostnames: ["pslens.example.com"]
      email: "admin@example.com"
      cacheDir: "/data/acme"

Cert cache lives in /data/acme so it survives container restarts as long as the pslens_data volume does. psLens listens on :80 for the ACME HTTP-01 challenge and :443 for TLS.

AxisDetail
Certificate sourceLet’s Encrypt (free, 90-day, auto-renewed at ~60 days)
RenewalFully automatic, in-process
Hot reloadN/A — the library reloads on its own renewal cycle
ProsCheapest TLS, zero ops effort after initial config
ConsRequires port 80 reachable from the public internet for HTTP-01 challenge; rules out fully internal deployments
Best forPublic-internet hosts on a real domain (pslens.client.com)

Option 3: Caddy sidecar

Run Caddy as a second service in the same docker-compose.yml. Caddy terminates TLS and reverse-proxies to psLens on the internal Docker network.

Caddyfile (5 lines for the public-internet case):

pslens.example.com {
    reverse_proxy pslens:8080
    encode gzip
}

For an internal-only deployment (no public DNS, no Let’s Encrypt), use Caddy’s built-in CA:

pslens.internal.example.com {
    tls internal
    reverse_proxy pslens:8080
}

You’ll need to add Caddy’s root cert to client browsers (push it via MDM) so they trust the internal cert.

docker-compose.yml addition:

services:
  pslens:
    image: ghcr.io/cedarhillsgroup/pslens:v1.4
    expose:
      - "8080"      # no longer "ports:" — only Caddy needs an external port
    volumes:
      - ./config.yaml:/app/config.yaml:ro
      - pslens_data:/data
    env_file: .env
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

volumes:
  pslens_data:
  caddy_data:
  caddy_config:
AxisDetail
Certificate sourceLet’s Encrypt (public), or Caddy’s built-in CA (tls internal)
RenewalFully automatic; cert state in the caddy_data volume
Hot reloadCaddy reloads certs on its own renewal cycle
ProsTrivial config, handles both public-internet and internal-only, decouples TLS from the app (restarting psLens doesn’t drop TLS sessions)
ConsSecond container to operate; internal CA requires distributing the root cert to clients
Best forThe default recommended option for most client deployments

Option 4: nginx sidecar

Same shape as Caddy but with nginx, using customer-provided cert files.

nginx.conf:

events {}

http {
    server {
        listen 443 ssl http2;
        server_name pslens.example.com;

        ssl_certificate     /certs/pslens.crt;
        ssl_certificate_key /certs/pslens.key;
        ssl_protocols       TLSv1.2 TLSv1.3;

        location / {
            proxy_pass         http://pslens:8080;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;

            # Server-Sent Events: disable buffering for the SSE endpoints
            proxy_buffering    off;
            proxy_cache        off;
        }
    }

    server {
        listen 80;
        server_name pslens.example.com;
        return 301 https://$host$request_uri;
    }
}

Important for psLens: the proxy_buffering off directive is required. psLens relies on Server-Sent Events for most of the UI; with buffering enabled, the UI will appear frozen until pages finish loading entirely.

docker-compose.yml addition:

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/certs:ro
    restart: unless-stopped

Cert renewal is a separate concern, typically certbot run as a host cron job that replaces the files in ./certs/ and signals nginx with docker compose exec nginx nginx -s reload.

AxisDetail
Certificate sourceCustomer-managed PEM (corporate CA, commercial CA, certbot)
RenewalCustomer’s responsibility (commonly certbot + cron)
Hot reloadYes via nginx -s reload
ProsThe most-deployed reverse proxy on earth; every enterprise ops team has nginx runbooks; easy to add request-level customization (auth, rate-limits, rewrites)
ConsNo built-in cert automation; more boilerplate than Caddy for the same outcome on the happy path
Best forClients who already standardize on nginx, or who need request-level customization

Option 5: Traefik sidecar

Same shape as Caddy but Traefik discovers routes from Docker labels on the psLens service. Useful only if the client already runs Traefik.

docker-compose.yml addition:

  pslens:
    # ... rest of config ...
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pslens.rule=Host(`pslens.example.com`)"
      - "traefik.http.routers.pslens.tls.certresolver=letsencrypt"
      - "traefik.http.services.pslens.loadbalancer.server.port=8080"

  traefik:
    image: traefik:v3
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      - --certificatesresolvers.letsencrypt.acme.email=admin@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_data:/letsencrypt
    restart: unless-stopped
AxisDetail
Certificate sourceLet’s Encrypt, customer PEM, or Vault
RenewalAutomatic
Hot reloadLive config reload from Docker labels
ProsIf the client already runs Traefik, psLens plugs in with zero net-new ops
ConsAdopting Traefik just for psLens isn’t worth the learning curve
Best forClients who already use Traefik (common in Kubernetes shops)

Option 6: Tailscale Serve / Funnel (or Cloudflare Tunnel)

Bypass TLS-at-psLens entirely by exposing the service over a managed tunnel. TLS terminates at the tunnel provider’s edge; psLens stays on plain HTTP inside the tunnel.

Tailscale Serve (private to your tailnet — internal use):

tailscale serve --bg --https 443 http://localhost:8080

You’ll get a URL like https://pslens.tailnet-name.ts.net. Tailscale issues and renews the cert. Only members of your tailnet can reach it.

Tailscale Funnel (public internet via Tailscale’s edge):

tailscale funnel --bg 443

Same URL shape; reachable from the public internet but rate-limited and not designed for high-volume traffic. Fine for an admin dashboard.

Cloudflare Tunnel (public, no inbound ports):

Install cloudflared on the Docker host (or run it as a sidecar container). Authenticate, create a tunnel, point a Cloudflare-managed DNS name at it.

AxisDetail
Certificate sourceTunnel provider (Tailscale or Cloudflare)
RenewalFully automatic
ProsZero TLS config on the psLens side; no inbound ports opened on the firewall; mesh networking (Tailscale) is great for multi-DB connectivity
ConsAdds a third-party dependency in the data path; some clients have policies against cloud tunnels for compliance-relevant tools; rate limits
Best forInternal-only deployments where you want easy access for a small team without standing up a reverse proxy or opening firewall ports

4. Upgrades

The upgrade flow depends on whether you can reach ghcr.io and whether you’re pinning to a specific version. The data volume and config.yaml are untouched in all three cases.

Standard (online) upgrade

If you pinned to :latest or to a vMAJOR.MINOR tag that’s auto-receiving patch fixes:

cd /opt/pslens
docker compose pull pslens
docker compose up -d pslens

pull fetches the new image; up -d recreates the psLens container with the new image and reattaches the existing volume and config. Data is preserved.

Pin to a specific tag in docker-compose.yml:

services:
  pslens:
    image: ghcr.io/cedarhillsgroup/pslens:v1.4

To upgrade to v1.5: edit the file, then pull and recreate:

# Edit docker-compose.yml: v1.4 to v1.5
docker compose pull pslens
docker compose up -d pslens

Rollback is a one-line edit back to the previous tag, then docker compose up -d pslens again. The old image is still in the local Docker cache (unless you ran docker image prune in between).

Air-gapped upgrade

# On a machine with internet:
docker pull ghcr.io/cedarhillsgroup/pslens:v1.5
docker save ghcr.io/cedarhillsgroup/pslens:v1.5 | gzip > pslens-v1.5.tar.gz

# Transfer the .tar.gz to the target host, then:
docker load < pslens-v1.5.tar.gz
docker compose up -d pslens

Cedar Hills Group’s breaking-change contract

  • Stable across minor versions: env-var names (PSLENS_DB_{NAME}_PASSWORD, PSLENS_MASTER_KEY), volume mount paths (/data, /app/config.yaml), and the data on disk.
  • Documented in CHANGELOG.md: any config schema change. Schema changes happen on major version bumps.
  • Automatic: NATS KV bucket schema migrations run on first start of a new version.

Before a major-version upgrade: always take a backup of the pslens_data volume (see Backups above). If something goes wrong, you can restore the volume and roll the image back to the previous tag.


See Also

5 - Architecture Overview

How psLens connects to PeopleSoft: a small SWS framework inside your PeopleSoft environment, and a psLens Docker container hosted externally.

Architecture Overview

psLens has only two moving parts:

  1. The SWS framework, a small Integration Broker service installed inside your PeopleSoft environment.
  2. The psLens application, a single Docker container hosted externally (default: fly.io) or on your own infrastructure.

Everything psLens displays (search results, alerts, reports) flows over a single HTTPS connection from the psLens container into your SWS endpoint. There is no other channel.

The Short Version

  • Two components, nothing else. SWS inside PeopleSoft; psLens as a Docker container outside it.
  • Traffic only flows one way. psLens calls SWS over HTTPS. SWS never reaches out to psLens.
  • One protocol. REST + HTTP basic auth + psoftQL JSON queries. No database drivers, no ODBC, no jump hosts.
  • One scope. SWS only answers queries against PeopleTools metadata tables that you whitelist. Anything outside the list is rejected before it touches the database.
  • Dedicated deployment per customer. No shared psLens app, no shared storage, no multi-tenant SaaS backend.

How the Pieces Fit

%%{init: {"flowchart": {"htmlLabels": true, "padding": 16, "nodeSpacing": 60, "rankSpacing": 80, "subGraphTitleMargin": {"top": 10, "bottom": 14}}}}%%
flowchart LR
    USER(["Your team's<br/>web browser"])
    subgraph EXT["Cedar Hills Group hosted or your own infrastructure"]
        APP["psLens<br/>Docker container"]
    end
    subgraph PS["Your PeopleSoft Environment"]
        SWS["SWS Framework<br/>REST endpoint"]
        DB[("PeopleSoft DB<br/>read-only<br/>whitelisted tables")]
        SWS --> DB
    end
    USER -- "HTTPS" --> APP
    APP -- "HTTPS · Basic auth<br/>psoftQL JSON" --> SWS

    classDef ps fill:#e8f4fd,stroke:#0d6efd,stroke-width:2px,color:#000
    classDef ext fill:#fff5e6,stroke:#fd7e14,stroke-width:2px,color:#000
    classDef user fill:#e9f7ef,stroke:#198754,stroke-width:2px,color:#000
    classDef subgraphStyle fill:#fafafa,stroke:#666,stroke-width:1px,color:#000
    class SWS,DB ps
    class APP ext
    class USER user
    class PS,EXT subgraphStyle

Your team reaches psLens with any current web browser over HTTPS. There is no desktop client to install. Everything the user sees comes from the psLens container; the browser never talks to PeopleSoft directly.

Inside Your PeopleSoft Environment: SWS

The SWS framework is a small Integration Broker service Cedar Hills Group provides. Your PeopleSoft team installs it once, alongside everything else PeopleSoft already runs. It exposes a single REST endpoint that accepts a structured query language called psoftQL and returns JSON.

What SWS gives you control over:

  • The whitelist. Your PeopleSoft admins decide which PeopleTools metadata tables SWS is allowed to read. psLens cannot ask for anything off the list.
  • The credentials. SWS authenticates incoming requests with HTTP basic auth. Your team owns the token; rotating it is a config change on both ends.
  • The audit trail. Calls land on your Integration Broker like any other inbound service, visible in the tooling your team already monitors.

No PeopleSoft database username or password is ever shared with psLens.

Outside Your Environment: psLens Container

psLens itself is a single Docker container: one Go binary, with embedded NATS for storing alert history and report output. That’s the entire runtime.

  • Default deployment is on fly.io as a managed instance dedicated to your organization.
  • Self-hosting is fully supported. Docker, docker-compose, bare-metal, and air-gapped environments are all covered in the installation guide.
  • Stateless toward PeopleSoft. psLens does not copy your business data. The only things it persists are alert history and report output, both inside its own dedicated storage. See the Security & Trust page for details on what is and isn’t stored.

When you upgrade psLens, you pull a new container image. Nothing inside PeopleSoft changes.

Server-Driven Hypermedia Architecture (Zero Client-Side Storage)

psLens renders pages on the server with Go templates and streams updates over Server-Sent Events using Datastar. There is no React, no Angular, no JSON API. The browser receives HTML fragments over SSE (TLS at the transport layer) and renders them.

Two consequences fall out of this:

  • Nothing from PeopleSoft is stored in the browser. psLens does not write to LocalStorage, SessionStorage, or IndexedDB. Closing the tab takes the active session data with it.
  • No JSON wire format. The server sends pre-rendered HTML; there is no client-side data structure for an attacker to scrape or tamper with. Page transitions are server round-trips of a few KB of HTML, and the browser tab holds no result set.

Why This Shape

  • Bounded blast radius. Even in a worst case where the psLens container were compromised, the SWS whitelist is the ceiling on what an attacker could read. They cannot drop into PeopleSoft, run PeopleCode, or pivot to other tables.
  • Easy to upgrade and operate. New psLens features ship as a new container image. No PeopleSoft change request, no App Designer migration, no downtime on the PeopleSoft side.
  • Multi-environment from day one. A single psLens deployment can connect to DEV, TEST, and PROD at the same time. Point at the SWS endpoint in each environment via separate database entries in config.yaml.

Where Next

  • Installation installs SWS in PeopleSoft and runs the psLens container.
  • Configuration wires psLens to your PeopleSoft environments.
  • Security & Trust covers the read-only design, dedicated deployment, and what psLens does and doesn’t store.