Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion public/admin/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@
<button onclick="cliUpdatesList()">List available updates</button>
<button onclick="cliUpdatesRun()">Run available updates</button>
</div>
<div id="cli-options">
<button onclick="sampleDataList()">List sample data profiles</button>
<select id="sample-data-profile">
<option value="en_community_radio">English Community Radio Station</option>
</select>
<input type="text" id="sample-data-username" placeholder="Observer admin username" value="admin">
<input type="password" id="sample-data-password" placeholder="Observer admin password">
<button onclick="sampleDataRun()">Import sample data</button>
</div>
<div id="cli-output" class="ansi_color_bg_black"></div>
</main>
</body>
</html>
</html>
69 changes: 68 additions & 1 deletion public/admin/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,77 @@
}

$validCommands = ['check', 'cron run', 'updates list all', 'updates run all'];

// Sample-data tool: lives in tools/sampledata/ and talks to the Observer
// API over HTTP. We pass Observer admin credentials through the environment
// so they don't appear in the process list.
if ($json->command === 'sampledata list') {
$output = [];
$resultCode = 0;
$cmd = 'php ' . escapeshellarg(__DIR__ . '/../../tools/sampledata/seed.php') . ' list 2>&1';
exec($cmd, $output, $resultCode);

echo json_encode([
'message' => 'Listing sample data profiles.',
'result' => $converter->convert(implode(PHP_EOL, $output) ?: 'No output.'),
'theme' => $theme->asCss()
]);

exit();
}

if (preg_match('/^sampledata run ([a-z0-9_]+)$/', $json->command, $matches)) {
$profile = $matches[1];
$obUser = $json->sampleDataUsername ?? null;
$obPass = $json->sampleDataPassword ?? null;

if (!$obUser || !$obPass) {
http_response_code(400);
echo json_encode(['message' => 'Observer admin credentials required to import sample data.']);
exit();
}

$baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http')
. '://' . ($_SERVER['HTTP_HOST'] ?? '127.0.0.1');

$env = [
'OB_BASE_URL' => $baseUrl,
'OB_USERNAME' => $obUser,
'OB_PASSWORD' => $obPass,
// Preserve PATH so curl etc. resolve.
'PATH' => getenv('PATH') ?: '/usr/local/bin:/usr/bin:/bin',
];

$cmd = 'php ' . escapeshellarg(__DIR__ . '/../../tools/sampledata/seed.php')
. ' run ' . escapeshellarg($profile) . ' 2>&1';

$descriptors = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open($cmd, $descriptors, $pipes, null, $env);

if (!is_resource($process)) {
http_response_code(500);
echo json_encode(['message' => 'Failed to start the sample-data tool.']);
exit();
}

$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
$resultCode = proc_close($process);

echo json_encode([
'message' => $resultCode === 0 ? 'Sample data imported.' : 'Sample data import failed.',
'result' => $converter->convert($stdout ?: 'No output.'),
'theme' => $theme->asCss()
]);

exit();
}

if (in_array($json->command, $validCommands)) {
$output = [];
$resultCode = 0;
exec(__DIR__ . "/../../tools/cli/ob {$json->command}", $output);
exec(__DIR__ . "/../../cli/ob {$json->command}", $output);

$output = $converter->convert(implode(PHP_EOL, $output));
if ($output === "" && $resultCode === 0) {
Expand Down
37 changes: 35 additions & 2 deletions public/admin/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async function run(data)
});

const output = document.querySelector("#cli-output");

response.json().then((data) => {
if (! response.ok) {
output.innerHTML += '<p class="error">' + data.message + '</p>';
Expand Down Expand Up @@ -58,4 +58,37 @@ async function cliUpdatesRun()
};

run(data);
}
}

async function sampleDataList()
{
const data = {
command: "sampledata list"
};

run(data);
}

async function sampleDataRun()
{
const profile = document.querySelector("#sample-data-profile").value;
const username = document.querySelector("#sample-data-username").value;
const password = document.querySelector("#sample-data-password").value;

if (! username || ! password) {
alert("Observer admin username and password are required to import sample data.");
return;
}

if (! confirm("Import sample data from profile: " + profile + "?\n\nThis will add categories, genres, users, playlists, and schedules. Existing data will not be overwritten.")) {
return;
}

const data = {
command: "sampledata run " + profile,
sampleDataUsername: username,
sampleDataPassword: password
};

run(data);
}
90 changes: 90 additions & 0 deletions tools/sampledata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Sample Data Seeder

Populates a fresh OpenBroadcaster Observer install with categories, genres,
permission groups, users, settings, custom metadata fields, playlists, a sample
player, and a daily show schedule.

The seeder talks to the Observer API over HTTP using `curl` — it does not touch
the database directly and does not depend on Observer's CLI or core code.

## Requirements

- PHP 8.1+ on the machine running the seeder (uses curl extension)
- Network access to a running Observer instance
- Credentials for an Observer admin user with permissions to manage users,
permissions, players, playlists, schedules, media settings, metadata, and
global client storage

## Usage

```sh
# List available profiles
php tools/sampledata/seed.php list

# Run a profile against a server
php tools/sampledata/seed.php run en_community_radio \
--base-url=https://observer.example.com \
--username=admin

# Same, but read all settings from environment variables
OB_BASE_URL=https://observer.example.com \
OB_USERNAME=admin \
OB_PASSWORD=hunter2 \
php tools/sampledata/seed.php run en_community_radio
```

If `--password` is not given and `OB_PASSWORD` is not set, the seeder prompts
interactively (without echoing) when run from a terminal.

### Configuration precedence

Each setting can come from a CLI flag, an environment variable, or a default;
the first source listed wins.

| Setting | CLI flag | Env var | Default |
| ---------- | ------------ | ------------ | ------------------------ |
| Base URL | `--base-url` | `OB_BASE_URL`| `http://127.0.0.1:8080` |
| Username | `--username` | `OB_USERNAME`| `admin` |
| Password | `--password` | `OB_PASSWORD`| (interactive prompt) |

## Idempotency

The seeder is safe to re-run. Before creating each item it checks whether one
with the same name already exists and skips it if so. This applies to
categories, genres, permission groups, users, custom metadata fields,
playlists, and the sample player.

Settings (file format whitelists, core metadata flags, login message, welcome
page) are always overwritten on each run, matching the original seeder
behaviour — the API endpoints for these settings are inherently
overwrite-only.

## Profile layout

```
tools/sampledata/profiles/<profile_name>/
manifest.json # name, description, version
categories.json # ["Music", "News", ...]
genres.json # { "<category>": [{name, description}, ...] }
groups.json # [{ name, permissions: ["perm_name", ...] }]
users.json # [{ name, username, email, password, ... , group }]
settings.json # { settings, client_login_message, client_welcome_page }
metadata_fields.json # [{ name, type, mode, visibility, ... }]
playlists.json # [{ name, description, status, type }]
schedule.json # { player: {...}, shows: [{ title, start_time, ... }] }
```

Each step is optional — if a JSON file is missing for a step the seeder logs
`[SKIP]` and moves on.

## Adding a new profile

Create a new directory under `tools/sampledata/profiles/` named with
lowercase letters, digits, and underscores only (matches `^[a-z0-9_]+$`), then
add the JSON files above. `seed.php list` will pick it up automatically.

## Admin UI integration

The Observer admin panel at `/admin/` exposes the seeder via the "Import
sample data" button, which `exec()`s this script with credentials supplied in
the form. The same auth and idempotency rules apply.
22 changes: 22 additions & 0 deletions tools/sampledata/profiles/en_community_radio/categories.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
"Commercial Ad",
"Community Events",
"Documents",
"Images",
"Interviews",
"Lecture Talk or Discussion",
"Music",
"News",
"Pop Vox",
"Priority Broadcast",
"PSA Audio",
"PSA Image",
"PSA Video",
"SFX",
"Show Promotion",
"Show Sponsor",
"Shows Complete",
"Station ID",
"Video",
"VoiceTrack"
]
Loading
Loading