Compare commits

..

3 Commits

Author SHA1 Message Date
Ean Milligan
8b36742150 update xivplan to support export button, try to make it clear db mod is not the same as xivplan for bug reporting 2026-04-24 14:47:59 -04:00
Ean Milligan
f412f06e10 implement export all 2026-04-24 14:28:01 -04:00
Ean Milligan
31e3e13ab9 pin bcrypt version 2026-04-24 14:00:37 -04:00
8 changed files with 161 additions and 40 deletions

View File

@@ -1,8 +1,28 @@
diff --git a/index.html b/index.html
index 65570da..ff4ae99 100644
--- a/index.html
+++ b/index.html
@@ -2,10 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
- <title>FFXIV Raid Planner</title>
- <meta name="description" content="A tool for diagramming raid strategies for Final Fantasy XIV" />
+ <title>FFXIV Raid Planner + DB</title>
+ <meta
+ name="description"
+ content="A tool for diagramming raid strategies for Final Fantasy XIV. This instance includes a small mod to add a built in online DB to simplify sharing plans."
+ />
<meta name="viewport" content="width=1280, initial-scale=1.0" />
- <meta name="description" content="FFXIV Raid Planner" />
<meta name="theme-color" content="#1e1e1e" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
</head>
diff --git a/src/AboutDialog.tsx b/src/AboutDialog.tsx
index a346146..b4ef320 100644
index a346146..12c5487 100644
--- a/src/AboutDialog.tsx
+++ b/src/AboutDialog.tsx
@@ -30,6 +30,13 @@ export const AboutDialog: React.FC<AboutDialogProps> = (props) => {
@@ -30,6 +30,18 @@ export const AboutDialog: React.FC<AboutDialogProps> = (props) => {
<HotkeyBlockingDialogBody>
<DialogTitle>About</DialogTitle>
<DialogContent className={classes.content}>
@@ -12,10 +32,34 @@ index a346146..b4ef320 100644
+ very basic database behind it to make more accessible share links. This minimal mod is open
+ source and can be found{' '}
+ <ExternalLink href="https://git.milligan.dev/xivdev/XIVPlan-DB">here</ExternalLink>.
+ </p>
+ <p>
+ If you run into any issues regarding saving/loading/signing into the DB mod (basically
+ anything that is on the Online DB part of the Open/Save screens), please email{' '}
+ <Link>ean [at] milligan [dot] dev</Link>.
+ </p>
<p>
XIVPlan is a tool for quickly diagramming raid strategies for Final Fantasy XIV, inspired by{' '}
<ExternalLink href="https://raidplan.io">RaidPlan.io</ExternalLink> and{' '}
@@ -41,7 +53,8 @@ export const AboutDialog: React.FC<AboutDialogProps> = (props) => {
<p>
XIVPlan is open source on{' '}
<ExternalLink href="https://github.com/joelspadin/xivplan">GitHub</ExternalLink>. If you
- find a bug or have other feedback, please create a{' '}
+ find a bug with XIVPlan itself (excluding anything relating to the Online DB) or have other
+ feedback, please create a{' '}
<ExternalLink href="https://github.com/joelspadin/xivplan/issues/new/choose">
new issue
</ExternalLink>{' '}
@@ -61,7 +74,7 @@ export const AboutDialog: React.FC<AboutDialogProps> = (props) => {
<ExternalLink href="https://github.com/kotarou3/ffxiv-arena-images">
arena background images
</ExternalLink>{' '}
- by kotarou3
+ by kotarou3.
</p>
</DialogContent>
<DialogActions>
diff --git a/src/App.tsx b/src/App.tsx
index 05a2fb7..f7ed022 100644
--- a/src/App.tsx
@@ -133,7 +177,7 @@ index 0000000..81bad97
+ return <>Loading plan from DB, please wait . . .</>;
+};
diff --git a/src/SiteHeader.tsx b/src/SiteHeader.tsx
index 524a0d3..8642bd4 100644
index 524a0d3..695a0f3 100644
--- a/src/SiteHeader.tsx
+++ b/src/SiteHeader.tsx
@@ -75,7 +75,7 @@ export const SiteHeader: React.FC<HTMLAttributes<HTMLElement>> = ({ className, .
@@ -145,6 +189,15 @@ index 524a0d3..8642bd4 100644
</Text>
{source && <SourceIndicator source={source} />}
</div>
@@ -87,7 +87,7 @@ export const SiteHeader: React.FC<HTMLAttributes<HTMLElement>> = ({ className, .
Help
</Link>
<AboutDialog className={classes.link} />
- <ExternalLink className={classes.link} href="https://github.com/joelspadin/xivplan" noIcon>
+ <ExternalLink className={classes.link} href="https://git.milligan.dev/xivdev/XIVPlan-DB" noIcon>
GitHub
</ExternalLink>
<div>
@@ -117,7 +117,10 @@ const SourceIndicator: React.FC<SourceIndicatorProps> = ({ source }) => {
return (
<Tooltip content={tooltip} relationship="description">
@@ -297,10 +350,10 @@ index 63a473b..d32cb53 100644
</TabActivity>
diff --git a/src/file/FileDialogDB.tsx b/src/file/FileDialogDB.tsx
new file mode 100644
index 0000000..05ea7ce
index 0000000..2e4a31c
--- /dev/null
+++ b/src/file/FileDialogDB.tsx
@@ -0,0 +1,508 @@
@@ -0,0 +1,515 @@
+import {
+ Button,
+ Checkbox,
@@ -533,6 +586,16 @@ index 0000000..05ea7ce
+ }
+ };
+
+ const exportAll = (event: MouseEvent<HTMLElement>) => {
+ event.preventDefault();
+ const a = document.createElement('a');
+ a.href = `/api/export/${dbUser?.userId}`;
+ a.download = `${dbUser?.userName}-plans.zip`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ };
+
+ const loadPlans = async (userId: string) => {
+ const newPlans = await getPlans(userId);
+
@@ -721,17 +784,14 @@ index 0000000..05ea7ce
+ {renderModal2()}
+ <InPortal node={actions}>
+ <DialogActions fluid style={{ width: '100%' }}>
+ <Tooltip
+ appearance="inverted"
+ showDelay={0}
+ relationship="label"
+ content="Coming Soon&trade;"
+ withArrow
+ <Button
+ icon={<ArrowDownloadRegular />}
+ style={{ marginRight: 'auto' }}
+ disabled={!dbUser}
+ onClick={exportAll}
+ >
+ <Button icon={<ArrowDownloadRegular />} style={{ marginRight: 'auto' }} disabled>
+ Export all
+ </Button>
+ </Tooltip>
+ <Button appearance="primary" disabled={!dbUser || selectedRows.size === 0} onClick={openCallback}>
+ Open
+ </Button>

View File

@@ -22,7 +22,8 @@
},
"nodeModulesDir": "none",
"imports": {
"@bcrypt": "https://deno.land/x/bcrypt/mod.ts",
"@bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts",
"@deno-zip": "https://deno.land/x/jszip@0.11.0/mod.ts",
"@mysql": "https://deno.land/x/mysql@v2.12.1/mod.ts",
"@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
"@std/http": "jsr:@std/http@1.0.15",

53
deno.lock generated
View File

@@ -9,7 +9,19 @@
}
},
"redirects": {
"https://deno.land/x/bcrypt/mod.ts": "https://deno.land/x/bcrypt@v0.4.1/mod.ts"
"https://deno.land/x/bcrypt/mod.ts": "https://deno.land/x/bcrypt@v0.4.1/mod.ts",
"https://esm.sh/core-util-is@~1.0.0?target=denonext": "https://esm.sh/core-util-is@1.0.3?target=denonext",
"https://esm.sh/immediate@~3.0.5?target=denonext": "https://esm.sh/immediate@3.0.6?target=denonext",
"https://esm.sh/isarray@~1.0.0?target=denonext": "https://esm.sh/isarray@1.0.0?target=denonext",
"https://esm.sh/lie@~3.3.0?target=denonext": "https://esm.sh/lie@3.3.0?target=denonext",
"https://esm.sh/pako@~1.0.2?target=denonext": "https://esm.sh/pako@1.0.11?target=denonext",
"https://esm.sh/process-nextick-args@~2.0.0?target=denonext": "https://esm.sh/process-nextick-args@2.0.1?target=denonext",
"https://esm.sh/readable-stream@~2.3.6?target=denonext": "https://esm.sh/readable-stream@2.3.8?target=denonext",
"https://esm.sh/safe-buffer@~5.1.0?target=denonext": "https://esm.sh/safe-buffer@5.1.2?target=denonext",
"https://esm.sh/safe-buffer@~5.1.1?target=denonext": "https://esm.sh/safe-buffer@5.1.2?target=denonext",
"https://esm.sh/set-immediate-shim@~1.0.1?target=denonext": "https://esm.sh/set-immediate-shim@1.0.1?target=denonext",
"https://esm.sh/string_decoder@~1.1.1?target=denonext": "https://esm.sh/string_decoder@1.1.1?target=denonext",
"https://esm.sh/util-deprecate@~1.0.1?target=denonext": "https://esm.sh/util-deprecate@1.0.2?target=denonext"
},
"remote": {
"https://deno.land/std@0.104.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
@@ -40,6 +52,18 @@
"https://deno.land/std@0.104.0/log/mod.ts": "91711789b28803082b1bdfb123d2c9685a7e01767f2e79c0a82706063ad964d8",
"https://deno.land/std@0.104.0/testing/_diff.ts": "5d3693155f561d1a5443ac751ac70aab9f5d67b4819a621d4b96b8a1a1c89620",
"https://deno.land/std@0.104.0/testing/asserts.ts": "e4311d45d956459d4423bc267208fe154b5294989da2ed93257b6a85cae0427e",
"https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.116.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac",
"https://deno.land/std@0.116.0/fs/walk.ts": "31464d75099aa3fc7764212576a8772dfabb2692783e6eabb910f874a26eac54",
"https://deno.land/std@0.116.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
"https://deno.land/std@0.116.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
"https://deno.land/std@0.116.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
"https://deno.land/std@0.116.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4",
"https://deno.land/std@0.116.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820",
"https://deno.land/std@0.116.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
"https://deno.land/std@0.116.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2",
"https://deno.land/std@0.116.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
"https://deno.land/std@0.116.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e",
"https://deno.land/std@0.77.0/fmt/colors.ts": "c5665c66f1a67228f21c5989bbb04b36d369b98dd7ceac06f5e26856c81c2531",
"https://deno.land/x/bcrypt@v0.4.1/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7",
"https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7",
@@ -49,6 +73,7 @@
"https://deno.land/x/bytes_formater@v1.4.0/deps.ts": "4f98f74e21145423b873a5ca6ead66dc3e674fa81e230a0a395f9b86aafeceea",
"https://deno.land/x/bytes_formater@v1.4.0/format.ts": "657c41b9f180c3ed0f934dcf75f77b09b6a610be98bb07525bffe2acfd5af4d5",
"https://deno.land/x/bytes_formater@v1.4.0/mod.ts": "c6bf35303f53d74e9134eb13f666fb388fb4c62c6b12b17542bbadade250a864",
"https://deno.land/x/jszip@0.11.0/mod.ts": "5661ddc18e9ac9c07e3c5d2483bc912a7022b6af0d784bb7b05035973e640ba1",
"https://deno.land/x/mysql@v2.12.1/deps.ts": "68635959a41bb08bc87db007679fb8449febc55d48202dff20b93cc23ef5820d",
"https://deno.land/x/mysql@v2.12.1/mod.ts": "3246c9c259434563be69cc95d5b792f8aac7ef5d10b8a6c6589aa54ebf1bd266",
"https://deno.land/x/mysql@v2.12.1/src/auth.ts": "129ea08b180d3e90e567c3f71e60432bb266304c224e17ea39d604bbcc1160d8",
@@ -82,7 +107,31 @@
"https://deno.land/x/nanoid@v3.0.0/nanoid.ts": "8d119bc89a0f34e7bbe0c2dbdc280d01753e431af553d189663492310a31085d",
"https://deno.land/x/nanoid@v3.0.0/random.ts": "4da71d5f72f2bfcc6a4ee79b5d4e72f48dcf4fe4c3835fd5ebab08b9f33cd598",
"https://deno.land/x/nanoid@v3.0.0/urlAlphabet.ts": "8b1511deb1ecb23c66202b6000dc10fb68f9a96b5550c6c8cef5009324793431",
"https://deno.land/x/sql_builder@v1.9.1/util.ts": "b9855dc435972704cf82655019f4ec168ac83550ab4db596c5f6b6d201466384"
"https://deno.land/x/sql_builder@v1.9.1/util.ts": "b9855dc435972704cf82655019f4ec168ac83550ab4db596c5f6b6d201466384",
"https://esm.sh/core-util-is@1.0.3/denonext/core-util-is.mjs": "cfcf1ae63d56751cbe4b3b90b90b7eea577c5380c4adc272ddea4b7db2bdbbf2",
"https://esm.sh/core-util-is@1.0.3?target=denonext": "6c72958f8a1c8f42016b48c984a0f3d799ea1e0cd321f499fec0bf8db916c17f",
"https://esm.sh/immediate@3.0.6/denonext/immediate.mjs": "7148ba33cb905f7aca49affbacfa6a8257cd6b89e8c3c7c728d2d0387b4cce29",
"https://esm.sh/immediate@3.0.6?target=denonext": "fba8d9ddb37f19ff27c0b1c5b4486ab82805114b14959379d92ca05d6351c5d3",
"https://esm.sh/isarray@1.0.0/denonext/isarray.mjs": "0f26133cd58fc8580f99bbfd81f6290718328dc2a683c313c36f6b1e8c174edc",
"https://esm.sh/isarray@1.0.0?target=denonext": "00e227f6d016cb5a5f832f6f2de91dd8ab092c7ac830c551bfcf0f63284d89e6",
"https://esm.sh/jszip@3.7.1": "5161d6a228d844791a60ab58360bd3b76c4d3921b4a725616cd7403203519249",
"https://esm.sh/jszip@3.7.1/denonext/jszip.mjs": "325e8509d94e1460a8bb0bbb58b47a7b70c63b48568a60993e2880dba3a3062d",
"https://esm.sh/lie@3.3.0/denonext/lie.mjs": "20db2fef139e87d467b7cf24a9e53053e96460fefedde5910f925b1d0ddc0cba",
"https://esm.sh/lie@3.3.0?target=denonext": "74a2c724bd2fef30c46c612632dfd2ee37394f1a4540eb112e0df2ef98df0434",
"https://esm.sh/pako@1.0.11/denonext/pako.mjs": "4895feb3e2441ef725da4052a5dc93d219d065fef1decc4452bb9b7ee1477c0d",
"https://esm.sh/pako@1.0.11?target=denonext": "bc43f66ed245d58d468bf9867b3e9080c5b0590b4c14038ea308954490e0b2ea",
"https://esm.sh/process-nextick-args@2.0.1/denonext/process-nextick-args.mjs": "adffdd507c6571957aaab9d3f0a2aa54febdda1b4d546a57967fd2299505339e",
"https://esm.sh/process-nextick-args@2.0.1?target=denonext": "b80260031d83086964facc0efc6e2cc8fd878d9ce14dfcf6999e508a4d8d13d0",
"https://esm.sh/readable-stream@2.3.8/denonext/readable-stream.mjs": "ce8c7e2e7783c4487c1e9fcaf8824f0af26d48e6f5fe02fa9cbd70c34799ef98",
"https://esm.sh/readable-stream@2.3.8?target=denonext": "a8d158c470101e7518fdf293728d4cb8b2ab2cac73140940c8a9ee5542194e13",
"https://esm.sh/safe-buffer@5.1.2/denonext/safe-buffer.mjs": "848e2c2dafb98ea738399526e4396607872d1118acf8eb56eecd2a5f3be75568",
"https://esm.sh/safe-buffer@5.1.2?target=denonext": "3126988c629e3dc2d6126b26f654aceae10ad989622a21cb2a73ee72603f7df8",
"https://esm.sh/set-immediate-shim@1.0.1/denonext/set-immediate-shim.mjs": "a0fc9b90f281a6541c474dbf55184ef3a9360248f53cb3fa9479480cd24cdd40",
"https://esm.sh/set-immediate-shim@1.0.1?target=denonext": "8d30997d25a26dbcd4d79b613e6f400af85194f8e18e8e7014bc5fe3c9ffd429",
"https://esm.sh/string_decoder@1.1.1/denonext/string_decoder.mjs": "494e5a7fae95d5326e8aee93b4adfde75e389eea7a54bc1feea8549e786da032",
"https://esm.sh/string_decoder@1.1.1?target=denonext": "092c97b62b99368a40fa044c402188472658bc71529415f73c16f66c05aaf6bf",
"https://esm.sh/util-deprecate@1.0.2/denonext/util-deprecate.mjs": "083639894972cb68837eef26346c43bdd01357977149e0a4493f76192a4008b8",
"https://esm.sh/util-deprecate@1.0.2?target=denonext": "859f4df8ba771a4c33143185d3db6a7edb824fab1ed4f9a4b96ac0e6bc3ef1a4"
},
"workspace": {
"dependencies": [

21
mod.ts
View File

@@ -1,4 +1,5 @@
import { hash, compare } from '@bcrypt';
import { JSZip } from '@deno-zip';
import { customAlphabet } from '@nanoid';
import { STATUS_CODE, STATUS_TEXT, StatusCode } from '@std/http/status';
@@ -82,14 +83,28 @@ Deno.serve({ port: config.api.port }, async (req) => {
return genericResponse(STATUS_CODE.OK, JSON.stringify(plans));
} else if (path.startsWith('/export/')) {
const userId = path.replace('/export/', '');
const userMatch = await dbClient.query('SELECT id FROM users WHERE id = ?', [userId]).catch(() => {
const userMatch = await dbClient.query('SELECT id, name FROM users WHERE id = ?', [userId]).catch(() => {
failed = true;
});
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
if (!userMatch.length) return genericResponse(STATUS_CODE.NotFound, 'User ID does not exist.');
// WIP: export plans to zip code goes here
return genericResponse(STATUS_CODE.NotImplemented, 'Export function WIP.');
const plans = await dbClient
.query('SELECT name, folder, data FROM plans WHERE ownerId = ? AND deleted = 0 ORDER BY folder ASC,name ASC', [userId])
.catch(() => {
failed = true;
});
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
const zip = new JSZip();
for (const plan of plans) {
zip.addFile(`${plan.folder}${plan.folder ? '/' : ''}${plan.name}.xivplan`, plan.data);
}
return new Response(await zip.generateAsync({ type: 'blob' }), {
status: STATUS_CODE.OK,
statusText: STATUS_TEXT[STATUS_CODE.OK],
});
}
} else if (req.method === 'POST' && path === '/enroll') {
const body = await req.json();

View File

@@ -11,8 +11,8 @@ interface Plan {
const makePlanButtons = (planId: string, deleted: boolean) =>
deleted
? `<button onclick="doAction('undelete','${planId}')">restore</button><button onclick="doAction('perm-delete','${planId}')">perm delete</button>`
: `<button onclick="openPlan('${planId}')">open</button><button onclick="sharePlan('${planId}')">share</button><button onclick="doAction('rename','${planId}')">rename</button><button onclick="doAction('move','${planId}')">move</button><button onclick="doAction('delete','${planId}')">delete</button>`;
? `<button class="btn" onclick="doAction('undelete','${planId}')">restore</button><button class="btn" onclick="doAction('perm-delete','${planId}')">perm delete</button>`
: `<button class="btn" onclick="openPlan('${planId}')">open</button><button class="btn" onclick="sharePlan('${planId}')">share</button><button class="btn" onclick="doAction('rename','${planId}')">rename</button><button class="btn" onclick="doAction('move','${planId}')">move</button><button class="btn" onclick="doAction('delete','${planId}')">delete</button>`;
const makePlanItem = (plan: Plan, deleted: boolean) =>
`<li>${plan.folder}${plan.folder && '/'}${plan.name} - ${plan.lastUpdated.toLocaleString()} - ${makePlanButtons(plan.id, deleted)}</li>`;
@@ -82,19 +82,6 @@ function doAction(action, planId) {
}
});
}
function exportPlans() {
fetch('/api/export/${userId}')
.catch((e) => {
e.text().then((text) => {
alert(text);
});
})
.then((r) => {
r.text().then((text) => {
alert(text);
});
});
}
function openPlan(planId) {
window.open(\`${config.api.publicDomain}share#\${planId}\`);
}
@@ -165,7 +152,7 @@ function deleteAccount() {
<p>Please note: anything modifying data will require you to enter your PIN again as both the web view you are looking at and server behind it are completely stateless for simplicity.</p>
<p>DateTimeStamps on this page are all displayed in the US Eastern time zone. I don't care enough to make this extremely basic page dynamic.</p>
<h3>${userName}'s Plans:</h3>
<button onclick="exportPlans()" disabled style="color:black;cursor:not-allowed;">WIP: export all plans</button>
<a href="/api/export/${userId}" download="${userName}-plans.zip" class="btn">download all plans</a>
<ul>
${plans.map((plan) => makePlanItem(plan, false)).join('')}
</ul>
@@ -173,6 +160,6 @@ ${plans.map((plan) => makePlanItem(plan, false)).join('')}
<ul>
${deletedPlans.map((plan) => makePlanItem(plan, true)).join('')}
</ul>
<button onclick="deleteAccount()">Delete my account</button>
<button class="btn" onclick="deleteAccount()">Delete my account</button>
</div>`;
};

View File

@@ -20,6 +20,15 @@ a {
text-decoration:none;
color:white;
}
.btn {
font: 13.333px Sans;
padding: 1px 6px;
border: 1px outset buttonborder;
border-radius: 3px;
color: buttontext;
background-color: buttonface;
text-decoration: none;
}
li {
margin:0.5rem 0;
}

View File

@@ -1 +1 @@
deno run --allow-net --allow-import mod.ts
deno run --allow-net --allow-import --allow-env mod.ts

View File

@@ -13,7 +13,7 @@ xivplan_db_log="/var/log/xivplan_db.log"
xivplan_db_chdir="${xivplan_db_root}"
command="/usr/sbin/daemon"
command_args="-f -R 5 -P ${pidfile} -o ${xivplan_db_log} /usr/local/bin/deno run --allow-net --allow-import ${xivplan_db_root}/mod.ts"
command_args="-f -R 5 -P ${pidfile} -o ${xivplan_db_log} /usr/local/bin/deno run --allow-net --allow-import --allow-env ${xivplan_db_root}/mod.ts"
load_rc_config xivplan-db
run_rc_command "$1"