Compare commits
No commits in common. "21e300df90d76d46496ca8f3a2e64d0888c3b9e4" and "946cdb17948419c2ee9561480521e7a183c0896a" have entirely different histories.
21e300df90
...
946cdb1794
|
|
@ -15,6 +15,26 @@ const TOES_URL = process.env.TOES_URL!
|
||||||
const REPOS_DIR = resolve(DATA_ROOT, 'repos')
|
const REPOS_DIR = resolve(DATA_ROOT, 'repos')
|
||||||
const VISIBILITY_PATH = join(DATA_DIR, 'visibility.json')
|
const VISIBILITY_PATH = join(DATA_DIR, 'visibility.json')
|
||||||
|
|
||||||
|
const TOGGLE_SCRIPT = `
|
||||||
|
function toggleVisibility(btn) {
|
||||||
|
var repo = btn.dataset.repo;
|
||||||
|
var current = btn.dataset.visibility;
|
||||||
|
var next = current === 'public' ? 'private' : 'public';
|
||||||
|
btn.dataset.visibility = next;
|
||||||
|
btn.textContent = next;
|
||||||
|
btn.classList.toggle('public', next === 'public');
|
||||||
|
fetch('/api/visibility/' + encodeURIComponent(repo), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ visibility: next })
|
||||||
|
}).catch(function() {
|
||||||
|
btn.dataset.visibility = current;
|
||||||
|
btn.textContent = current;
|
||||||
|
btn.classList.toggle('public', current === 'public');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const app = new Hype({ prettyHTML: false, layout: false })
|
const app = new Hype({ prettyHTML: false, layout: false })
|
||||||
const deployLocks = new Map<string, Promise<void>>()
|
const deployLocks = new Map<string, Promise<void>>()
|
||||||
|
|
||||||
|
|
@ -93,31 +113,6 @@ const RepoName = define('RepoName', {
|
||||||
color: theme('colors-text'),
|
color: theme('colors-text'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const Tab = define('Tab', {
|
|
||||||
base: 'button',
|
|
||||||
padding: '6px 0',
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
borderBottom: '2px solid transparent',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '14px',
|
|
||||||
color: theme('colors-textMuted'),
|
|
||||||
states: {
|
|
||||||
':hover': { color: theme('colors-text') },
|
|
||||||
'.active': {
|
|
||||||
color: theme('colors-text'),
|
|
||||||
borderBottomColor: theme('colors-primary'),
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const TabBar = define('TabBar', {
|
|
||||||
display: 'flex',
|
|
||||||
gap: '24px',
|
|
||||||
marginBottom: '20px',
|
|
||||||
})
|
|
||||||
|
|
||||||
const Toggle = define('Toggle', {
|
const Toggle = define('Toggle', {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
|
|
@ -161,7 +156,7 @@ interface LayoutProps {
|
||||||
interface RepoListPageProps {
|
interface RepoListPageProps {
|
||||||
baseUrl: string
|
baseUrl: string
|
||||||
external: boolean
|
external: boolean
|
||||||
repos: Array<{ name: string; commits: boolean; branch: string; visibility: Visibility; tool: boolean }>
|
repos: Array<{ name: string; commits: boolean; branch: string; visibility: Visibility }>
|
||||||
tunnelUrl?: string
|
tunnelUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -549,56 +544,7 @@ function AppRepo({ appName, baseUrl, branch, exists, commits }: AppRepoProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RepoListItems({ baseUrl, external, repos, tunnelUrl }: {
|
|
||||||
baseUrl: string
|
|
||||||
external: boolean
|
|
||||||
repos: RepoListPageProps['repos']
|
|
||||||
tunnelUrl?: string
|
|
||||||
}) {
|
|
||||||
if (repos.length === 0) {
|
|
||||||
return <HelpText>No repositories yet.</HelpText>
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<RepoList>
|
|
||||||
{repos.map(({ name, commits, branch, visibility }) => (
|
|
||||||
<RepoItem>
|
|
||||||
<div>
|
|
||||||
<RepoName>{name}</RepoName>
|
|
||||||
<HelpText style="margin: 4px 0 0; font-size: 12px">
|
|
||||||
git clone {baseUrl}/{name}
|
|
||||||
</HelpText>
|
|
||||||
{!external && tunnelUrl && visibility === 'public' && (
|
|
||||||
<HelpText style="margin: 2px 0 0; font-size: 12px">
|
|
||||||
git clone {tunnelUrl}/{name}
|
|
||||||
</HelpText>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; gap: 8px; align-items: center">
|
|
||||||
{!external && (
|
|
||||||
<Toggle
|
|
||||||
class={visibility === 'public' ? 'public' : ''}
|
|
||||||
data-repo={name}
|
|
||||||
data-visibility={visibility}
|
|
||||||
onclick="toggleVisibility(this)"
|
|
||||||
>
|
|
||||||
{visibility === 'public' ? 'public' : 'private'}
|
|
||||||
</Toggle>
|
|
||||||
)}
|
|
||||||
<Badge>{branch}</Badge>
|
|
||||||
{commits
|
|
||||||
? <Badge style={`color: ${theme('colors-statusRunning')}`}>deployed</Badge>
|
|
||||||
: <Badge>empty</Badge>}
|
|
||||||
</div>
|
|
||||||
</RepoItem>
|
|
||||||
))}
|
|
||||||
</RepoList>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function RepoListPage({ baseUrl, external, repos, tunnelUrl }: RepoListPageProps) {
|
function RepoListPage({ baseUrl, external, repos, tunnelUrl }: RepoListPageProps) {
|
||||||
const appRepos = repos.filter(r => !r.tool)
|
|
||||||
const toolRepos = repos.filter(r => r.tool)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout title="Git">
|
<Layout title="Git">
|
||||||
{!external && (
|
{!external && (
|
||||||
|
|
@ -620,31 +566,43 @@ function RepoListPage({ baseUrl, external, repos, tunnelUrl }: RepoListPageProps
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{repos.length > 0 && appRepos.length > 0 && toolRepos.length > 0 && (
|
{repos.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Heading>Repositories</Heading>
|
<Heading>Repositories</Heading>
|
||||||
<TabBar>
|
<RepoList>
|
||||||
<Tab class="active" data-tab="tab-apps" onclick="switchTab(this)">Apps</Tab>
|
{repos.map(({ name, commits, branch, visibility }) => (
|
||||||
<Tab data-tab="tab-tools" onclick="switchTab(this)">Tools</Tab>
|
<RepoItem>
|
||||||
</TabBar>
|
<div>
|
||||||
<div>
|
<RepoName>{name}</RepoName>
|
||||||
<div id="tab-apps">
|
<HelpText style="margin: 4px 0 0; font-size: 12px">
|
||||||
<RepoListItems baseUrl={baseUrl} external={external} repos={appRepos} tunnelUrl={tunnelUrl} />
|
git clone {baseUrl}/{name}
|
||||||
</div>
|
</HelpText>
|
||||||
<div id="tab-tools" style="display: none">
|
{!external && tunnelUrl && visibility === 'public' && (
|
||||||
<RepoListItems baseUrl={baseUrl} external={external} repos={toolRepos} tunnelUrl={tunnelUrl} />
|
<HelpText style="margin: 2px 0 0; font-size: 12px">
|
||||||
</div>
|
git clone {tunnelUrl}/{name}
|
||||||
</div>
|
</HelpText>
|
||||||
{!external && <script src="/client/toggle.js" />}
|
)}
|
||||||
<script src="/client/tabs.js" />
|
</div>
|
||||||
</>
|
<div style="display: flex; gap: 8px; align-items: center">
|
||||||
)}
|
{!external && (
|
||||||
|
<Toggle
|
||||||
{repos.length > 0 && (appRepos.length === 0 || toolRepos.length === 0) && (
|
class={visibility === 'public' ? 'public' : ''}
|
||||||
<>
|
data-repo={name}
|
||||||
<Heading>Repositories</Heading>
|
data-visibility={visibility}
|
||||||
<RepoListItems baseUrl={baseUrl} external={external} repos={repos} tunnelUrl={tunnelUrl} />
|
onclick="toggleVisibility(this)"
|
||||||
{!external && <script src="/client/toggle.js" />}
|
>
|
||||||
|
{visibility === 'public' ? 'public' : 'private'}
|
||||||
|
</Toggle>
|
||||||
|
)}
|
||||||
|
<Badge>{branch}</Badge>
|
||||||
|
{commits
|
||||||
|
? <Badge style={`color: ${theme('colors-statusRunning')}`}>deployed</Badge>
|
||||||
|
: <Badge>empty</Badge>}
|
||||||
|
</div>
|
||||||
|
</RepoItem>
|
||||||
|
))}
|
||||||
|
</RepoList>
|
||||||
|
{!external && <script src="/toggle.js" />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -672,6 +630,9 @@ app.get('/styles.css', c =>
|
||||||
c.text(baseStyles + stylesToCSS(), 200, { 'Content-Type': 'text/css; charset=utf-8' }),
|
c.text(baseStyles + stylesToCSS(), 200, { 'Content-Type': 'text/css; charset=utf-8' }),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.get('/toggle.js', c =>
|
||||||
|
c.text(TOGGLE_SCRIPT, 200, { 'Content-Type': 'application/javascript; charset=utf-8' }),
|
||||||
|
)
|
||||||
|
|
||||||
// GET /:repo[.git]/info/refs?service=git-upload-pack|git-receive-pack
|
// GET /:repo[.git]/info/refs?service=git-upload-pack|git-receive-pack
|
||||||
app.on('GET', ['/:repo{.+\\.git}/info/refs', '/:repo/info/refs'], async c => {
|
app.on('GET', ['/:repo{.+\\.git}/info/refs', '/:repo/info/refs'], async c => {
|
||||||
|
|
@ -819,19 +780,6 @@ app.get('/', async c => {
|
||||||
|
|
||||||
// No app selected — show all repos
|
// No app selected — show all repos
|
||||||
const repos = await listRepos()
|
const repos = await listRepos()
|
||||||
|
|
||||||
// Fetch all apps to determine which repos are tools
|
|
||||||
let toolSet = new Set<string>()
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${TOES_URL}/api/apps`)
|
|
||||||
if (res.ok) {
|
|
||||||
const apps = await res.json() as Array<{ name: string; tool?: boolean | string }>
|
|
||||||
for (const a of apps) {
|
|
||||||
if (a.tool) toolSet.add(a.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
const repoData = await Promise.all(repos.map(async name => {
|
const repoData = await Promise.all(repos.map(async name => {
|
||||||
const bare = repoPath(name)
|
const bare = repoPath(name)
|
||||||
const [commits, branch, visibility] = await Promise.all([
|
const [commits, branch, visibility] = await Promise.all([
|
||||||
|
|
@ -839,7 +787,7 @@ app.get('/', async c => {
|
||||||
getDefaultBranch(bare),
|
getDefaultBranch(bare),
|
||||||
getVisibility(name),
|
getVisibility(name),
|
||||||
])
|
])
|
||||||
return { name, commits, branch, visibility, tool: toolSet.has(name) }
|
return { name, commits, branch, visibility }
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Hide private repos from external (sneaker) requests
|
// Hide private repos from external (sneaker) requests
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
function switchTab(btn: HTMLButtonElement) {
|
|
||||||
const tabs = btn.parentElement!.querySelectorAll('button')
|
|
||||||
for (const tab of tabs) tab.classList.remove('active')
|
|
||||||
btn.classList.add('active')
|
|
||||||
|
|
||||||
const panels = btn.parentElement!.nextElementSibling!.children
|
|
||||||
for (const panel of panels) (panel as HTMLElement).style.display = 'none'
|
|
||||||
|
|
||||||
const target = document.getElementById(btn.dataset.tab!)
|
|
||||||
if (target) target.style.display = 'block'
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(window, { switchTab })
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
function toggleVisibility(btn: HTMLButtonElement) {
|
|
||||||
const repo = btn.dataset.repo!
|
|
||||||
const current = btn.dataset.visibility!
|
|
||||||
const next = current === 'public' ? 'private' : 'public'
|
|
||||||
btn.dataset.visibility = next
|
|
||||||
btn.textContent = next
|
|
||||||
btn.classList.toggle('public', next === 'public')
|
|
||||||
fetch('/api/visibility/' + encodeURIComponent(repo), {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ visibility: next }),
|
|
||||||
}).catch(() => {
|
|
||||||
btn.dataset.visibility = current
|
|
||||||
btn.textContent = current
|
|
||||||
btn.classList.toggle('public', current === 'public')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(window, { toggleVisibility })
|
|
||||||
Loading…
Reference in New Issue
Block a user