Compare commits
1 Commits
main
...
487eb57e28
| Author | SHA1 | Date | |
|---|---|---|---|
| 487eb57e28 |
+60
-16
@@ -26,6 +26,14 @@ func Open(driver, dsn string) (*Store, error) {
|
||||
var err error
|
||||
|
||||
drv = driverName(driver)
|
||||
if drv == "sqlite" {
|
||||
dsn = appendSQLitePragmas(dsn, map[string]string{
|
||||
"busy_timeout": "10000",
|
||||
"journal_mode": "WAL",
|
||||
"foreign_keys": "1",
|
||||
})
|
||||
}
|
||||
fmt.Printf("CONNECTING TO [[[%s]]]\n", dsn)
|
||||
db, err = sql.Open(drv, dsn)
|
||||
if err != nil {
|
||||
goto oops
|
||||
@@ -41,22 +49,6 @@ func Open(driver, dsn string) (*Store, error) {
|
||||
goto oops
|
||||
}
|
||||
|
||||
if drv == "sqlite" {
|
||||
_, err = db.Exec(`PRAGMA busy_timeout = 10000`)
|
||||
if err != nil {
|
||||
goto oops
|
||||
}
|
||||
|
||||
_, err = db.Exec(`PRAGMA journal_mode = WAL`)
|
||||
if err != nil {
|
||||
goto oops
|
||||
}
|
||||
|
||||
_, err = db.Exec(`PRAGMA foreign_keys = 1`)
|
||||
if err != nil {
|
||||
goto oops
|
||||
}
|
||||
}
|
||||
return &Store{DB: db}, nil
|
||||
|
||||
oops:
|
||||
@@ -66,6 +58,58 @@ oops:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// appendSQLitePragmas appends _pragma=name(value) URI parameters to the DSN
|
||||
// for any pragma not already present. A plain file path is promoted to a
|
||||
// file: URI so it can carry query parameters.
|
||||
func appendSQLitePragmas(dsn string, pragmas map[string]string) string {
|
||||
var base string
|
||||
var existing string
|
||||
var sep string
|
||||
var idx int
|
||||
var name string
|
||||
var value string
|
||||
var result string
|
||||
|
||||
if strings.HasPrefix(dsn, "file:") {
|
||||
idx = strings.Index(dsn, "?")
|
||||
if idx < 0 {
|
||||
base = dsn
|
||||
existing = ""
|
||||
} else {
|
||||
base = dsn[:idx]
|
||||
existing = dsn[idx+1:]
|
||||
}
|
||||
} else if strings.Contains(dsn, "?") {
|
||||
idx = strings.Index(dsn, "?")
|
||||
base = dsn[:idx]
|
||||
existing = dsn[idx+1:]
|
||||
} else if dsn != ":memory:" {
|
||||
base = "file:" + dsn
|
||||
existing = ""
|
||||
} else {
|
||||
base = dsn
|
||||
existing = ""
|
||||
}
|
||||
|
||||
result = base
|
||||
if existing != "" {
|
||||
result = base + "?" + existing
|
||||
sep = "&"
|
||||
} else {
|
||||
sep = "?"
|
||||
}
|
||||
|
||||
for name, value = range pragmas {
|
||||
// [NOTE] this existence check isn't accurate but is practically good
|
||||
if strings.Contains(existing, "_pragma="+name+"(") {
|
||||
continue
|
||||
}
|
||||
result = result + sep + "_pragma=" + name + "(" + value + ")"
|
||||
sep = "&"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func driverName(driver string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(driver)) {
|
||||
case "sqlite", "sqlite3":
|
||||
|
||||
@@ -277,6 +277,15 @@ function SessionTerminalPanel(props: SessionPanelProps) {
|
||||
syncedSessionIDsRef.current = syncedSessionIDs
|
||||
}, [syncedSessionIDs])
|
||||
|
||||
useEffect(() => {
|
||||
if (status !== 'pending' && status !== 'connecting') { return }
|
||||
const timer = window.setTimeout(() => {
|
||||
setError('Connection timed out. Click Connect to try again.')
|
||||
setStatus('error')
|
||||
}, 30000)
|
||||
return () => { window.clearTimeout(timer) }
|
||||
}, [status, sessionID])
|
||||
|
||||
useEffect(() => {
|
||||
let animationFrameID: number
|
||||
let settleTimerOne: number
|
||||
@@ -853,6 +862,8 @@ export default function SSHSessionPage() {
|
||||
const [streamWSCheck, setStreamWSCheck] = useState<number>(0)
|
||||
const streamHandlersRef = useRef<Record<string, SessionStreamHandlers>>({})
|
||||
const attachedSessionIDsRef = useRef<Record<string, boolean>>({})
|
||||
const wsReconnectAttemptRef = useRef<number>(0)
|
||||
const wsReconnectTimerRef = useRef<number>(0)
|
||||
const stackedLayout = useMediaQuery(theme.breakpoints.down('lg'))
|
||||
const historyDialogMode: boolean = useMediaQuery(theme.breakpoints.down('md'))
|
||||
const rowCount = useMemo(() => {
|
||||
@@ -1334,6 +1345,7 @@ export default function SSHSessionPage() {
|
||||
streamWSRef.current = ws
|
||||
ws.onopen = () => {
|
||||
let i: number
|
||||
wsReconnectAttemptRef.current = 0
|
||||
attachedSessionIDsRef.current = {}
|
||||
for (i = 0; i < openSessionIDs.length; i += 1) {
|
||||
attachStreamSession(openSessionIDs[i])
|
||||
@@ -1374,6 +1386,12 @@ export default function SSHSessionPage() {
|
||||
handlers = streamHandlersRef.current[ids[i]]
|
||||
if (handlers) handlers.onTransportClosed('SSH workspace stream closed')
|
||||
}
|
||||
// schedule reconnect with exponential backoff; immediate on first attempt
|
||||
const delay: number = Math.min(Math.pow(2, wsReconnectAttemptRef.current + 1) * 1000, 30000)
|
||||
wsReconnectAttemptRef.current += 1
|
||||
wsReconnectTimerRef.current = window.setTimeout(() => {
|
||||
setStreamWSCheck((prev) => prev + 1)
|
||||
}, delay)
|
||||
}
|
||||
ws.onerror = () => {
|
||||
setPageError((prev) => prev || 'SSH workspace stream error')
|
||||
@@ -1383,6 +1401,10 @@ export default function SSHSessionPage() {
|
||||
useEffect(() => {
|
||||
// close the workspace websocket upon unmount
|
||||
return () => {
|
||||
if (wsReconnectTimerRef.current) {
|
||||
window.clearTimeout(wsReconnectTimerRef.current)
|
||||
wsReconnectTimerRef.current = 0
|
||||
}
|
||||
if (streamWSRef.current) {
|
||||
streamWSRef.current.close()
|
||||
streamWSRef.current = null
|
||||
|
||||
Reference in New Issue
Block a user