feat(nginx): configure client max body size and update timeouts for API proxy
feat(clipboard): implement copyTextToClipboard utility function feat(desktop): add upload timeout for desktop client release API feat(k3s): add nginx config and update kustomization for ops-web
This commit is contained in:
@@ -8,6 +8,7 @@ real_ip_recursive on;
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
client_max_body_size 600M;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
@@ -30,8 +31,8 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_read_timeout 900s;
|
||||
proxy_send_timeout 900s;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
export async function copyTextToClipboard(text: string): Promise<void> {
|
||||
if (!text) {
|
||||
throw new Error('clipboard_empty_text')
|
||||
}
|
||||
|
||||
if (window.isSecureContext && navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
return
|
||||
} catch {
|
||||
// Fall back to execCommand for browsers that expose clipboard but deny it.
|
||||
}
|
||||
}
|
||||
|
||||
if (copyTextWithSelection(text)) {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('clipboard_write_failed')
|
||||
}
|
||||
|
||||
function copyTextWithSelection(text: string): boolean {
|
||||
const textarea = document.createElement('textarea')
|
||||
const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null
|
||||
|
||||
textarea.value = text
|
||||
textarea.setAttribute('readonly', '')
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.top = '0'
|
||||
textarea.style.left = '-9999px'
|
||||
textarea.style.opacity = '0'
|
||||
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
textarea.setSelectionRange(0, text.length)
|
||||
|
||||
try {
|
||||
return document.execCommand('copy')
|
||||
} finally {
|
||||
document.body.removeChild(textarea)
|
||||
activeElement?.focus({ preventScroll: true })
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,8 @@ export interface DesktopClientReleaseDownloadURLResult {
|
||||
expires_in?: number | null
|
||||
}
|
||||
|
||||
const DESKTOP_CLIENT_RELEASE_UPLOAD_TIMEOUT_MS = 15 * 60 * 1000
|
||||
|
||||
export const platformOptions = [
|
||||
{ label: 'macOS', value: 'darwin' },
|
||||
{ label: 'Windows', value: 'win32' },
|
||||
@@ -138,7 +140,9 @@ export const desktopClientReleaseApi = {
|
||||
code: number
|
||||
message: string
|
||||
data: DesktopClientReleaseUploadResult
|
||||
}>('/desktop-client/releases/upload', form)
|
||||
}>('/desktop-client/releases/upload', form, {
|
||||
timeout: DESKTOP_CLIENT_RELEASE_UPLOAD_TIMEOUT_MS,
|
||||
})
|
||||
return response.data.data
|
||||
},
|
||||
resolveDownloadURL(id: number) {
|
||||
|
||||
@@ -357,6 +357,7 @@ import {
|
||||
type DesktopReleasePlatform,
|
||||
type DesktopReleaseSource,
|
||||
} from '@/lib/desktop-client-releases'
|
||||
import { copyTextToClipboard } from '@/lib/clipboard'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
const enabledOptions = [
|
||||
@@ -763,19 +764,31 @@ async function deleteRelease(row: DesktopClientRelease) {
|
||||
}
|
||||
|
||||
async function copyDownloadURL(row: DesktopClientRelease) {
|
||||
let downloadURL = ''
|
||||
try {
|
||||
const result = await desktopClientReleaseApi.resolveDownloadURL(row.id)
|
||||
await navigator.clipboard.writeText(result.download_url)
|
||||
message.success('下载地址已复制')
|
||||
downloadURL = result.download_url
|
||||
} catch (error) {
|
||||
showOpsError(error)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await copyTextToClipboard(downloadURL)
|
||||
message.success('下载地址已复制')
|
||||
} catch {
|
||||
message.error('复制失败,请在接口返回中手动复制下载地址')
|
||||
}
|
||||
}
|
||||
|
||||
async function copyFormDownloadURL() {
|
||||
if (form.download_url) {
|
||||
await navigator.clipboard.writeText(form.download_url)
|
||||
message.success('下载地址已复制')
|
||||
try {
|
||||
await copyTextToClipboard(form.download_url)
|
||||
message.success('下载地址已复制')
|
||||
} catch {
|
||||
message.error('复制失败,请手动复制下载地址')
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!editingRow.value?.id) return
|
||||
|
||||
@@ -684,3 +684,12 @@ spec:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
volumeMounts:
|
||||
- name: ops-web-nginx-config
|
||||
mountPath: /etc/nginx/http.d/default.conf
|
||||
subPath: default.conf
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: ops-web-nginx-config
|
||||
configMap:
|
||||
name: ops-web-nginx-config
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
set_real_ip_from 127.0.0.1;
|
||||
set_real_ip_from 10.42.0.0/16;
|
||||
set_real_ip_from 10.43.0.0/16;
|
||||
set_real_ip_from 172.16.0.0/12;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
client_max_body_size 600M;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Never cache the SPA entrypoint — chunk hashes change every deploy and a
|
||||
# stale index.html keeps users stuck on 404'd chunks even after reload.
|
||||
location = /index.html {
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||
expires -1;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://ops-api:8090;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 900s;
|
||||
proxy_send_timeout 900s;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
gzip_vary on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml;
|
||||
|
||||
brotli_static on;
|
||||
}
|
||||
@@ -29,6 +29,9 @@ configMapGenerator:
|
||||
- config.yaml=config/config.yaml
|
||||
- prompts.yml=config/prompts.yml
|
||||
- ops-config.yaml=config/ops-config.yaml
|
||||
- name: ops-web-nginx-config
|
||||
files:
|
||||
- default.conf=config/ops-web-nginx.conf
|
||||
|
||||
images:
|
||||
- name: geo-rankly/migrate
|
||||
|
||||
Reference in New Issue
Block a user