feat(files): API tokens in Settings, the Hub folder in Files, a notice before a token runs out
Settings gains an API tokens tab: a row per token (name and prefix; each folder with its rights; last use and expiry, "expires soon" marked), revoked from its menu behind a confirm. A new token is the Hub exchange in one click, or folders and rights picked per folder (no Write where an upload cannot land); expiry 30/90 days, 1 year or never (warned). The token shows once in the same dialog, with Copy and a ready curl line. The Hub folder closes the Sources in the Files rail, takes uploads and moved-in files. A token about to run out warns its user once, a week before (the Files GC's sixth pass); the notice opens the tokens tab.
This commit is contained in:
@@ -155,6 +155,7 @@ const folder = (id: number, extra: Partial<FileFolder>): FileFolder => ({
|
||||
});
|
||||
const YT = folder(1, { source_key: "youtube" });
|
||||
const OWN = folder(2, { kind: "own", name: "Cimboráknak", retention_days: null });
|
||||
const HUB = folder(4, { kind: "hub", retention_days: null, count: 0 });
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
@@ -377,6 +378,18 @@ describe("Files · my file's row", () => {
|
||||
await waitFor(() => expect(update).toHaveBeenCalledWith(2, { folder_id: 1 }));
|
||||
});
|
||||
|
||||
it("moves into the Hub folder too, the one its hub picks files up from", async () => {
|
||||
mocks.files = [file(2, { folder_id: 1 })];
|
||||
mocks.folders = [YT, HUB, OWN];
|
||||
const update = vi.spyOn(api, "updateFile").mockResolvedValue({} as never);
|
||||
mount();
|
||||
await openMenu(() => fireEvent.contextMenu(rowOf("Clip 2")));
|
||||
fireEvent.keyDown(menuItem("files.actions.move"), { key: "ArrowRight" });
|
||||
await waitFor(() => expect(menuRows()).toContain("files.folder.hub"));
|
||||
fireEvent.click(menuItem("files.folder.hub"));
|
||||
await waitFor(() => expect(update).toHaveBeenCalledWith(2, { folder_id: 4 }));
|
||||
});
|
||||
|
||||
it("offers no move home while it is home, and says so when there is nowhere to go", async () => {
|
||||
mocks.files = [file(2, { folder_id: 1 })];
|
||||
mocks.folders = [YT];
|
||||
@@ -1043,6 +1056,14 @@ describe("Files · dropping a file on the page", () => {
|
||||
expect(await screen.findByText("recept.pdf")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("lands in the Hub folder when dropped there", () => {
|
||||
mocks.files = [];
|
||||
mocks.folders = [YT, HUB];
|
||||
mount({ kind: "folder", id: 4 });
|
||||
drag("dragenter");
|
||||
expect(screen.getByText("files.drop.here:files.folder.hub")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("is no target where nothing can be uploaded — but the browser never gets the file", () => {
|
||||
mocks.files = [];
|
||||
mocks.folders = [YT];
|
||||
|
||||
Reference in New Issue
Block a user