diff --git a/tools/upend_js/types.ts b/tools/upend_js/types.ts
index a876d60..8ef9424 100644
--- a/tools/upend_js/types.ts
+++ b/tools/upend_js/types.ts
@@ -25,6 +25,21 @@ export type IValue =
c: null;
};
+export interface InvariantEntry {
+ attribute: string;
+ value: IValue;
+}
+
+export type InAddress =
+ | Address
+ | { t: "Attribute" | "Url" | "Uuid"; c?: string };
+
+export type InEntry =
+ | IEntry
+ | IEntry[]
+ | InvariantEntry
+ | { entity: InAddress };
+
export interface ListingResult {
[key: string]: IEntry;
}
diff --git a/webui/src/components/AddModal.svelte b/webui/src/components/AddModal.svelte
index bb942b1..4f2b534 100644
--- a/webui/src/components/AddModal.svelte
+++ b/webui/src/components/AddModal.svelte
@@ -10,7 +10,7 @@
diff --git a/webui/src/components/Inspect.svelte b/webui/src/components/Inspect.svelte
index 49124c5..0612821 100644
--- a/webui/src/components/Inspect.svelte
+++ b/webui/src/components/Inspect.svelte
@@ -16,6 +16,7 @@
import type { BrowseContext } from "../util/browse";
import { useParams } from "svelte-navigator";
import { GROUP_TYPE_ADDR } from "upend/constants";
+ import { deleteEntry, putEntityAttribute, putEntry } from "../lib/api";
const params = useParams();
export let address: string;
@@ -119,25 +120,17 @@
const change = ev.detail;
switch (change.type) {
case "create":
- await fetch(`api/obj`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- entity: address,
- attribute: change.attribute,
- value: change.value,
- }),
+ await putEntry({
+ entity: address,
+ attribute: change.attribute,
+ value: change.value,
});
break;
case "delete":
- await fetch(`api/obj/${change.address}`, { method: "DELETE" });
+ await deleteEntry(change.address);
break;
case "update":
- await fetch(`api/obj/${address}/${change.attribute}`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(change.value),
- });
+ await putEntityAttribute(address, change.attribute, change.value);
break;
default:
console.error("Unimplemented AttributeChange", change);
@@ -151,34 +144,30 @@
if (!groupToAdd) {
return;
}
- await fetch(`api/obj`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify([
- {
- entity: String(groupToAdd.c),
- attribute: "HAS",
- value: {
- t: "Address",
- c: address,
- },
+ await putEntry([
+ {
+ entity: String(groupToAdd.c),
+ attribute: "HAS",
+ value: {
+ t: "Address",
+ c: address,
},
- {
- entity: String(groupToAdd.c),
- attribute: "IS",
- value: {
- t: "Address",
- c: GROUP_TYPE_ADDR,
- },
+ },
+ {
+ entity: String(groupToAdd.c),
+ attribute: "IS",
+ value: {
+ t: "Address",
+ c: GROUP_TYPE_ADDR,
},
- ]),
- });
+ },
+ ]);
revalidate();
groupToAdd = undefined;
}
async function removeGroup(address: string) {
- await fetch(`api/obj/${address}`, { method: "DELETE" });
+ await deleteEntry(address);
revalidate();
}
diff --git a/webui/src/components/display/UpObject.svelte b/webui/src/components/display/UpObject.svelte
index 309f6fc..12aab83 100644
--- a/webui/src/components/display/UpObject.svelte
+++ b/webui/src/components/display/UpObject.svelte
@@ -5,6 +5,7 @@
import Ellipsis from "../utils/Ellipsis.svelte";
import UpLink from "./UpLink.svelte";
import { useEntity } from "../../lib/entity";
+ import { nativeOpen as nativeOpenApi } from "../../lib/api";
import { readable } from "svelte/store";
import { notify, UpNotification } from "../../notifications";
import IconButton from "../utils/IconButton.svelte";
@@ -49,7 +50,7 @@
} in a default native application...`
)
);
- fetch(`api/raw/${address}?native=1`)
+ nativeOpenApi(address)
.then(async (response) => {
if (!response.ok) {
throw new Error(`${response.statusText} - ${await response.text()}`);
diff --git a/webui/src/components/display/blobs/ImageViewer.svelte b/webui/src/components/display/blobs/ImageViewer.svelte
index 8d04967..61343eb 100644
--- a/webui/src/components/display/blobs/ImageViewer.svelte
+++ b/webui/src/components/display/blobs/ImageViewer.svelte
@@ -1,6 +1,8 @@
diff --git a/webui/src/components/display/blobs/TextViewer.svelte b/webui/src/components/display/blobs/TextViewer.svelte
index e2b6a98..1d638f5 100644
--- a/webui/src/components/display/blobs/TextViewer.svelte
+++ b/webui/src/components/display/blobs/TextViewer.svelte
@@ -1,15 +1,13 @@
diff --git a/webui/src/components/layout/Jobs.svelte b/webui/src/components/layout/Jobs.svelte
index 9f94d08..e409353 100644
--- a/webui/src/components/layout/Jobs.svelte
+++ b/webui/src/components/layout/Jobs.svelte
@@ -11,18 +11,18 @@
import type { IJob } from "upend/types";
import { fade } from "svelte/transition";
import ProgessBar from "../utils/ProgessBar.svelte";
+ import { fetchJobs } from "../../lib/api";
interface JobWithId extends IJob {
id: string;
}
- let jobs: JobWithId[] = [];
+ let jobs: IJob[] = [];
let activeJobs: JobWithId[] = [];
let timeout: number;
async function updateJobs() {
clearTimeout(timeout);
- let request = await fetch("api/jobs");
- jobs = await request.json();
+ jobs = await fetchJobs();
activeJobs = Object.entries(jobs)
.filter(([_, job]) => job.state == "InProgress")
diff --git a/webui/src/components/utils/Selector.svelte b/webui/src/components/utils/Selector.svelte
index b80178d..e33cf7b 100644
--- a/webui/src/components/utils/Selector.svelte
+++ b/webui/src/components/utils/Selector.svelte
@@ -2,6 +2,7 @@
import { debounce } from "lodash";
import { createEventDispatcher } from "svelte";
import type { IValue, VALUE_TYPE } from "upend/types";
+ import { fetchAllAttributes } from "../../lib/api";
import {
baseSearchOnce,
createLabelled,
@@ -53,8 +54,7 @@
switch (type) {
case "attribute": {
- const req = await fetch("api/all/attributes");
- const allAttributes: string[] = await req.json();
+ const allAttributes = await fetchAllAttributes();
options = allAttributes
.filter((attr) => attr.toLowerCase().includes(query.toLowerCase()))
.map((attribute) => {
diff --git a/webui/src/lib/api.ts b/webui/src/lib/api.ts
new file mode 100644
index 0000000..bd7517a
--- /dev/null
+++ b/webui/src/lib/api.ts
@@ -0,0 +1,127 @@
+import LRU from "lru-cache";
+import { UpListing, UpObject } from "upend";
+import type {
+ Address,
+ IJob,
+ InEntry,
+ IValue,
+ ListingResult,
+ PutResult,
+ VaultInfo
+} from "upend/types";
+import type { EntityListing } from "./entity";
+
+export async function fetchEntity(address: string): Promise {
+ const entityFetch = await fetch(`api/obj/${address}`);
+ const entityResult = (await entityFetch.json()) as EntityListing;
+ const entityListing = new UpListing(entityResult.entries);
+ return entityListing.getObject(address);
+}
+
+export async function fetchEntry(address: string) {
+ const response = await fetch(`api/raw/${address}`);
+ const data = await response.json();
+ const listing = new UpListing({ address: data });
+ return listing.entries[0];
+}
+
+const queryOnceLRU = new LRU(128);
+const inFlightRequests: { [key: string]: Promise } = {};
+
+export async function queryOnce(query: string): Promise {
+ const cacheResult = queryOnceLRU.get(query);
+ if (!cacheResult) {
+ if (!inFlightRequests[query]) {
+ console.debug(`Querying: ${query}`);
+ inFlightRequests[query] = new Promise((resolve, reject) => {
+ fetch("api/query", { method: "POST", body: query, keepalive: true })
+ .then(async (response) => {
+ resolve(new UpListing(await response.json()));
+ })
+ .catch((err) => reject(err));
+ });
+ } else {
+ console.debug(`Chaining request for ${query}...`);
+ }
+ return await inFlightRequests[query];
+ } else {
+ console.debug(`Returning cached: ${query}`);
+ return cacheResult;
+ }
+}
+
+export async function putEntry(entry: InEntry): Promise {
+ const response = await fetch(`api/obj`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(entry),
+ });
+
+ return await response.json();
+}
+
+export async function putEntityAttribute(
+ entity: Address,
+ attribute: string,
+ value: IValue
+): Promise {
+ const response = await fetch(`api/obj/${entity}/${attribute}`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(value),
+ });
+
+ return await response.json();
+}
+
+export async function uploadFile(file: File): Promise {
+ const formData = new FormData();
+ formData.append("file", file);
+
+ const response = await fetch("api/obj", {
+ method: "PUT",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ throw Error(await response.text());
+ }
+
+ return await response.json();
+}
+
+export async function deleteEntry(address: Address): Promise {
+ await fetch(`api/obj/${address}`, { method: "DELETE" });
+}
+
+export async function getRaw(address: Address, preview = false) {
+ return await fetch(`api/${preview ? "thumb" : "raw"}/${address}`);
+}
+
+export async function refreshVault() {
+ return await fetch("api/refresh", { method: "POST" });
+}
+
+export async function nativeOpen(address: Address) {
+ return fetch(`api/raw/${address}?native=1`);
+}
+
+export async function fetchRoots(): Promise {
+ const response = await fetch("api/hier_roots");
+ return await response.json();
+}
+
+export async function fetchJobs(): Promise {
+ const response = await fetch("api/jobs");
+ return await response.json();
+}
+
+export async function fetchAllAttributes(): Promise {
+ const response = await fetch("api/all/attributes");
+ return await response.json();
+}
+
+export async function fetchInfo(): Promise {
+ const response = await fetch("api/info");
+ return await response.json();
+}
diff --git a/webui/src/lib/entity.ts b/webui/src/lib/entity.ts
index 605f6a8..a52f472 100644
--- a/webui/src/lib/entity.ts
+++ b/webui/src/lib/entity.ts
@@ -1,5 +1,4 @@
// import { useSWR } from "sswr";
-import LRU from "lru-cache";
import { derived, Readable } from "svelte/store";
import { UpListing, UpObject } from "upend";
import type { ListingResult } from "upend/types";
@@ -19,9 +18,6 @@ export interface EntityListing {
entries: ListingResult;
}
-const queryOnceLRU = new LRU(128);
-const inFlightRequests: { [key: string]: Promise } = {};
-
export function useEntity(address: string) {
const { data, error, revalidate } = useSWR(
`api/obj/${address}`
@@ -51,20 +47,6 @@ export function useEntity(address: string) {
};
}
-export async function fetchEntity(address: string): Promise {
- const entityFetch = await fetch(`api/obj/${address}`);
- const entityResult = (await entityFetch.json()) as EntityListing;
- const entityListing = new UpListing(entityResult.entries);
- return entityListing.getObject(address);
-}
-
-export async function fetchEntry(address: string) {
- const response = await fetch(`api/raw/${address}`);
- const data = await response.json();
- const listing = new UpListing({ address: data });
- return listing.entries[0];
-}
-
export function query(query: string) {
console.debug(`Querying: ${query}`);
const { data, error, revalidate } = useSWR(
@@ -82,25 +64,3 @@ export function query(query: string) {
revalidate,
};
}
-
-export async function queryOnce(query: string): Promise {
- const cacheResult = queryOnceLRU.get(query);
- if (!cacheResult) {
- if (!inFlightRequests[query]) {
- console.debug(`Querying: ${query}`);
- inFlightRequests[query] = new Promise((resolve, reject) => {
- fetch("api/query", { method: "POST", body: query, keepalive: true })
- .then(async (response) => {
- resolve(new UpListing(await response.json()));
- })
- .catch((err) => reject(err));
- });
- } else {
- console.debug(`Chaining request for ${query}...`);
- }
- return await inFlightRequests[query];
- } else {
- console.debug(`Returning cached: ${query}`);
- return cacheResult;
- }
-}
diff --git a/webui/src/util/info.ts b/webui/src/util/info.ts
index 5c3c477..1582733 100644
--- a/webui/src/util/info.ts
+++ b/webui/src/util/info.ts
@@ -1,8 +1,9 @@
import { readable, Readable } from "svelte/store";
import type { VaultInfo } from "upend/types";
+import { fetchInfo } from "../lib/api";
export const vaultInfo: Readable = readable(undefined, (set) => {
- fetch("api/info").then(async (response) => {
- set(await response.json());
+ fetchInfo().then(async (info) => {
+ set(info);
});
});
diff --git a/webui/src/util/search.ts b/webui/src/util/search.ts
index 56ade8e..5f9e9b7 100644
--- a/webui/src/util/search.ts
+++ b/webui/src/util/search.ts
@@ -1,6 +1,7 @@
import type { UpEntry } from "upend";
-import type { PutResult } from "upend/types";
-import { query as queryFn, queryOnce } from "../lib/entity";
+import type { InEntry } from "upend/types";
+import { putEntry, queryOnce } from "../lib/api";
+import { query as queryFn } from "../lib/entity";
export function baseSearch(query: string) {
return queryFn(
@@ -25,7 +26,7 @@ export async function getObjects(
}
export async function createLabelled(label: string) {
- let body: unknown;
+ let body: InEntry;
if (label.match("^[\\w]+://[\\w]")) {
body = {
entity: {
@@ -43,16 +44,10 @@ export async function createLabelled(label: string) {
};
}
- const response = await fetch(`api/obj`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body),
- });
-
- if (!response.ok) {
- throw new Error(`Failed to create object: ${await response.text()}`);
+ try {
+ const [_, entry] = await putEntry(body);
+ return entry;
+ } catch (error) {
+ throw new Error(`Failed to create object: ${error}`);
}
-
- const [_, entry] = (await response.json()) as PutResult;
- return entry;
}
diff --git a/webui/src/views/Home.svelte b/webui/src/views/Home.svelte
index 83ca0c0..d116d47 100644
--- a/webui/src/views/Home.svelte
+++ b/webui/src/views/Home.svelte
@@ -1,19 +1,16 @@