feat: sing-box Clash template support + Docker CI + frontend scaffold
- Clash→sing-box template conversion engine (singbox_template.go) - Auto-map 40+ Clash rule-providers to sing-box .srs binary rule-sets - Fix sing-box 1.11+/1.12+ migrations (sniff/block/WireGuard/DNS/rule_set) - Fix WireGuard URI '+' parsing bug (rawParamGet) - Add REJECT block outbound for selector group references - Skip rules referencing rule-sets with no sing-box equivalent - Verified: sing-box run succeeds with ACL4SSR template (16 rule-sets loaded) - Add Dockerfile (multi-stage Go build) - Add GitHub Actions workflow (multi-arch: amd64+arm64, push to ghcr.io) - Add frontend scaffold (Vue3 + Vite + Tailwind)
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import axios from 'axios'
|
||||
import { getEffectiveToken, clearToken } from '@/utils/token'
|
||||
|
||||
// Read runtime config (injected by config.js, defaults to same-origin)
|
||||
const apiBaseUrl = (window.SUB_STORE_CONFIG?.apiBaseUrl) || ''
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: apiBaseUrl + '/api',
|
||||
timeout: 30000,
|
||||
})
|
||||
|
||||
// Request interceptor — attach admin token
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = getEffectiveToken()
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
// Response interceptor — unwrap data, handle errors
|
||||
api.interceptors.response.use(
|
||||
(response) => response.data,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token might be stale — clear it and let the router guard handle redirect
|
||||
clearToken()
|
||||
}
|
||||
const msg = error.response?.data?.error?.message || error.message || 'Request failed'
|
||||
return Promise.reject(new Error(msg))
|
||||
}
|
||||
)
|
||||
|
||||
// --- API methods ---
|
||||
|
||||
// Env
|
||||
export const getEnv = () => api.get('/env')
|
||||
export const getScripts = () => api.get('/scripts')
|
||||
|
||||
// Settings
|
||||
export const getSettings = () => api.get('/settings')
|
||||
export const updateSettings = (data) => api.patch('/settings', data)
|
||||
|
||||
// Storage
|
||||
export const exportStorage = () => api.get('/storage')
|
||||
export const importStorage = (data) => api.post('/storage', data)
|
||||
|
||||
// Sources
|
||||
export const listSources = () => api.get('/sources')
|
||||
export const createSource = (data) => api.post('/sources', data)
|
||||
export const getSource = (name) => api.get(`/sources/${name}`)
|
||||
export const updateSource = (name, data) => api.patch(`/sources/${name}`, data)
|
||||
export const deleteSource = (name) => api.delete(`/sources/${name}`)
|
||||
export const sortSources = (ids) => api.put('/sources', ids)
|
||||
|
||||
// Collections
|
||||
export const listCollections = () => api.get('/collections')
|
||||
export const createCollection = (data) => api.post('/collections', data)
|
||||
export const getCollection = (name) => api.get(`/collections/${name}`)
|
||||
export const updateCollection = (name, data) => api.patch(`/collections/${name}`, data)
|
||||
export const deleteCollection = (name) => api.delete(`/collections/${name}`)
|
||||
export const sortCollections = (ids) => api.put('/collections', ids)
|
||||
|
||||
// Templates
|
||||
export const listTemplates = () => api.get('/templates')
|
||||
export const createTemplate = (data) => api.post('/templates', data)
|
||||
export const getTemplate = (name) => api.get(`/templates/${name}`)
|
||||
export const updateTemplate = (name, data) => api.patch(`/templates/${name}`, data)
|
||||
export const deleteTemplate = (name) => api.delete(`/templates/${name}`)
|
||||
|
||||
// Shares
|
||||
export const listShares = () => api.get('/shares')
|
||||
export const createShare = (data) => api.post('/shares', data)
|
||||
export const updateShare = (id, data) => api.patch(`/shares/${id}`, data)
|
||||
export const deleteShare = (id) => api.delete(`/shares/${id}`)
|
||||
|
||||
// Recycle bin
|
||||
export const listRecycleBin = () => api.get('/recycle-bin')
|
||||
export const deleteRecycleEntry = (id) => api.delete(`/recycle-bin/${id}`)
|
||||
export const restoreRecycleEntry = (id) => api.post(`/recycle-bin/${id}/restore`)
|
||||
|
||||
// Preview
|
||||
export const previewSource = (data) => api.post('/preview/source', data)
|
||||
export const previewCollection = (data) => api.post('/preview/collection', data)
|
||||
|
||||
// Download links
|
||||
export const getLinkSource = (name, target) => api.get(`/link/source/${name}`, { params: target ? { target } : {} })
|
||||
export const getLinkCollection = (name, target) => api.get(`/link/collection/${name}`, { params: target ? { target } : {} })
|
||||
|
||||
// Flow info
|
||||
export const getFlowInfo = (name) => api.get(`/source/flow/${name}`)
|
||||
|
||||
// Tools
|
||||
export const parseProxy = (data) => api.post('/proxy/parse', data)
|
||||
export const parseRule = (data) => api.post('/rule/parse', data)
|
||||
export const getNodeInfo = (server) => api.post('/utils/node-info', { server })
|
||||
|
||||
export default api
|
||||
Reference in New Issue
Block a user