diff --git a/changelog/items/key-updates/unpin-skylink.md b/changelog/items/key-updates/unpin-skylink.md
new file mode 100644
index 00000000..ab403ff9
--- /dev/null
+++ b/changelog/items/key-updates/unpin-skylink.md
@@ -0,0 +1 @@
+- added unpinning skylinks from account dashboard
diff --git a/docker/kratos/oathkeeper/access-rules.yml b/docker/kratos/oathkeeper/access-rules.yml
index a45f75b2..75bdee65 100644
--- a/docker/kratos/oathkeeper/access-rules.yml
+++ b/docker/kratos/oathkeeper/access-rules.yml
@@ -97,7 +97,7 @@
preserve_host: true
url: "http://accounts:3000"
match:
- url: "http://oathkeeper<{,:4455}>/<{login,logout,user,user/uploads,user/downloads,user/stats}>"
+ url: "http://oathkeeper<{,:4455}>/<{login,logout,user,user/uploads,user/uploads/*,user/downloads,user/stats}>"
methods:
- GET
- POST
diff --git a/packages/dashboard/Dockerfile b/packages/dashboard/Dockerfile
index 888d7c51..8d93a036 100644
--- a/packages/dashboard/Dockerfile
+++ b/packages/dashboard/Dockerfile
@@ -10,6 +10,6 @@ RUN yarn --frozen-lockfile
COPY public ./public
COPY src ./src
COPY styles ./styles
-COPY next.config.js postcss.config.js tailwind.config.js ./
+COPY postcss.config.js tailwind.config.js ./
CMD ["sh", "-c", "env | grep -E 'NEXT_PUBLIC|KRATOS|STRIPE' > .env.local && yarn build && yarn start"]
diff --git a/packages/dashboard/next.config.js b/packages/dashboard/next.config.js
deleted file mode 100644
index d62def0f..00000000
--- a/packages/dashboard/next.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- webpack5: true,
-};
diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json
index 44ac4351..ab74f41f 100644
--- a/packages/dashboard/package.json
+++ b/packages/dashboard/package.json
@@ -23,11 +23,13 @@
"http-status-codes": "2.1.4",
"ky": "0.25.1",
"next": "11.1.2",
+ "normalize.css": "8.0.1",
"postcss": "8.3.8",
"prettier": "2.4.1",
"pretty-bytes": "5.6.0",
"react": "17.0.2",
"react-dom": "17.0.2",
+ "react-toastify": "8.0.2",
"skynet-js": "3.0.2",
"stripe": "8.176.0",
"superagent": "6.1.0",
diff --git a/packages/dashboard/src/components/Table.js b/packages/dashboard/src/components/Table.js
index e3ae6d5e..1c01b1fa 100644
--- a/packages/dashboard/src/components/Table.js
+++ b/packages/dashboard/src/components/Table.js
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import classnames from "classnames";
-function Button({ children, disabled, className, ...props }) {
+function Button({ children, disabled, ...props }) {
return (
+ );
+}
+
+export default function Table({ items, count, headers, mutate, actions, offset, setOffset, pageSize = 10 }) {
useEffect(() => {
if (offset < 0) setOffset(0);
else if (offset >= count && count > 0) setOffset(Math.floor(count / pageSize - 1) * pageSize);
@@ -45,8 +63,8 @@ export default function Table({ items, count, headers, actions, offset, setOffse
{name}
))}
- {actions.map(({ key, name }) => (
-
+ {actions.map(({ name }, index) => (
+ |
{name}
|
))}
@@ -56,7 +74,7 @@ export default function Table({ items, count, headers, actions, offset, setOffse
{items && items.length ? (
items.map((row, index) => (
- {headers.map(({ key, formatter, href, nowrap = true }) => (
+ {headers.map(({ key, formatter, href, nowrap }) => (
—>}
|
))}
- {actions.map(({ key, name, action }) => (
-
-
- {name}
-
+ {actions.map(({ name, action }, index) => (
+ |
+ action(row, mutate)}>{name}
|
))}
@@ -107,15 +123,11 @@ export default function Table({ items, count, headers, actions, offset, setOffse
{count} results
-
+
-
diff --git a/packages/dashboard/src/pages/_app.js b/packages/dashboard/src/pages/_app.js
index 5be206d2..811d3a0d 100644
--- a/packages/dashboard/src/pages/_app.js
+++ b/packages/dashboard/src/pages/_app.js
@@ -1,6 +1,9 @@
import { Elements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";
+import { ToastContainer } from "react-toastify";
import Head from "next/head";
+import "normalize.css";
+import "react-toastify/dist/ReactToastify.css";
import "tailwindcss/tailwind.css";
import "@fontsource/metropolis/all.css";
@@ -14,6 +17,7 @@ function MyApp({ Component, pageProps }) {
Skynet
+
"Toastify__toast-body text-sm font-medium text-palette-500"} />
);
}
diff --git a/packages/dashboard/src/pages/api/accounts/login.js b/packages/dashboard/src/pages/api/accounts/login.js
index 4890d52b..89bad4dc 100644
--- a/packages/dashboard/src/pages/api/accounts/login.js
+++ b/packages/dashboard/src/pages/api/accounts/login.js
@@ -10,6 +10,8 @@ export default async (req, res) => {
res.setHeader("Set-Cookie", header["set-cookie"]);
res.redirect(req.query.return_to ?? "/");
} catch (error) {
+ console.log(`Cookie is present but authentication failed: ${error.message}`);
+
// credentials were correct but accounts service failed
res.redirect("/.ory/kratos/public/self-service/browser/flows/logout");
}
diff --git a/packages/dashboard/src/pages/auth/login.js b/packages/dashboard/src/pages/auth/login.js
index 481f8d44..f9e8ab45 100644
--- a/packages/dashboard/src/pages/auth/login.js
+++ b/packages/dashboard/src/pages/auth/login.js
@@ -32,6 +32,8 @@ export async function getServerSideProps(context) {
throw new Error(`Failed to retrieve flow ${flow} with code ${status}`);
} catch (error) {
+ console.log(`Unexpected error retrieving login flow: ${error.message}`);
+
return {
redirect: {
permanent: false,
diff --git a/packages/dashboard/src/pages/auth/registration.js b/packages/dashboard/src/pages/auth/registration.js
index 33230c54..23414f72 100644
--- a/packages/dashboard/src/pages/auth/registration.js
+++ b/packages/dashboard/src/pages/auth/registration.js
@@ -35,6 +35,8 @@ export async function getServerSideProps(context) {
throw new Error(`Failed to retrieve flow ${flow} with code ${status}`);
} catch (error) {
+ console.log(`Unexpected error retrieving registration flow: ${error.message}`);
+
return {
redirect: {
permanent: false,
diff --git a/packages/dashboard/src/pages/downloads.js b/packages/dashboard/src/pages/downloads.js
index 942efd11..d18257f7 100644
--- a/packages/dashboard/src/pages/downloads.js
+++ b/packages/dashboard/src/pages/downloads.js
@@ -12,8 +12,25 @@ const apiPrefix = process.env.NODE_ENV === "development" ? "/api/stubs" : "";
const getSkylinkLink = ({ skylink }) => skynetClient.getSkylinkUrl(skylink);
const getRelativeDate = ({ downloadedOn }) => dayjs(downloadedOn).format("YYYY-MM-DD HH:mm:ss");
const headers = [
- { key: "name", name: "Name", nowrap: false, href: getSkylinkLink },
- { key: "skylink", name: "Skylink" },
+ {
+ key: "name",
+ name: "File",
+ formatter: ({ name, skylink }) => (
+ <>
+
+
+ {name}
+
+
+ {skylink}
+ >
+ ),
+ },
{ key: "size", name: "Size", formatter: ({ size }) => prettyBytes(size) },
{ key: "downloadedOn", name: "Accessed on", formatter: getRelativeDate },
];
diff --git a/packages/dashboard/src/pages/uploads.js b/packages/dashboard/src/pages/uploads.js
index d78288f7..ff452f49 100644
--- a/packages/dashboard/src/pages/uploads.js
+++ b/packages/dashboard/src/pages/uploads.js
@@ -1,6 +1,8 @@
import dayjs from "dayjs";
import prettyBytes from "pretty-bytes";
import { useState } from "react";
+import ky from "ky/umd";
+import { toast } from "react-toastify";
import Layout from "../components/Layout";
import Table from "../components/Table";
import authServerSideProps from "../services/authServerSideProps";
@@ -12,12 +14,42 @@ const apiPrefix = process.env.NODE_ENV === "development" ? "/api/stubs" : "";
const getSkylinkLink = ({ skylink }) => skynetClient.getSkylinkUrl(skylink);
const getRelativeDate = ({ uploadedOn }) => dayjs(uploadedOn).format("YYYY-MM-DD HH:mm:ss");
const headers = [
- { key: "name", name: "Name", nowrap: false, href: getSkylinkLink },
- { key: "skylink", name: "Skylink" },
+ {
+ key: "name",
+ name: "File",
+ formatter: ({ name, skylink }) => (
+ <>
+
+
+ {name}
+
+
+ {skylink}
+ >
+ ),
+ },
{ key: "size", name: "Size", formatter: ({ size }) => prettyBytes(size) },
{ key: "uploadedOn", name: "Uploaded on", formatter: getRelativeDate },
];
-const actions = [];
+const actions = [
+ {
+ name: "Unpin Skylink",
+ action: async ({ skylink }, mutate) => {
+ await toast.promise(ky.delete(`/user/uploads/${skylink}`), {
+ pending: "Unpinning Skylink",
+ success: "Skylink unpinned",
+ error: (error) => error.message,
+ });
+
+ mutate();
+ },
+ },
+];
export const getServerSideProps = authServerSideProps(async (context, api) => {
const initialData = await api.get("user/uploads?pageSize=10&offset=0").json();
@@ -27,7 +59,7 @@ export const getServerSideProps = authServerSideProps(async (context, api) => {
export default function Uploads({ initialData }) {
const [offset, setOffset] = useState(0);
- const { data } = useAccountsApi(`${apiPrefix}/user/uploads?pageSize=10&offset=${offset}`, {
+ const { data, mutate } = useAccountsApi(`${apiPrefix}/user/uploads?pageSize=10&offset=${offset}`, {
initialData: offset === 0 ? initialData : undefined,
revalidateOnMount: true,
});
@@ -38,7 +70,7 @@ export default function Uploads({ initialData }) {
return (
-
+
);
}
diff --git a/packages/dashboard/src/services/authServerSideProps.js b/packages/dashboard/src/services/authServerSideProps.js
index bc7bf43c..83c848d3 100644
--- a/packages/dashboard/src/services/authServerSideProps.js
+++ b/packages/dashboard/src/services/authServerSideProps.js
@@ -4,7 +4,17 @@ const isProduction = process.env.NODE_ENV === "production";
export default function authServerSideProps(getServerSideProps) {
return function authenticate(context) {
- if (isProduction && (!("ory_kratos_session" in context.req.cookies) || !("skynet-jwt" in context.req.cookies))) {
+ const authCookies = ["ory_kratos_session", "skynet-jwt"];
+
+ if (isProduction && !authCookies.every((cookie) => context.req.cookies[cookie])) {
+ // it is higly unusual that some of the cookies would be set but other would not
+ if (authCookies.some((cookie) => context.req.cookies[cookie])) {
+ console.log(
+ "Unexpected auth cookies state!",
+ authCookies.map((cookie) => `[${cookie}] is ${context.req.cookies[cookie] ? "set" : "not set"}`)
+ );
+ }
+
return {
redirect: {
permanent: false,
diff --git a/packages/dashboard/yarn.lock b/packages/dashboard/yarn.lock
index d22c46cc..bbf6091b 100644
--- a/packages/dashboard/yarn.lock
+++ b/packages/dashboard/yarn.lock
@@ -640,6 +640,11 @@ clipboardy@2.3.0:
execa "^1.0.0"
is-wsl "^2.1.1"
+clsx@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
+ integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
+
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -2090,6 +2095,11 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
+normalize.css@8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3"
+ integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
+
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -2523,6 +2533,13 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
+react-toastify@8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-8.0.2.tgz#11f73b3a847fcffeb47b1823e8974e9895f98fae"
+ integrity sha512-0Nud2d0VD4LIevgkB4L8NYoQ5plTpfqgj2CRVxs58SGA/TTO+2Ojz4C1bLUdGUWsw0zuWqd4GJqxNuMIv0cXMw==
+ dependencies:
+ clsx "^1.1.1"
+
react@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"