Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d1656688e | ||
|
|
e53a25750d | ||
|
|
ffcd857d9a | ||
|
|
48a6ceb3b4 | ||
|
|
84833e5f91 | ||
|
|
1864951bdb | ||
|
|
427e99c7b1 | ||
|
|
ccc783ab6a | ||
|
|
853f217bf1 | ||
|
|
af047c0bc1 | ||
|
|
8ddee32664 | ||
|
|
e7128dcfef | ||
|
|
56d8517073 | ||
|
|
df4341163a | ||
|
|
81547ba1e0 | ||
|
|
a790ac73fd | ||
|
|
3fd689a760 | ||
|
|
6a8ac1aa65 |
3
.github/workflows/backend-system-tests.yml
vendored
3
.github/workflows/backend-system-tests.yml
vendored
@@ -10,8 +10,9 @@ on:
|
||||
|
||||
jobs:
|
||||
system-tests:
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
container: node:18
|
||||
container: node:22
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Dependencies
|
||||
|
||||
1
.github/workflows/build-docker-image.yml
vendored
1
.github/workflows/build-docker-image.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,3 +1,19 @@
|
||||
## [1.12.0](https://github.com/stonith404/pingvin-share/compare/v1.11.1...v1.12.0) (2025-05-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **s3:** stream s3 content over a zip file ([#822](https://github.com/stonith404/pingvin-share/issues/822)) ([ccc783a](https://github.com/stonith404/pingvin-share/commit/ccc783ab6a00841a7041c454e77afb472d76999e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* disable HTML rendering in Markdown preview ([427e99c](https://github.com/stonith404/pingvin-share/commit/427e99c7b1d00ff6ed7b5fd879d8cf0f0d49281a))
|
||||
* health check for containers with reverse proxy disabled ([#816](https://github.com/stonith404/pingvin-share/issues/816)) ([a790ac7](https://github.com/stonith404/pingvin-share/commit/a790ac73fd42d266a957e09a05b1894199605f6a)), closes [#809](https://github.com/stonith404/pingvin-share/issues/809)
|
||||
* OIDC configuration from YAML configuration file doesn't get loaded ([48a6ceb](https://github.com/stonith404/pingvin-share/commit/48a6ceb3b4b4dfc0407dc6f9ee2e07cca1829cef))
|
||||
* spelling mistake and add clarity in email template ([#824](https://github.com/stonith404/pingvin-share/issues/824)) ([af047c0](https://github.com/stonith404/pingvin-share/commit/af047c0bc152a955b3ab135f5a9ea3d62b32fb0f))
|
||||
* use sandbox CSP for file previews ([1864951](https://github.com/stonith404/pingvin-share/commit/1864951bdbf573431e795109224a45545b86b54d))
|
||||
|
||||
## [1.11.1](https://github.com/stonith404/pingvin-share/compare/v1.11.0...v1.11.1) (2025-04-06)
|
||||
|
||||
## [1.11.0](https://github.com/stonith404/pingvin-share/compare/v1.10.4...v1.11.0) (2025-04-05)
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@@ -1,25 +1,25 @@
|
||||
# Stage 1: Frontend dependencies
|
||||
FROM node:20-alpine AS frontend-dependencies
|
||||
FROM node:22-alpine AS frontend-dependencies
|
||||
WORKDIR /opt/app
|
||||
COPY frontend/package.json frontend/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Stage 2: Build frontend
|
||||
FROM node:20-alpine AS frontend-builder
|
||||
FROM node:22-alpine AS frontend-builder
|
||||
WORKDIR /opt/app
|
||||
COPY ./frontend .
|
||||
COPY --from=frontend-dependencies /opt/app/node_modules ./node_modules
|
||||
RUN npm run build
|
||||
|
||||
# Stage 3: Backend dependencies
|
||||
FROM node:20-alpine AS backend-dependencies
|
||||
FROM node:22-alpine AS backend-dependencies
|
||||
RUN apk add --no-cache python3
|
||||
WORKDIR /opt/app
|
||||
COPY backend/package.json backend/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Stage 4: Build backend
|
||||
FROM node:20-alpine AS backend-builder
|
||||
FROM node:22-alpine AS backend-builder
|
||||
RUN apk add openssl
|
||||
|
||||
WORKDIR /opt/app
|
||||
@@ -29,7 +29,7 @@ RUN npx prisma generate
|
||||
RUN npm run build && npm prune --production
|
||||
|
||||
# Stage 5: Final image
|
||||
FROM node:20-alpine AS runner
|
||||
FROM node:22-alpine AS runner
|
||||
ENV NODE_ENV=docker
|
||||
|
||||
# Delete default node user
|
||||
@@ -59,7 +59,7 @@ COPY ./scripts/docker ./scripts/docker
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/health || exit 1
|
||||
HEALTHCHECK --interval=10s --timeout=3s CMD /bin/sh -c '(if [[ "$CADDY_DISABLED" = "true" ]]; then curl -fs http://localhost:${BACKEND_PORT:-8080}/api/health; else curl -fs http://localhost:3000/api/health; fi) || exit 1'
|
||||
|
||||
ENTRYPOINT ["sh", "./scripts/docker/create-user.sh"]
|
||||
CMD ["sh", "./scripts/docker/entrypoint.sh"]
|
||||
4189
backend/package-lock.json
generated
4189
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "1.11.1",
|
||||
"version": "1.12.0",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "cross-env NODE_ENV=development nest start --watch",
|
||||
@@ -13,34 +13,34 @@
|
||||
"seed": "ts-node prisma/seed/config.seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.758.0",
|
||||
"@nestjs/cache-manager": "^2.2.2",
|
||||
"@nestjs/common": "^10.4.3",
|
||||
"@nestjs/config": "^3.2.3",
|
||||
"@nestjs/core": "^10.4.3",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.4.3",
|
||||
"@nestjs/schedule": "^4.1.1",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@nestjs/throttler": "^6.2.1",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@aws-sdk/client-s3": "^3.787.0",
|
||||
"@nestjs/cache-manager": "^3.0.1",
|
||||
"@nestjs/common": "^11.0.17",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.17",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.17",
|
||||
"@nestjs/schedule": "^5.0.1",
|
||||
"@nestjs/swagger": "^11.1.3",
|
||||
"@nestjs/throttler": "^6.4.0",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@types/jmespath": "^0.15.2",
|
||||
"archiver": "^7.0.1",
|
||||
"argon2": "^0.41.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"cache-manager": "^5.7.6",
|
||||
"clamscan": "^2.3.1",
|
||||
"body-parser": "^2.2.0",
|
||||
"cache-manager": "^6.4.2",
|
||||
"clamscan": "^2.4.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"content-disposition": "^0.5.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"jmespath": "^0.16.0",
|
||||
"ldapts": "^7.2.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"ldapts": "^7.4.0",
|
||||
"mime-types": "^3.0.1",
|
||||
"moment": "^2.30.1",
|
||||
"nanoid": "^3.3.7",
|
||||
"nodemailer": "^6.9.15",
|
||||
"nodemailer": "^6.10.1",
|
||||
"otplib": "^12.0.1",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
@@ -48,42 +48,43 @@
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"sharp": "^0.33.5",
|
||||
"rxjs": "^7.8.2",
|
||||
"sharp": "^0.34.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"yaml": "^2.7.0"
|
||||
"uuid": "^11.1.0",
|
||||
"yaml": "^2.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.5",
|
||||
"@nestjs/schematics": "^10.1.4",
|
||||
"@nestjs/testing": "^10.4.3",
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/clamscan": "^2.0.8",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@nestjs/cli": "^11.0.6",
|
||||
"@nestjs/schematics": "^11.0.5",
|
||||
"@nestjs/testing": "^11.0.17",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/clamscan": "^2.4.1",
|
||||
"@types/cookie-parser": "^1.4.8",
|
||||
"@types/cron": "^2.4.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.5.5",
|
||||
"@types/nodemailer": "^6.4.16",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/qrcode-svg": "^1.1.5",
|
||||
"@types/sharp": "^0.32.0",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.6.0",
|
||||
"@typescript-eslint/parser": "^8.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^9.10.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"newman": "^6.2.1",
|
||||
"prettier": "^3.3.3",
|
||||
"prisma": "^6.4.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prisma": "^6.6.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-loader": "^9.5.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "^5.6.2",
|
||||
"wait-on": "^8.0.1"
|
||||
"typescript": "^5.8.3",
|
||||
"wait-on": "^8.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export const configVariables = {
|
||||
shareRecipientsMessage: {
|
||||
type: "text",
|
||||
defaultValue:
|
||||
"Hey!\n\n{creator} ({creatorEmail}) shared some files with you, view or download the files with this link: {shareUrl}\n\nThe share will expire {expires}.\n\nNote: {desc}\n\nShared securely with Pingvin Share 🐧",
|
||||
"Hey!\n\n{creator} ({creatorEmail}) shared some files with you. You can view or download the files with this link: {shareUrl}\n\nThe share will expire {expires}.\n\nNote: {desc}\n\nShared securely with Pingvin Share 🐧",
|
||||
},
|
||||
reverseShareSubject: {
|
||||
type: "string",
|
||||
@@ -107,7 +107,7 @@ export const configVariables = {
|
||||
resetPasswordMessage: {
|
||||
type: "text",
|
||||
defaultValue:
|
||||
"Hey!\n\nYou requested a password reset. Click this link to reset your password: {url}\nThe link expires in a hour.\n\nPingvin Share 🐧",
|
||||
"Hey!\n\nYou requested a password reset. Click this link to reset your password: {url}\nThe link expires in an hour.\n\nPingvin Share 🐧",
|
||||
},
|
||||
inviteSubject: {
|
||||
type: "string",
|
||||
|
||||
@@ -20,12 +20,12 @@ import { UserModule } from "./user/user.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
AuthModule,
|
||||
ShareModule,
|
||||
FileModule,
|
||||
EmailModule,
|
||||
PrismaModule,
|
||||
ConfigModule,
|
||||
JobsModule,
|
||||
UserModule,
|
||||
ThrottlerModule.forRoot([
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Global, Module } from "@nestjs/common";
|
||||
import { Config } from "@prisma/client";
|
||||
import { EmailModule } from "src/email/email.module";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { ConfigController } from "./config.controller";
|
||||
@@ -16,7 +17,15 @@ import { LogoService } from "./logo.service";
|
||||
},
|
||||
inject: [PrismaService],
|
||||
},
|
||||
ConfigService,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useFactory: async (prisma: PrismaService, configVariables: Config[]) => {
|
||||
const configService = new ConfigService(configVariables, prisma);
|
||||
await configService.initialize();
|
||||
return configService;
|
||||
},
|
||||
inject: [PrismaService, "CONFIG_VARIABLES"],
|
||||
},
|
||||
LogoService,
|
||||
],
|
||||
controllers: [ConfigController],
|
||||
|
||||
@@ -30,7 +30,8 @@ export class ConfigService extends EventEmitter {
|
||||
super();
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
// Initialize gets called by the ConfigModule
|
||||
async initialize() {
|
||||
await this.loadYamlConfig();
|
||||
|
||||
if (this.yamlConfig) {
|
||||
@@ -49,12 +50,13 @@ export class ConfigService extends EventEmitter {
|
||||
}
|
||||
try {
|
||||
this.yamlConfig = yamlParse(configFile);
|
||||
|
||||
if (this.yamlConfig) {
|
||||
for (const configVariable of this.configVariables) {
|
||||
const category = this.yamlConfig[configVariable.category];
|
||||
if (!category) continue;
|
||||
|
||||
configVariable.value = category[configVariable.name];
|
||||
this.emit("update", configVariable.name, configVariable.value);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -54,7 +54,7 @@ export class FileController {
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@Param("shareId") shareId: string,
|
||||
) {
|
||||
const zipStream = this.fileService.getZip(shareId);
|
||||
const zipStream = await this.fileService.getZip(shareId);
|
||||
|
||||
res.set({
|
||||
"Content-Type": "application/zip",
|
||||
@@ -78,7 +78,7 @@ export class FileController {
|
||||
"Content-Type":
|
||||
mime?.lookup?.(file.metaData.name) || "application/octet-stream",
|
||||
"Content-Length": file.metaData.size,
|
||||
"Content-Security-Policy": "script-src 'none'",
|
||||
"Content-Security-Policy": "sandbox",
|
||||
};
|
||||
|
||||
if (download === "true") {
|
||||
|
||||
@@ -59,9 +59,9 @@ export class FileService {
|
||||
return storageService.deleteAllFiles(shareId);
|
||||
}
|
||||
|
||||
getZip(shareId: string) {
|
||||
async getZip(shareId: string): Promise<Readable> {
|
||||
const storageService = this.getStorageService();
|
||||
return storageService.getZip(shareId) as Readable;
|
||||
return await storageService.getZip(shareId);
|
||||
}
|
||||
|
||||
private async streamToUint8Array(stream: Readable): Promise<Uint8Array> {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ConfigService } from "src/config/config.service";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { validate as isValidUUID } from "uuid";
|
||||
import { SHARE_DIRECTORY } from "../constants";
|
||||
import { Readable } from "stream";
|
||||
|
||||
@Injectable()
|
||||
export class LocalFileService {
|
||||
@@ -155,7 +156,19 @@ export class LocalFileService {
|
||||
});
|
||||
}
|
||||
|
||||
getZip(shareId: string) {
|
||||
return createReadStream(`${SHARE_DIRECTORY}/${shareId}/archive.zip`);
|
||||
async getZip(shareId: string): Promise<Readable> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const zipStream = createReadStream(
|
||||
`${SHARE_DIRECTORY}/${shareId}/archive.zip`,
|
||||
);
|
||||
|
||||
zipStream.on("error", (err) => {
|
||||
reject(new InternalServerErrorException(err));
|
||||
});
|
||||
|
||||
zipStream.on("open", () => {
|
||||
resolve(zipStream);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import * as mime from "mime-types";
|
||||
import { File } from "./file.service";
|
||||
import { Readable } from "stream";
|
||||
import { validate as isValidUUID } from "uuid";
|
||||
import * as archiver from "archiver";
|
||||
|
||||
@Injectable()
|
||||
export class S3FileService {
|
||||
@@ -275,7 +276,8 @@ export class S3FileService {
|
||||
}
|
||||
|
||||
getS3Instance(): S3Client {
|
||||
const checksumCalculation = this.config.get("s3.useChecksum") === true ? null : "WHEN_REQUIRED";
|
||||
const checksumCalculation =
|
||||
this.config.get("s3.useChecksum") === true ? null : "WHEN_REQUIRED";
|
||||
|
||||
return new S3Client({
|
||||
endpoint: this.config.get("s3.endpoint"),
|
||||
@@ -290,10 +292,95 @@ export class S3FileService {
|
||||
});
|
||||
}
|
||||
|
||||
getZip() {
|
||||
throw new BadRequestException(
|
||||
"ZIP download is not supported with S3 storage",
|
||||
);
|
||||
getZip(shareId: string) {
|
||||
return new Promise<Readable>(async (resolve, reject) => {
|
||||
const s3Instance = this.getS3Instance();
|
||||
const bucketName = this.config.get("s3.bucketName");
|
||||
const compressionLevel = this.config.get("share.zipCompressionLevel");
|
||||
|
||||
const prefix = `${this.getS3Path()}${shareId}/`;
|
||||
|
||||
try {
|
||||
const listResponse = await s3Instance.send(
|
||||
new ListObjectsV2Command({
|
||||
Bucket: bucketName,
|
||||
Prefix: prefix,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!listResponse.Contents || listResponse.Contents.length === 0) {
|
||||
throw new NotFoundException(`No files found for share ${shareId}`);
|
||||
}
|
||||
|
||||
const archive = archiver("zip", {
|
||||
zlib: { level: parseInt(compressionLevel) },
|
||||
});
|
||||
|
||||
archive.on("error", (err) => {
|
||||
this.logger.error("Archive error", err);
|
||||
reject(new InternalServerErrorException("Error creating ZIP file"));
|
||||
});
|
||||
|
||||
const fileKeys = listResponse.Contents.filter(
|
||||
(object) => object.Key && object.Key !== prefix,
|
||||
).map((object) => object.Key as string);
|
||||
|
||||
if (fileKeys.length === 0) {
|
||||
throw new NotFoundException(
|
||||
`No valid files found for share ${shareId}`,
|
||||
);
|
||||
}
|
||||
|
||||
let filesAdded = 0;
|
||||
|
||||
const processNextFile = async (index: number) => {
|
||||
if (index >= fileKeys.length) {
|
||||
archive.finalize();
|
||||
return;
|
||||
}
|
||||
|
||||
const key = fileKeys[index];
|
||||
const fileName = key.replace(prefix, "");
|
||||
|
||||
try {
|
||||
const response = await s3Instance.send(
|
||||
new GetObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: key,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.Body instanceof Readable) {
|
||||
const fileStream = response.Body;
|
||||
|
||||
fileStream.on("end", () => {
|
||||
filesAdded++;
|
||||
processNextFile(index + 1);
|
||||
});
|
||||
|
||||
fileStream.on("error", (err) => {
|
||||
this.logger.error(`Error streaming file ${fileName}`, err);
|
||||
processNextFile(index + 1);
|
||||
});
|
||||
|
||||
archive.append(fileStream, { name: fileName });
|
||||
} else {
|
||||
processNextFile(index + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Error processing file ${fileName}`, error);
|
||||
processNextFile(index + 1);
|
||||
}
|
||||
};
|
||||
|
||||
resolve(archive);
|
||||
processNextFile(0);
|
||||
} catch (error) {
|
||||
this.logger.error("Error creating ZIP file", error);
|
||||
|
||||
reject(new InternalServerErrorException("Error creating ZIP file"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getS3Path(): string {
|
||||
|
||||
@@ -10,7 +10,7 @@ general:
|
||||
secureCookies: "false"
|
||||
#Whether to show the home page
|
||||
showHomePage: "true"
|
||||
#Time in hours after which a user must log in again (default: 3 months).
|
||||
#Time after which a user must log in again (default: 3 months).
|
||||
sessionDuration: 3 months
|
||||
share:
|
||||
#Whether registration is allowed
|
||||
@@ -45,7 +45,7 @@ email:
|
||||
Hey!
|
||||
|
||||
|
||||
{creator} ({creatorEmail}) shared some files with you, view or download the
|
||||
{creator} ({creatorEmail}) shared some files with you. You can view or download the
|
||||
files with this link: {shareUrl}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ email:
|
||||
You requested a password reset. Click this link to reset your password:
|
||||
{url}
|
||||
|
||||
The link expires in a hour.
|
||||
The link expires in an hour.
|
||||
|
||||
|
||||
Pingvin Share 🐧
|
||||
@@ -103,7 +103,7 @@ smtp:
|
||||
host: ""
|
||||
#Port of the SMTP server
|
||||
port: "0"
|
||||
#Email address from wich the emails get sent
|
||||
#Email address from which the emails get sent
|
||||
email: ""
|
||||
#Username of the SMTP server
|
||||
username: ""
|
||||
@@ -204,6 +204,8 @@ s3:
|
||||
key: ""
|
||||
#The secret which allows you to access the S3 bucket.
|
||||
secret: ""
|
||||
#Turn off for backends that do not support checksum (e.g. B2).
|
||||
useChecksum: "true"
|
||||
legal:
|
||||
#Whether to show a link to imprint and privacy policy in the footer.
|
||||
enabled: "false"
|
||||
|
||||
@@ -25,7 +25,7 @@ Your container is now listening on `http://localhost:<externalport>`, have fun w
|
||||
|
||||
Required tools:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download/) >= 16
|
||||
- [Node.js](https://nodejs.org/en/download/) >= 22
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [pm2](https://pm2.keymetrics.io/) for running Pingvin Share in the background
|
||||
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "1.11.1",
|
||||
"version": "1.12.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "1.11.1",
|
||||
"version": "1.12.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/server": "^11.11.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "1.11.1",
|
||||
"version": "1.12.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import Markdown, { MarkdownToJSX } from "markdown-to-jsx";
|
||||
import Link from "next/link";
|
||||
import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import api from "../../services/api.service";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
|
||||
const FilePreviewContext = React.createContext<{
|
||||
shareId: string;
|
||||
@@ -132,7 +132,8 @@ const TextPreview = () => {
|
||||
.then((res) => setText(res.data ?? "Preview couldn't be fetched."));
|
||||
}, [shareId, fileId]);
|
||||
|
||||
const options = {
|
||||
const options: MarkdownToJSX.Options = {
|
||||
disableParsingRawHTML: true,
|
||||
overrides: {
|
||||
pre: {
|
||||
props: {
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "المشاركة {shareId}",
|
||||
"share.description": "انظر ما الذي شاركته معك!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "تم تجاوز حد المشاهدات",
|
||||
"share.error.visitor-limit-exceeded.description": "تم تجاوز الحد الأقصى لزوار هذه المشاركة.",
|
||||
"share.error.removed.title": "تمت إزالة المشاركة",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Sdílení {shareId}",
|
||||
"share.description": "Podívejte se, co jsem s vámi sdílel!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Limit návštěvníků překročen",
|
||||
"share.error.visitor-limit-exceeded.description": "Limit návštěvníků tohoto sdílení byl překročen.",
|
||||
"share.error.removed.title": "Sdílení bylo odstraněno",
|
||||
@@ -531,4 +532,4 @@ export default {
|
||||
"common.error.exact-length": "Musí mít přesně {length} znaků",
|
||||
"common.error.invalid-number": "Musí být číslo",
|
||||
"common.error.field-required": "Toto pole je povinné"
|
||||
};
|
||||
};
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Del {shareId}",
|
||||
"share.description": "Se hvad jeg har delt med dig!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Grænsen for besøgende overskredet",
|
||||
"share.error.visitor-limit-exceeded.description": "Besøgsgrænsen for denne deling er blevet overskredet.",
|
||||
"share.error.removed.title": "Deling fjernet",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Freigabe {shareId}",
|
||||
"share.description": "Schau, was ich mit dir geteilt habe!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Besucher Limit erreicht",
|
||||
"share.error.visitor-limit-exceeded.description": "Die maximale Besucheranzahl für diese Freigabe ist überschritten.",
|
||||
"share.error.removed.title": "Freigabe entfernt",
|
||||
@@ -525,7 +526,7 @@ export default {
|
||||
"common.error.unknown": "Ein unbekannter Fehler ist aufgetreten",
|
||||
"common.error.invalid-email": "Ungültige E-Mail-Adresse",
|
||||
"common.error.too-short": "Muss mindestens {length} Zeichen enthalten",
|
||||
"common.error.too-long": "Muss maximal {length} Zeichen enthalten",
|
||||
"common.error.too-long": "Darf maximal {length} Zeichen enthalten",
|
||||
"common.error.number-too-small": "Darf mindestens {min} sein",
|
||||
"common.error.number-too-large": "Darf höchstens {max} sein",
|
||||
"common.error.exact-length": "Muss genau {length} Zeichen lang sein",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Διαμοιρασμός {shareId}",
|
||||
"share.description": "Σας προωθώ αρχεία προς κοινοποίηση.",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Υπέρβαση ορίου επισκέπτη",
|
||||
"share.error.visitor-limit-exceeded.description": "Ξεπεράστηκε το όριο επισκεπτών σε αυτή την κοινοποίηση.",
|
||||
"share.error.removed.title": "Κοινοποίηση αφαιρέθηκε",
|
||||
|
||||
@@ -366,6 +366,8 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.fileCount":
|
||||
"{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
@@ -408,14 +410,15 @@ export default {
|
||||
// /imprint
|
||||
"imprint.title": "Imprint",
|
||||
// END /imprint
|
||||
|
||||
|
||||
// /privacy
|
||||
"privacy.title": "Privacy Policy",
|
||||
// END /privacy
|
||||
|
||||
// /admin/config
|
||||
"admin.config.config-file-warning.title": "Configuration file present",
|
||||
"admin.config.config-file-warning.description": "As you have a configured Pingvin Share with a configuration file, you can't change the configuration through the UI.",
|
||||
"admin.config.config-file-warning.description":
|
||||
"As you have a configured Pingvin Share with a configuration file, you can't change the configuration through the UI.",
|
||||
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
@@ -642,7 +645,8 @@ export default {
|
||||
|
||||
"admin.config.category.s3": "S3",
|
||||
"admin.config.s3.enabled": "Enabled",
|
||||
"admin.config.s3.enabled.description": "Whether S3 should be used to store the shared files instead of the local file system.",
|
||||
"admin.config.s3.enabled.description":
|
||||
"Whether S3 should be used to store the shared files instead of the local file system.",
|
||||
"admin.config.s3.endpoint": "Endpoint",
|
||||
"admin.config.s3.endpoint.description": "The URL of the S3 bucket.",
|
||||
"admin.config.s3.region": "Region",
|
||||
@@ -650,25 +654,34 @@ export default {
|
||||
"admin.config.s3.bucket-name": "Bucket name",
|
||||
"admin.config.s3.bucket-name.description": "The name of the S3 bucket.",
|
||||
"admin.config.s3.bucket-path": "Path",
|
||||
"admin.config.s3.bucket-path.description": "The default path which should be used to store the files in the S3 bucket.",
|
||||
"admin.config.s3.bucket-path.description":
|
||||
"The default path which should be used to store the files in the S3 bucket.",
|
||||
"admin.config.s3.key": "Key",
|
||||
"admin.config.s3.key.description": "The key which allows you to access the S3 bucket.",
|
||||
"admin.config.s3.key.description":
|
||||
"The key which allows you to access the S3 bucket.",
|
||||
"admin.config.s3.secret": "Secret",
|
||||
"admin.config.s3.secret.description": "The secret which allows you to access the S3 bucket.",
|
||||
"admin.config.s3.secret.description":
|
||||
"The secret which allows you to access the S3 bucket.",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum.description":
|
||||
"Turn off for backends that do not support checksum (e.g. B2).",
|
||||
|
||||
"admin.config.category.legal": "Legal",
|
||||
"admin.config.legal.enabled": "Enable legal notices",
|
||||
"admin.config.legal.enabled.description": "Whether to show a link to imprint and privacy policy in the footer.",
|
||||
"admin.config.legal.enabled.description":
|
||||
"Whether to show a link to imprint and privacy policy in the footer.",
|
||||
"admin.config.legal.imprint-text": "Imprint text",
|
||||
"admin.config.legal.imprint-text.description": "The text which should be shown in the imprint. Supports Markdown. Leave blank to link to an external imprint page.",
|
||||
"admin.config.legal.imprint-text.description":
|
||||
"The text which should be shown in the imprint. Supports Markdown. Leave blank to link to an external imprint page.",
|
||||
"admin.config.legal.imprint-url": "Imprint URL",
|
||||
"admin.config.legal.imprint-url.description": "If you already have an imprint page you can link it here instead of using the text field.",
|
||||
"admin.config.legal.imprint-url.description":
|
||||
"If you already have an imprint page you can link it here instead of using the text field.",
|
||||
"admin.config.legal.privacy-policy-text": "Privacy policy text",
|
||||
"admin.config.legal.privacy-policy-text.description": "The text which should be shown in the privacy policy. Supports Markdown. Leave blank to link to an external privacy policy page.",
|
||||
"admin.config.legal.privacy-policy-text.description":
|
||||
"The text which should be shown in the privacy policy. Supports Markdown. Leave blank to link to an external privacy policy page.",
|
||||
"admin.config.legal.privacy-policy-url": "Privacy policy URL",
|
||||
"admin.config.legal.privacy-policy-url.description": "If you already have a privacy policy page you can link it here instead of using the text field.",
|
||||
"admin.config.legal.privacy-policy-url.description":
|
||||
"If you already have a privacy policy page you can link it here instead of using the text field.",
|
||||
|
||||
// 404
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Compartido {shareId}",
|
||||
"share.description": "¡Mira lo que he compartido contigo!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Se excedió el límite de visitas",
|
||||
"share.error.visitor-limit-exceeded.description": "Se ha excedido el límite de visitas para este compartido.",
|
||||
"share.error.removed.title": "Compartido eliminado",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Jagamine {shareId}",
|
||||
"share.description": "Vaata, mida ma sinuga jagasin!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Külastajate limiit ületatud",
|
||||
"share.error.visitor-limit-exceeded.description": "Selle jagamise külastajate limiit on ületatud.",
|
||||
"share.error.removed.title": "Jagamine eemaldatud",
|
||||
@@ -465,8 +466,8 @@ export default {
|
||||
"admin.config.s3.key.description": "S3 ämbri võti.",
|
||||
"admin.config.s3.secret": "Saladus",
|
||||
"admin.config.s3.secret.description": "S3 ämbri saladus.",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum": "Kasuta kontrollsummat",
|
||||
"admin.config.s3.use-checksum.description": "Lülita välja, kui tagaliides ei toeta kontrollsummasid (nt B2).",
|
||||
"admin.config.category.legal": "Juriidiline",
|
||||
"admin.config.legal.enabled": "Luba juriidilised teated",
|
||||
"admin.config.legal.enabled.description": "Kas kuvada linki kontaktandmetele ja privaatsuspoliitikale jaluses.",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Jaa {shareId}",
|
||||
"share.description": "Katso, mitä olen jakanut kanssasi!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Vierailijoiden raja ylitetty",
|
||||
"share.error.visitor-limit-exceeded.description": "Tämän jaon kävijäraja on ylittynyt.",
|
||||
"share.error.removed.title": "Jako poistettu",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Partage {shareId}",
|
||||
"share.description": "Regardez ce que j’ai partagé !",
|
||||
"share.fileCount": "{count, plural, =1 {# fichier} other {# fichiers}} · {size} (le fichier compressé peut être plus petit)",
|
||||
"share.error.visitor-limit-exceeded.title": "Limite de visiteurs dépassée",
|
||||
"share.error.visitor-limit-exceeded.description": "La limite de visiteurs de ce partage a été dépassée.",
|
||||
"share.error.removed.title": "Partage supprimé",
|
||||
@@ -465,8 +466,8 @@ export default {
|
||||
"admin.config.s3.key.description": "La clé qui vous permet d'accéder au bucket S3.",
|
||||
"admin.config.s3.secret": "Secret",
|
||||
"admin.config.s3.secret.description": "Le secret qui vous permet d'accéder au bucket S3.",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum": "Utiliser la somme de contrôle",
|
||||
"admin.config.s3.use-checksum.description": "Désactivez cette option pour les backends qui ne prennent pas en charge la somme de contrôle (ex. B2).",
|
||||
"admin.config.category.legal": "Juridique",
|
||||
"admin.config.legal.enabled": "Activer les mentions légales",
|
||||
"admin.config.legal.enabled.description": "Afficher/Masquer dans le pied de page un lien vers les mentions légales et la politique de confidentialité.",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Dijeljenje {shareId}",
|
||||
"share.description": "Pogledajte što sam podijelio s vama!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Prekoračeno ograničenje posjetitelja",
|
||||
"share.error.visitor-limit-exceeded.description": "Ograničenje broja posjetitelja za ovo dijeljenje je premašeno.",
|
||||
"share.error.removed.title": "Dijeljenje je uklonjeno",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Megosztás: {shareId}",
|
||||
"share.description": "Megosztottak Önnel valami fontosat!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Túl sok látogató",
|
||||
"share.error.visitor-limit-exceeded.description": "A látogatók száma elérte a megszabott korlátot.",
|
||||
"share.error.removed.title": "Korábban már eltávolított megosztás",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Condividi {shareId}",
|
||||
"share.description": "Guarda cosa ho condiviso con te!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# file}} · {size} (il file zip potrebbe essere più piccolo a causa della compressione)",
|
||||
"share.error.visitor-limit-exceeded.title": "Limite visitatori superato",
|
||||
"share.error.visitor-limit-exceeded.description": "Il limite di visitatori di questa condivisione è stato superato.",
|
||||
"share.error.removed.title": "Condivisione rimossa",
|
||||
@@ -465,8 +466,8 @@ export default {
|
||||
"admin.config.s3.key.description": "La chiave che consente di accedere al bucket S3.",
|
||||
"admin.config.s3.secret": "Parola chiave",
|
||||
"admin.config.s3.secret.description": "La parola chiave che consente di accedere al bucket S3.",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum": "Usa il checksum",
|
||||
"admin.config.s3.use-checksum.description": "Disattiva per i backend che non supportano il checksum (ad es. B2).",
|
||||
"admin.config.category.legal": "Legale",
|
||||
"admin.config.legal.enabled": "Abilita le notifiche legali",
|
||||
"admin.config.legal.enabled.description": "Indica se mostrare o meno un link alle informative legali e sulla privacy a piè di pagina.",
|
||||
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
"navbar.links.reverse": "ファイルリクエスト",
|
||||
"navbar.avatar.account": "マイアカウント",
|
||||
"navbar.avatar.admin": "管理画面",
|
||||
"navbar.avatar.signout": "サインアウト",
|
||||
"navbar.avatar.signout": "ログアウト",
|
||||
// END navbar
|
||||
// /
|
||||
"home.title": "<h>セルフホスト</h>のファイル共有プラットフォーム。",
|
||||
@@ -25,16 +25,16 @@ export default {
|
||||
// /auth/signin
|
||||
"signin.title": "おかえりなさい",
|
||||
"signin.description": "アカウントをお持ちではありませんか?",
|
||||
"signin.button.signup": "会員登録",
|
||||
"signin.button.signup": "新規登録",
|
||||
"signin.input.email-or-username": "メールアドレスまたはユーザー名",
|
||||
"signin.input.email-or-username.placeholder": "メールアドレスまたはユーザー名",
|
||||
"signin.input.password": "パスワード",
|
||||
"signin.input.password.placeholder": "あなたのパスワード",
|
||||
"signin.button.submit": "サインイン",
|
||||
"signin.button.submit": "ログイン",
|
||||
"signIn.notify.totp-required.title": "二段階認証が必要です",
|
||||
"signIn.notify.totp-required.description": "二段階認証コードを入力してください",
|
||||
"signIn.oauth.or": "または",
|
||||
"signIn.oauth.signInWith": "サインインの方法",
|
||||
"signIn.oauth.signInWith": "ログインの方法",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
@@ -44,7 +44,7 @@ export default {
|
||||
// /auth/signup
|
||||
"signup.title": "アカウントを作成",
|
||||
"signup.description": "既にアカウントをお持ちですか?",
|
||||
"signup.button.signin": "サインイン",
|
||||
"signup.button.signin": "ログイン",
|
||||
"signup.input.username": "ユーザー名",
|
||||
"signup.input.username.placeholder": "あなたのユーザー名",
|
||||
"signup.input.email": "メールアドレス",
|
||||
@@ -53,13 +53,13 @@ export default {
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "二段階認証",
|
||||
"totp.button.signIn": "サインイン",
|
||||
"totp.button.signIn": "ログイン",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "パスワードを忘れてしまいましたか?",
|
||||
"resetPassword.description": "登録しているメールアドレスを入力してください。",
|
||||
"resetPassword.notify.success": "指定されたメールアドレスが存在する場合、パスワードをリセットするためのリンクを含むメッセージが送信されました。",
|
||||
"resetPassword.button.back": "サインインページに戻る",
|
||||
"resetPassword.button.back": "ログインページに戻る",
|
||||
"resetPassword.text.resetPassword": "パスワードをリセット",
|
||||
"resetPassword.text.enterNewPassword": "新規パスワードを入力",
|
||||
"resetPassword.input.password": "新規パスワード",
|
||||
@@ -85,11 +85,11 @@ export default {
|
||||
"account.card.oauth.unlink": "リンク解除",
|
||||
"account.card.oauth.unlinked": "リンクされていません",
|
||||
"account.modal.unlink.title": "アカウントのリンクを解除",
|
||||
"account.modal.unlink.description": "ソーシャルアカウントのリンクを解除すると、ログイン認証情報を忘れた場合、アカウントを失う可能性があります。",
|
||||
"account.modal.unlink.description": "ソーシャルアカウントのリンクを解除すると、ログイン認証情報を忘れた場合にカウントへアクセスできなくなる可能性があります。",
|
||||
"account.notify.oauth.unlinked.success": "リンク解除に成功しました",
|
||||
"account.card.security.title": "セキュリティ",
|
||||
"account.card.security.totp.enable.description": "2段階認証を有効にするため、現在のパスワードを入力してください",
|
||||
"account.card.security.totp.disable.description": "2段階認証を無効にするため、現在のパスワードを入力してください",
|
||||
"account.card.security.totp.enable.description": "2段階認証を有効にするには、現在のパスワードを入力してください",
|
||||
"account.card.security.totp.disable.description": "2段階認証を無効にするには、現在のパスワードを入力してください",
|
||||
"account.card.security.totp.button.start": "開始",
|
||||
"account.modal.totp.title": "2段階認証を有効にする",
|
||||
"account.modal.totp.step1": "ステップ1: 認証アプリを追加する",
|
||||
@@ -216,7 +216,7 @@ export default {
|
||||
// /upload
|
||||
"upload.title": "アップロード",
|
||||
"upload.notify.confirm-leave": "このページから離れますか?アップロードはキャンセルされます。",
|
||||
"upload.notify.generic-error": "共有を仕上げている最中にエラーが発生しました。",
|
||||
"upload.notify.generic-error": "共有の最終処理でエラーが発生しました。",
|
||||
"upload.notify.count-failed": "{count} ファイルがアップロードに失敗しました。再度お試しください。",
|
||||
"upload.reverse-share.error.invalid.title": "無効なファイルリクエストリンク",
|
||||
"upload.reverse-share.error.invalid.description": "このファイルリクエストは期限が切れているか無効です。",
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "「{shareId}」が共有されました",
|
||||
"share.description": "あなたと共有したファイルをご確認ください!",
|
||||
"share.fileCount": "{count, plural, =1 {# ファイル} other {# ファイル}} · {size} (圧縮により ZIP ファイルはより小さくなる場合があります)",
|
||||
"share.error.visitor-limit-exceeded.title": "訪問者の上限を超えました",
|
||||
"share.error.visitor-limit-exceeded.description": "この共有からの訪問者の回数が制限を超えています。",
|
||||
"share.error.removed.title": "共有が削除されました",
|
||||
@@ -419,7 +420,7 @@ export default {
|
||||
"admin.config.oauth.oidc-username-claim": "OpenID Connect ユーザー名の要求",
|
||||
"admin.config.oauth.oidc-username-claim.description": "OpenID Connect ID トークンのユーザー名要求。この設定が何かわからない場合は空白のままにしてください。",
|
||||
"admin.config.oauth.oidc-role-path": "OpenID Connectトークンのロールへのパス",
|
||||
"admin.config.oauth.oidc-role-path.description": "ロールの配列を参照する有効なJMESパスでなければなりません。" + "OpenID Connectのロールを使用してアクセス権を管理することは、他のIDプロバイダが設定されておらず、パスワードログインが無効になっている場合にのみ推奨されます。この構成がわからない場合は空白のままにしてください。" + "この設定が何であるか分からない場合は空白のままにしてください。",
|
||||
"admin.config.oauth.oidc-role-path.description": "ロールの配列を参照する有効なJMESパスでなければなりません。 " + "OpenID Connectのロールを使用してアクセス権を管理することは、他のIDプロバイダが設定されておらず、パスワードログインが無効になっている場合にのみ推奨されます。この構成がわからない場合は空白のままにしてください。 " + "この設定が何であるか分からない場合は空白のままにしてください。",
|
||||
"admin.config.oauth.oidc-role-general-access": "一般的なアクセスのためのOpenID Connectのロール",
|
||||
"admin.config.oauth.oidc-role-general-access.description": "一般的なアクセスに必要なロール。ログインするユーザーのロールに存在する必要があります。 " + "この設定が何であるか分からない場合は空白のままにしてください。",
|
||||
"admin.config.oauth.oidc-role-admin-access": "管理者アクセスのための OpenID Connectのロール",
|
||||
@@ -465,8 +466,8 @@ export default {
|
||||
"admin.config.s3.key.description": "S3バケットにアクセスするためのキー。",
|
||||
"admin.config.s3.secret": "秘密",
|
||||
"admin.config.s3.secret.description": "S3バケットにアクセスするための秘密。",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum": "チェックサムを使用",
|
||||
"admin.config.s3.use-checksum.description": "チェックサムをサポートしていないバックエンド(例:B2)では無効にしてください。",
|
||||
"admin.config.category.legal": "法的事項",
|
||||
"admin.config.legal.enabled": "法的通知を有効にする",
|
||||
"admin.config.legal.enabled.description": "フッターにインプリントとプライバシーポリシーへのリンクを表示するか。",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "공유 {shareId}",
|
||||
"share.description": "내가 당신과 공유한 것을 보세요!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "방문자 한도 초과",
|
||||
"share.error.visitor-limit-exceeded.description": "이 공유의 방문자 한도를 초과했습니다.",
|
||||
"share.error.removed.title": "공유가 삭제됨",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Kijk eens wat ik met je heb gedeeld!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Bezoekerslimiet overschreden",
|
||||
"share.error.visitor-limit-exceeded.description": "De bezoekerslimiet van deze share is overschreden.",
|
||||
"share.error.removed.title": "Share was verwijderd",
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Nie pamiętasz hasła?",
|
||||
"resetPassword.description": "Wprowadź swój e-mail, aby zresetować swoje hasło.",
|
||||
"resetPassword.notify.success": "A message with a link to reset your password has been sent if the provided email exists.",
|
||||
"resetPassword.notify.success": "Jeśli podany adres e-mail istnieje, wiadomość z linkiem umożliwiającym zresetowanie hasła została wysłana.",
|
||||
"resetPassword.button.back": "Powrót do strony logowania",
|
||||
"resetPassword.text.resetPassword": "Resetuj hasło",
|
||||
"resetPassword.text.enterNewPassword": "Wprowadź nowe hasło",
|
||||
@@ -129,7 +129,7 @@ export default {
|
||||
"account.shares.table.expiry-never": "Never",
|
||||
"account.shares.modal.share-informations": "Informacje udziału",
|
||||
"account.shares.modal.share-link": "Udostępnij link",
|
||||
"account.shares.modal.delete.title": "Delete share: {share}",
|
||||
"account.shares.modal.delete.title": "Usuń udostępnianie: {share}",
|
||||
"account.shares.modal.delete.description": "Czy na pewno chcesz usunąć to udostępnianie?",
|
||||
// END /account/shares
|
||||
// /account/reverseShares
|
||||
@@ -156,7 +156,7 @@ export default {
|
||||
"account.reverseShares.modal.send-email": "Send email notifications",
|
||||
"account.reverseShares.modal.send-email.description": "Sends you an email notification when a share is created with this reverse share link.",
|
||||
"account.reverseShares.modal.simplified": "Tryb uproszczony",
|
||||
"account.reverseShares.modal.simplified.description": "Make it easy for the person uploading the file to share it with you. They will only be able to customize the name and description of the share.",
|
||||
"account.reverseShares.modal.simplified.description": "",
|
||||
"account.reverseShares.modal.public-access": "Dostęp publiczny",
|
||||
"account.reverseShares.modal.public-access.description": "Make the shares created with this reverse share public. If disabled, only you and the share creator will have access to view it.",
|
||||
"account.reverseShares.modal.max-use.label": "Limit użyć",
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Udostępnij {shareId}",
|
||||
"share.description": "Spójrz, co ci udostępniłem!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Przekroczono limit odwiedzających",
|
||||
"share.error.visitor-limit-exceeded.description": "Limit odwiedzających dla tego udziału został przekroczony.",
|
||||
"share.error.removed.title": "Udostępnianie usunięte",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Compartilhar {shareId}",
|
||||
"share.description": "Veja o que eu compartilhei com você!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Limite de visitantes excedido",
|
||||
"share.error.visitor-limit-exceeded.description": "O limite de visitantes deste compartilhamento foi excedido.",
|
||||
"share.error.removed.title": "Compartilhamento removido",
|
||||
@@ -465,8 +466,8 @@ export default {
|
||||
"admin.config.s3.key.description": "A chave que permite a você acessar o bucket S3.",
|
||||
"admin.config.s3.secret": "Segredo",
|
||||
"admin.config.s3.secret.description": "O segredo que permite a você acessar o bucket S3.",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum": "Usar Checksum",
|
||||
"admin.config.s3.use-checksum.description": "Desligar para backends que não suportem checksum (por exemplo, B2).",
|
||||
"admin.config.category.legal": "Aviso legal",
|
||||
"admin.config.legal.enabled": "Habilitar avisos legais",
|
||||
"admin.config.legal.enabled.description": "Exibir um link para imprimir e política de privacidade no rodapé.",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Загрузка {shareId}",
|
||||
"share.description": "Посмотрите, чем я поделился с вами!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Превышен лимит посетителей",
|
||||
"share.error.visitor-limit-exceeded.description": "Превышен лимит посетителей.",
|
||||
"share.error.removed.title": "Загрузка удалена",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Deli {shareId}",
|
||||
"share.description": "Poglej kaj sem delil s taboj!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Število ogledov je prekoračena",
|
||||
"share.error.visitor-limit-exceeded.description": "Število ogledov za to delitev je bila prekoračena.",
|
||||
"share.error.removed.title": "Delitev je odstranjena",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Deljenje {shareId}",
|
||||
"share.description": "Pogledajte šta sam podelio sa vama!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Prekoračeno je ograničenje posetilaca",
|
||||
"share.error.visitor-limit-exceeded.description": "Ograničenje posetilaca iz ovog deljenja je premašeno.",
|
||||
"share.error.removed.title": "Deljenje je uklonjeno",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Дељење {shareId}",
|
||||
"share.description": "Погледајте шта сам поделио са вама!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Прекорачено је ограничење посетилаца",
|
||||
"share.error.visitor-limit-exceeded.description": "Ограничење посетилаца из овог дељења је премашено.",
|
||||
"share.error.removed.title": "Дељење је уклоњено",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Delning {shareId}",
|
||||
"share.description": "Titta vad jag har delat med dig!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Besökargränsen överskriden",
|
||||
"share.error.visitor-limit-exceeded.description": "Gränsen för antalet besökare för denna delning har överskridits.",
|
||||
"share.error.removed.title": "Delning borttagen",
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
"signin.button.submit": "เข้าสู่ระบบ",
|
||||
"signIn.notify.totp-required.title": "ยืนยันตรวจสอบสิทธิ์สองปัจจัย",
|
||||
"signIn.notify.totp-required.description": "กรุณาใส่รหัสยืนยันตัวตนสองปัจจัย",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.or": "หรือ",
|
||||
"signIn.oauth.signInWith": "Sign in with",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
@@ -485,7 +485,7 @@ export default {
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.default": "มีบางอย่างผิดพลาด",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "{shareId} Paylaşımı",
|
||||
"share.description": "Sizinle paylaştıklarımı bir göz atın!",
|
||||
"share.fileCount": "{count, plural, =1 {# dosya} other {# dosya}} · {size} (sıkıştırma nedeniyle zip dosyası daha küçük olabilir)",
|
||||
"share.error.visitor-limit-exceeded.title": "Ziyaretçi limiti aşıldı",
|
||||
"share.error.visitor-limit-exceeded.description": "Bu paylaşımın ziyaretçi limiti aşıldı.",
|
||||
"share.error.removed.title": "Paylaşım kaldırıldı",
|
||||
@@ -465,8 +466,8 @@ export default {
|
||||
"admin.config.s3.key.description": "S3 kovasına erişmenizi sağlayan anahtar.",
|
||||
"admin.config.s3.secret": "Gizli",
|
||||
"admin.config.s3.secret.description": "S3 kovasına erişmenizi sağlayan gizli anahtar.",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum": "Doğrulama kullan",
|
||||
"admin.config.s3.use-checksum.description": "Doğrulama desteklemeyen arka uçlar için kapatın (ör. B2).",
|
||||
"admin.config.category.legal": "Yasal",
|
||||
"admin.config.legal.enabled": "Yasal Bildirimleri Etkinleştir",
|
||||
"admin.config.legal.enabled.description": "Alt bilgi kısmında künyeye ve gizlilik politikasına bir bağlantı gösterilip gösterilmeyeceğini belirtir.",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Завантаження {shareId}",
|
||||
"share.description": "Подивіться, чим я поділився з вами!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Перевищено ліміт відвідувачів",
|
||||
"share.error.visitor-limit-exceeded.description": "Перевищено ліміт відвідувачів.",
|
||||
"share.error.removed.title": "Завантаження видалено",
|
||||
@@ -465,8 +466,8 @@ export default {
|
||||
"admin.config.s3.key.description": "Ключ, який дозволяє отримати доступ до S3 бакету.",
|
||||
"admin.config.s3.secret": "Секрет",
|
||||
"admin.config.s3.secret.description": "Секрет, який дозволяє отримати доступ до S3 бакету.",
|
||||
"admin.config.s3.use-checksum": "Use checksum",
|
||||
"admin.config.s3.use-checksum.description": "Turn off for backends that do not support checksum (e.g. B2).",
|
||||
"admin.config.s3.use-checksum": "Використовувати контрольну суму",
|
||||
"admin.config.s3.use-checksum.description": "Вимкніть для непідтримуваних контрольних сум (напр. B2).",
|
||||
"admin.config.category.legal": "Юридична інформація",
|
||||
"admin.config.legal.enabled": "Увімкнути правові сповіщення",
|
||||
"admin.config.legal.enabled.description": "Чи відображати посилання на вихідні дані та політику конфіденційності у нижньому колонтитулі.",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description": "The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "共享 {shareId}",
|
||||
"share.description": "瞧瞧我给你共享了些什么!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "访问次数达到上限",
|
||||
"share.error.visitor-limit-exceeded.description": "访问次数达到上限",
|
||||
"share.error.removed.title": "共享已删除",
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
// /share/[id]
|
||||
"share.title": "分享 {shareId}",
|
||||
"share.description": "瞧瞧我給您分享了些什麽!",
|
||||
"share.fileCount": "{count, plural, =1 {# file} other {# files}} · {size} (zip file may be smaller due to compression)",
|
||||
"share.error.visitor-limit-exceeded.title": "查看次數達到上限",
|
||||
"share.error.visitor-limit-exceeded.description": "查看次數達到上限",
|
||||
"share.error.removed.title": "分享已刪除",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Box, Group, Text, Title } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../../components/Meta";
|
||||
import DownloadAllButton from "../../../components/share/DownloadAllButton";
|
||||
import FileList from "../../../components/share/FileList";
|
||||
@@ -11,6 +12,7 @@ import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { Share as ShareType } from "../../../types/share.type";
|
||||
import toast from "../../../utils/toast.util";
|
||||
import { byteToHumanSizeString } from "../../../utils/fileSize.util";
|
||||
|
||||
export function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
@@ -107,7 +109,25 @@ const Share = ({ shareId }: { shareId: string }) => {
|
||||
<Box style={{ maxWidth: "70%" }}>
|
||||
<Title order={3}>{share?.name || share?.id}</Title>
|
||||
<Text size="sm">{share?.description}</Text>
|
||||
{share?.files?.length > 0 && (
|
||||
<Text size="sm" color="dimmed" mt={5}>
|
||||
<FormattedMessage
|
||||
id="share.fileCount"
|
||||
values={{
|
||||
count: share?.files?.length || 0,
|
||||
size: byteToHumanSizeString(
|
||||
share?.files?.reduce(
|
||||
(total: number, file: { size: string }) =>
|
||||
total + parseInt(file.size),
|
||||
0,
|
||||
) || 0,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{share?.files.length > 1 && <DownloadAllButton shareId={shareId} />}
|
||||
</Group>
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share",
|
||||
"version": "1.11.1",
|
||||
"version": "1.12.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share",
|
||||
"version": "1.11.1",
|
||||
"version": "1.12.0",
|
||||
"devDependencies": {
|
||||
"conventional-changelog-cli": "^3.0.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share",
|
||||
"version": "1.11.1",
|
||||
"version": "1.12.0",
|
||||
"scripts": {
|
||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||
@@ -11,5 +11,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"conventional-changelog-cli": "^3.0.0"
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user