Add frontend #1
@@ -110,6 +110,9 @@ func validateIncomingSite(site *models.Site) error {
|
|||||||
if site.NotFoundFile == "" {
|
if site.NotFoundFile == "" {
|
||||||
site.NotFoundFile = "404.html"
|
site.NotFoundFile = "404.html"
|
||||||
}
|
}
|
||||||
|
if site.IndexFile == "" {
|
||||||
|
site.IndexFile = "index.html"
|
||||||
|
}
|
||||||
if site.ForwardRules == nil {
|
if site.ForwardRules == nil {
|
||||||
site.ForwardRules = []models.ForwardRule{}
|
site.ForwardRules = []models.ForwardRule{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func NewStaticHandler(storagePath string, siteRepo repository.SiteRepository) fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if urlPath == "/" || urlPath == "." {
|
if urlPath == "/" || urlPath == "." {
|
||||||
urlPath = "/index.html"
|
urlPath = "/" + site.IndexFile
|
||||||
}
|
}
|
||||||
|
|
||||||
basePath := filepath.Join(storagePath, site.ID)
|
basePath := filepath.Join(storagePath, site.ID)
|
||||||
@@ -71,7 +71,7 @@ func NewStaticHandler(storagePath string, siteRepo repository.SiteRepository) fi
|
|||||||
info, err := os.Stat(filePath)
|
info, err := os.Stat(filePath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
indexPath := filepath.Join(filePath, "index.html")
|
indexPath := filepath.Join(filePath, site.IndexFile)
|
||||||
if _, err := os.Stat(indexPath); err == nil {
|
if _, err := os.Stat(indexPath); err == nil {
|
||||||
return c.SendFile(indexPath)
|
return c.SendFile(indexPath)
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ func NewStaticHandler(storagePath string, siteRepo repository.SiteRepository) fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if site.Spa {
|
if site.Spa {
|
||||||
indexPath := filepath.Join(basePath, "index.html")
|
indexPath := filepath.Join(basePath, site.IndexFile)
|
||||||
if _, err := os.Stat(indexPath); err == nil {
|
if _, err := os.Stat(indexPath); err == nil {
|
||||||
return c.SendFile(indexPath)
|
return c.SendFile(indexPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ type Site struct {
|
|||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Spa bool `json:"spa"`
|
Spa bool `json:"spa"`
|
||||||
NotFoundFile string `json:"not_found_file"`
|
NotFoundFile string `json:"not_found_file"`
|
||||||
|
IndexFile string `json:"index_file"`
|
||||||
|
TrailingSlash *bool `json:"trailing_slash"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
ForwardRules []ForwardRule `json:"forward_rules"`
|
ForwardRules []ForwardRule `json:"forward_rules"`
|
||||||
CustomHeaders []CustomHeaders `json:"custom_headers"`
|
CustomHeaders []CustomHeaders `json:"custom_headers"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ CREATE TABLE IF NOT EXISTS sites (
|
|||||||
deploy_token TEXT NOT NULL,
|
deploy_token TEXT NOT NULL,
|
||||||
enabled INTEGER NOT NULL DEFAULT 1,
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
spa INTEGER NOT NULL DEFAULT 0,
|
spa INTEGER NOT NULL DEFAULT 0,
|
||||||
not_found_file TEXT NOT NULL DEFAULT '404.html'
|
not_found_file TEXT NOT NULL DEFAULT '404.html',
|
||||||
|
index_file TEXT NOT NULL DEFAULT 'index.html',
|
||||||
|
trailing_slash INTEGER DEFAULT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS forward_rules (
|
CREATE TABLE IF NOT EXISTS forward_rules (
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ var _ repository.SiteRepository = (*SQLiteSiteRepository)(nil)
|
|||||||
|
|
||||||
func (r *SQLiteSiteRepository) GetSite(id string) (*models.Site, error) {
|
func (r *SQLiteSiteRepository) GetSite(id string) (*models.Site, error) {
|
||||||
row := r.db.QueryRow(`
|
row := r.db.QueryRow(`
|
||||||
SELECT id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file
|
SELECT id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file, index_file, trailing_slash, created_at
|
||||||
FROM sites WHERE id = ?`, id)
|
FROM sites WHERE id = ?`, id)
|
||||||
|
|
||||||
s, err := scanSite(row)
|
s, err := scanSite(row)
|
||||||
@@ -41,7 +41,7 @@ func (r *SQLiteSiteRepository) GetSite(id string) (*models.Site, error) {
|
|||||||
|
|
||||||
func (r *SQLiteSiteRepository) GetSiteByDomain(domain string) (*models.Site, error) {
|
func (r *SQLiteSiteRepository) GetSiteByDomain(domain string) (*models.Site, error) {
|
||||||
row := r.db.QueryRow(`
|
row := r.db.QueryRow(`
|
||||||
SELECT id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file
|
SELECT id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file, index_file, trailing_slash, created_at
|
||||||
FROM sites WHERE domain = ?`, domain)
|
FROM sites WHERE domain = ?`, domain)
|
||||||
|
|
||||||
s, err := scanSite(row)
|
s, err := scanSite(row)
|
||||||
@@ -60,7 +60,7 @@ func (r *SQLiteSiteRepository) GetSiteByDomain(domain string) (*models.Site, err
|
|||||||
|
|
||||||
func (r *SQLiteSiteRepository) ListSites() ([]models.Site, error) {
|
func (r *SQLiteSiteRepository) ListSites() ([]models.Site, error) {
|
||||||
rows, err := r.db.Query(`
|
rows, err := r.db.Query(`
|
||||||
SELECT id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file
|
SELECT id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file, index_file, trailing_slash, created_at
|
||||||
FROM sites`)
|
FROM sites`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list sites: %w", err)
|
return nil, fmt.Errorf("list sites: %w", err)
|
||||||
@@ -98,10 +98,10 @@ func (r *SQLiteSiteRepository) CreateSite(s *models.Site) error {
|
|||||||
s.ID = uuid.NewString()
|
s.ID = uuid.NewString()
|
||||||
|
|
||||||
_, err = tx.Exec(`
|
_, err = tx.Exec(`
|
||||||
INSERT INTO sites (id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file)
|
INSERT INTO sites (id, name, git_server, owner, repository, branch, domain, deploy_token, spa, enabled, not_found_file, index_file, trailing_slash)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
s.ID, s.Name, s.GitServer, s.Owner, s.Repository, s.Branch,
|
s.ID, s.Name, s.GitServer, s.Owner, s.Repository, s.Branch,
|
||||||
s.Domain, s.DeployToken, s.Spa, s.Enabled, s.NotFoundFile,
|
s.Domain, s.DeployToken, s.Spa, s.Enabled, s.NotFoundFile, s.IndexFile, s.TrailingSlash,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create site insert: %w", err)
|
return fmt.Errorf("create site insert: %w", err)
|
||||||
@@ -131,9 +131,10 @@ func (r *SQLiteSiteRepository) UpdateSite(s *models.Site) error {
|
|||||||
|
|
||||||
_, err = tx.Exec(`
|
_, err = tx.Exec(`
|
||||||
UPDATE sites SET name=?, git_server=?, owner=?, repository=?, branch=?, domain=?,
|
UPDATE sites SET name=?, git_server=?, owner=?, repository=?, branch=?, domain=?,
|
||||||
deploy_token=?, spa=?, enabled=?, not_found_file=? WHERE id=?`,
|
deploy_token=?, spa=?, enabled=?, not_found_file=?, index_file=?, trailing_slash=? WHERE id=?`,
|
||||||
s.Name, s.GitServer, s.Owner, s.Repository, s.Branch, s.Domain,
|
s.Name, s.GitServer, s.Owner, s.Repository, s.Branch, s.Domain,
|
||||||
s.DeployToken, s.Spa, s.Enabled, s.NotFoundFile, s.ID,
|
s.DeployToken, s.Spa, s.Enabled, s.NotFoundFile, s.IndexFile, s.TrailingSlash,
|
||||||
|
s.ID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("update site: %w", err)
|
return fmt.Errorf("update site: %w", err)
|
||||||
@@ -364,6 +365,7 @@ func scanSite(s scanner) (*models.Site, error) {
|
|||||||
err := s.Scan(
|
err := s.Scan(
|
||||||
&site.ID, &site.Name, &site.GitServer, &site.Owner, &site.Repository,
|
&site.ID, &site.Name, &site.GitServer, &site.Owner, &site.Repository,
|
||||||
&site.Branch, &site.Domain, &site.DeployToken, &site.Spa, &enabled, &site.NotFoundFile,
|
&site.Branch, &site.Domain, &site.DeployToken, &site.Spa, &enabled, &site.NotFoundFile,
|
||||||
|
&site.IndexFile, &site.TrailingSlash, &site.CreatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export interface Site {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
spa: boolean;
|
spa: boolean;
|
||||||
not_found_file: string;
|
not_found_file: string;
|
||||||
last_deployed: string;
|
index_file: string;
|
||||||
|
trailing_slash: boolean | null;
|
||||||
|
created_at: string;
|
||||||
forward_rules: ForwardRule[];
|
forward_rules: ForwardRule[];
|
||||||
custom_headers: CustomHeaders[];
|
custom_headers: CustomHeaders[];
|
||||||
}
|
}
|
||||||
@@ -45,6 +47,8 @@ export interface CreateSiteRequest {
|
|||||||
domain: string;
|
domain: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
spa: boolean;
|
spa: boolean;
|
||||||
|
not_found_file: string;
|
||||||
|
index_file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetAllSitesResponse {
|
export interface GetAllSitesResponse {
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ const NewSite = () => {
|
|||||||
domain,
|
domain,
|
||||||
spa,
|
spa,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
not_found_file: '',
|
||||||
|
index_file: '',
|
||||||
});
|
});
|
||||||
console.log('Created site:', data);
|
console.log('Created site:', data);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const OverviewTab = ({ site }: { site: Site }) => (
|
|||||||
<Separator />
|
<Separator />
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted-foreground">Last Deployed</span>
|
<span className="text-muted-foreground">Last Deployed</span>
|
||||||
<span>{formatDate(site.last_deployed)}</span>
|
<span>{formatDate('2024-01-01T12:00:00Z')}</span>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ const SettingsTab = ({ site }: { site: Site }) => {
|
|||||||
const [domain, setDomain] = useState(site.domain);
|
const [domain, setDomain] = useState(site.domain);
|
||||||
const [branch, setBranch] = useState(site.branch);
|
const [branch, setBranch] = useState(site.branch);
|
||||||
const [spa, setSpa] = useState(site.spa);
|
const [spa, setSpa] = useState(site.spa);
|
||||||
|
const [notFoundFile, setNotFoundFile] = useState(site.not_found_file);
|
||||||
|
const [indexFile, setIndexFile] = useState(site.index_file);
|
||||||
|
|
||||||
const [confirmName, setConfirmName] = useState('');
|
const [confirmName, setConfirmName] = useState('');
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
@@ -44,6 +47,8 @@ const SettingsTab = ({ site }: { site: Site }) => {
|
|||||||
domain,
|
domain,
|
||||||
branch,
|
branch,
|
||||||
spa,
|
spa,
|
||||||
|
not_found_file: notFoundFile,
|
||||||
|
index_file: indexFile,
|
||||||
enabled: site.enabled,
|
enabled: site.enabled,
|
||||||
git_server: site.git_server,
|
git_server: site.git_server,
|
||||||
owner: site.owner,
|
owner: site.owner,
|
||||||
@@ -74,6 +79,7 @@ const SettingsTab = ({ site }: { site: Site }) => {
|
|||||||
<Input
|
<Input
|
||||||
id="settings-name"
|
id="settings-name"
|
||||||
value={name}
|
value={name}
|
||||||
|
placeholder="e.g. My Awesome Site"
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,6 +88,7 @@ const SettingsTab = ({ site }: { site: Site }) => {
|
|||||||
<Input
|
<Input
|
||||||
id="settings-domain"
|
id="settings-domain"
|
||||||
value={domain}
|
value={domain}
|
||||||
|
placeholder="e.g. www.example.com"
|
||||||
onChange={(e) => setDomain(e.target.value)}
|
onChange={(e) => setDomain(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,9 +97,30 @@ const SettingsTab = ({ site }: { site: Site }) => {
|
|||||||
<Input
|
<Input
|
||||||
id="settings-branch"
|
id="settings-branch"
|
||||||
value={branch}
|
value={branch}
|
||||||
|
placeholder="e.g. gh-pages"
|
||||||
onChange={(e) => setBranch(e.target.value)}
|
onChange={(e) => setBranch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="space-y-2 grow">
|
||||||
|
<Label htmlFor="settings-index-file">Custom Index File</Label>
|
||||||
|
<Input
|
||||||
|
id="settings-index-file"
|
||||||
|
value={indexFile}
|
||||||
|
placeholder="e.g. index.html"
|
||||||
|
onChange={(e) => setIndexFile(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 grow">
|
||||||
|
<Label htmlFor="settings-not-found">Custom 404 Page</Label>
|
||||||
|
<Input
|
||||||
|
id="settings-not-found"
|
||||||
|
value={notFoundFile}
|
||||||
|
placeholder="e.g. 404.html"
|
||||||
|
onChange={(e) => setNotFoundFile(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex items-center justify-between rounded-lg border p-4">
|
<div className="flex items-center justify-between rounded-lg border p-4">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="settings-spa">Single Page Application</Label>
|
<Label htmlFor="settings-spa">Single Page Application</Label>
|
||||||
|
|||||||
Reference in New Issue
Block a user