mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-29 11:48:56 +03:00
Move install pages out of main macaron routes (#13195)
* Move install pages out of main macaron loop Signed-off-by: Andrew Thornton <art27@cantab.net> * Update templates/post-install.tmpl Co-authored-by: Lauris BH <lauris@nix.lv> * remove prefetch Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
parent
3ddf3f93d6
commit
2f1353a2f3
10 changed files with 234 additions and 115 deletions
73
cmd/web.go
73
cmd/web.go
|
@ -19,6 +19,8 @@ import (
|
||||||
"code.gitea.io/gitea/routers"
|
"code.gitea.io/gitea/routers"
|
||||||
"code.gitea.io/gitea/routers/routes"
|
"code.gitea.io/gitea/routers/routes"
|
||||||
|
|
||||||
|
"gitea.com/macaron/macaron"
|
||||||
|
|
||||||
context2 "github.com/gorilla/context"
|
context2 "github.com/gorilla/context"
|
||||||
"github.com/unknwon/com"
|
"github.com/unknwon/com"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
@ -114,6 +116,39 @@ func runWeb(ctx *cli.Context) error {
|
||||||
setting.WritePIDFile = true
|
setting.WritePIDFile = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag for port number in case first time run conflict.
|
||||||
|
if ctx.IsSet("port") {
|
||||||
|
if err := setPort(ctx.String("port")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform pre-initialization
|
||||||
|
needsInstall := routers.PreInstallInit(graceful.GetManager().HammerContext())
|
||||||
|
if needsInstall {
|
||||||
|
m := routes.NewMacaron()
|
||||||
|
routes.RegisterInstallRoute(m)
|
||||||
|
err := listen(m, false)
|
||||||
|
select {
|
||||||
|
case <-graceful.GetManager().IsShutdown():
|
||||||
|
<-graceful.GetManager().Done()
|
||||||
|
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||||
|
log.Close()
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NoInstallListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.EnablePprof {
|
||||||
|
go func() {
|
||||||
|
log.Info("Starting pprof server on localhost:6060")
|
||||||
|
log.Info("%v", http.ListenAndServe("localhost:6060", nil))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Global init")
|
||||||
// Perform global initialization
|
// Perform global initialization
|
||||||
routers.GlobalInit(graceful.GetManager().HammerContext())
|
routers.GlobalInit(graceful.GetManager().HammerContext())
|
||||||
|
|
||||||
|
@ -121,10 +156,16 @@ func runWeb(ctx *cli.Context) error {
|
||||||
m := routes.NewMacaron()
|
m := routes.NewMacaron()
|
||||||
routes.RegisterRoutes(m)
|
routes.RegisterRoutes(m)
|
||||||
|
|
||||||
// Flag for port number in case first time run conflict.
|
err := listen(m, true)
|
||||||
if ctx.IsSet("port") {
|
<-graceful.GetManager().Done()
|
||||||
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1)
|
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||||
setting.HTTPPort = ctx.String("port")
|
log.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPort(port string) error {
|
||||||
|
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1)
|
||||||
|
setting.HTTPPort = port
|
||||||
|
|
||||||
switch setting.Protocol {
|
switch setting.Protocol {
|
||||||
case setting.UnixSocket:
|
case setting.UnixSocket:
|
||||||
|
@ -154,8 +195,10 @@ func runWeb(ctx *cli.Context) error {
|
||||||
return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
|
return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listen(m *macaron.Macaron, handleRedirector bool) error {
|
||||||
listenAddr := setting.HTTPAddr
|
listenAddr := setting.HTTPAddr
|
||||||
if setting.Protocol != setting.UnixSocket && setting.Protocol != setting.FCGIUnix {
|
if setting.Protocol != setting.UnixSocket && setting.Protocol != setting.FCGIUnix {
|
||||||
listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort)
|
listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort)
|
||||||
|
@ -166,37 +209,40 @@ func runWeb(ctx *cli.Context) error {
|
||||||
log.Info("LFS server enabled")
|
log.Info("LFS server enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.EnablePprof {
|
|
||||||
go func() {
|
|
||||||
log.Info("Starting pprof server on localhost:6060")
|
|
||||||
log.Info("%v", http.ListenAndServe("localhost:6060", nil))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch setting.Protocol {
|
switch setting.Protocol {
|
||||||
case setting.HTTP:
|
case setting.HTTP:
|
||||||
|
if handleRedirector {
|
||||||
NoHTTPRedirector()
|
NoHTTPRedirector()
|
||||||
|
}
|
||||||
err = runHTTP("tcp", listenAddr, context2.ClearHandler(m))
|
err = runHTTP("tcp", listenAddr, context2.ClearHandler(m))
|
||||||
case setting.HTTPS:
|
case setting.HTTPS:
|
||||||
if setting.EnableLetsEncrypt {
|
if setting.EnableLetsEncrypt {
|
||||||
err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m))
|
err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if handleRedirector {
|
||||||
if setting.RedirectOtherPort {
|
if setting.RedirectOtherPort {
|
||||||
go runHTTPRedirector()
|
go runHTTPRedirector()
|
||||||
} else {
|
} else {
|
||||||
NoHTTPRedirector()
|
NoHTTPRedirector()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
|
err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
|
||||||
case setting.FCGI:
|
case setting.FCGI:
|
||||||
|
if handleRedirector {
|
||||||
NoHTTPRedirector()
|
NoHTTPRedirector()
|
||||||
|
}
|
||||||
err = runFCGI("tcp", listenAddr, context2.ClearHandler(m))
|
err = runFCGI("tcp", listenAddr, context2.ClearHandler(m))
|
||||||
case setting.UnixSocket:
|
case setting.UnixSocket:
|
||||||
|
if handleRedirector {
|
||||||
NoHTTPRedirector()
|
NoHTTPRedirector()
|
||||||
|
}
|
||||||
err = runHTTP("unix", listenAddr, context2.ClearHandler(m))
|
err = runHTTP("unix", listenAddr, context2.ClearHandler(m))
|
||||||
case setting.FCGIUnix:
|
case setting.FCGIUnix:
|
||||||
|
if handleRedirector {
|
||||||
NoHTTPRedirector()
|
NoHTTPRedirector()
|
||||||
|
}
|
||||||
err = runFCGI("unix", listenAddr, context2.ClearHandler(m))
|
err = runFCGI("unix", listenAddr, context2.ClearHandler(m))
|
||||||
default:
|
default:
|
||||||
log.Fatal("Invalid protocol: %s", setting.Protocol)
|
log.Fatal("Invalid protocol: %s", setting.Protocol)
|
||||||
|
@ -206,8 +252,5 @@ func runWeb(ctx *cli.Context) error {
|
||||||
log.Critical("Failed to start server: %v", err)
|
log.Critical("Failed to start server: %v", err)
|
||||||
}
|
}
|
||||||
log.Info("HTTP Listener: %s Closed", listenAddr)
|
log.Info("HTTP Listener: %s Closed", listenAddr)
|
||||||
<-graceful.GetManager().Done()
|
return err
|
||||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
|
||||||
log.Close()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ func NoMainListener() {
|
||||||
graceful.GetManager().InformCleanup()
|
graceful.GetManager().InformCleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener
|
||||||
|
// for our install HTTP/HTTPS service
|
||||||
|
func NoInstallListener() {
|
||||||
|
graceful.GetManager().InformCleanup()
|
||||||
|
}
|
||||||
|
|
||||||
func runFCGI(network, listenAddr string, m http.Handler) error {
|
func runFCGI(network, listenAddr string, m http.Handler) error {
|
||||||
// This needs to handle stdin as fcgi point
|
// This needs to handle stdin as fcgi point
|
||||||
fcgiServer := graceful.NewServer(network, listenAddr)
|
fcgiServer := graceful.NewServer(network, listenAddr)
|
||||||
|
|
|
@ -26,12 +26,6 @@ type ToggleOptions struct {
|
||||||
// Toggle returns toggle options as middleware
|
// Toggle returns toggle options as middleware
|
||||||
func Toggle(options *ToggleOptions) macaron.Handler {
|
func Toggle(options *ToggleOptions) macaron.Handler {
|
||||||
return func(ctx *Context) {
|
return func(ctx *Context) {
|
||||||
// Cannot view any page before installation.
|
|
||||||
if !setting.InstallLock {
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/install")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path)
|
isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path)
|
||||||
|
|
||||||
// Check prohibit login users.
|
// Check prohibit login users.
|
||||||
|
|
|
@ -31,7 +31,7 @@ const (
|
||||||
//
|
//
|
||||||
// If you add an additional place you must increment this number
|
// If you add an additional place you must increment this number
|
||||||
// and add a function to call manager.InformCleanup if it's not going to be used
|
// and add a function to call manager.InformCleanup if it's not going to be used
|
||||||
const numberOfServersToCreate = 3
|
const numberOfServersToCreate = 4
|
||||||
|
|
||||||
// Manager represents the graceful server manager interface
|
// Manager represents the graceful server manager interface
|
||||||
var manager *Manager
|
var manager *Manager
|
||||||
|
|
|
@ -162,7 +162,7 @@ func (srv *Server) Serve(serve ServeFunction) error {
|
||||||
srv.setState(stateTerminate)
|
srv.setState(stateTerminate)
|
||||||
GetManager().ServerDone()
|
GetManager().ServerDone()
|
||||||
// use of closed means that the listeners are closed - i.e. we should be shutting down - return nil
|
// use of closed means that the listeners are closed - i.e. we should be shutting down - return nil
|
||||||
if err != nil && strings.Contains(err.Error(), "use of closed") {
|
if err == nil || strings.Contains(err.Error(), "use of closed") || strings.Contains(err.Error(), "http: Server closed") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -117,9 +117,46 @@ func InitLocales() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PreInstallInit preloads the configuration to check if we need to run install
|
||||||
|
func PreInstallInit(ctx context.Context) bool {
|
||||||
|
setting.NewContext()
|
||||||
|
if !setting.InstallLock {
|
||||||
|
log.Trace("AppPath: %s", setting.AppPath)
|
||||||
|
log.Trace("AppWorkPath: %s", setting.AppWorkPath)
|
||||||
|
log.Trace("Custom path: %s", setting.CustomPath)
|
||||||
|
log.Trace("Log path: %s", setting.LogRootPath)
|
||||||
|
log.Trace("Preparing to run install page")
|
||||||
|
InitLocales()
|
||||||
|
if setting.EnableSQLite3 {
|
||||||
|
log.Info("SQLite3 Supported")
|
||||||
|
}
|
||||||
|
setting.InitDBConfig()
|
||||||
|
svg.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
return !setting.InstallLock
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostInstallInit rereads the settings and starts up the database
|
||||||
|
func PostInstallInit(ctx context.Context) {
|
||||||
|
setting.NewContext()
|
||||||
|
setting.InitDBConfig()
|
||||||
|
if setting.InstallLock {
|
||||||
|
if err := initDBEngine(ctx); err == nil {
|
||||||
|
log.Info("ORM engine initialization successful!")
|
||||||
|
} else {
|
||||||
|
log.Fatal("ORM engine initialization failed: %v", err)
|
||||||
|
}
|
||||||
|
svg.Init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GlobalInit is for global configuration reload-able.
|
// GlobalInit is for global configuration reload-able.
|
||||||
func GlobalInit(ctx context.Context) {
|
func GlobalInit(ctx context.Context) {
|
||||||
setting.NewContext()
|
setting.NewContext()
|
||||||
|
if !setting.InstallLock {
|
||||||
|
log.Fatal("Gitea is not installed")
|
||||||
|
}
|
||||||
if err := git.Init(ctx); err != nil {
|
if err := git.Init(ctx); err != nil {
|
||||||
log.Fatal("Git module init failed: %v", err)
|
log.Fatal("Git module init failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -134,7 +171,6 @@ func GlobalInit(ctx context.Context) {
|
||||||
|
|
||||||
NewServices()
|
NewServices()
|
||||||
|
|
||||||
if setting.InstallLock {
|
|
||||||
highlight.NewContext()
|
highlight.NewContext()
|
||||||
external.RegisterParsers()
|
external.RegisterParsers()
|
||||||
markup.Init()
|
markup.Init()
|
||||||
|
@ -166,27 +202,19 @@ func GlobalInit(ctx context.Context) {
|
||||||
log.Fatal("Failed to initialize task scheduler: %v", err)
|
log.Fatal("Failed to initialize task scheduler: %v", err)
|
||||||
}
|
}
|
||||||
eventsource.GetManager().Init()
|
eventsource.GetManager().Init()
|
||||||
}
|
|
||||||
if setting.EnableSQLite3 {
|
if setting.EnableSQLite3 {
|
||||||
log.Info("SQLite3 Supported")
|
log.Info("SQLite3 Supported")
|
||||||
}
|
}
|
||||||
checkRunMode()
|
checkRunMode()
|
||||||
|
|
||||||
// Now because Install will re-run GlobalInit once it has set InstallLock
|
|
||||||
// we can't tell if the ssh port will remain unused until that's done.
|
|
||||||
// However, see FIXME comment in install.go
|
|
||||||
if setting.InstallLock {
|
|
||||||
if setting.SSH.StartBuiltinServer {
|
if setting.SSH.StartBuiltinServer {
|
||||||
ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
|
ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
|
||||||
log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
|
log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
|
||||||
} else {
|
} else {
|
||||||
ssh.Unused()
|
ssh.Unused()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if setting.InstallLock {
|
|
||||||
sso.Init()
|
sso.Init()
|
||||||
}
|
|
||||||
|
|
||||||
svg.Init()
|
svg.Init()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -28,12 +28,14 @@ import (
|
||||||
const (
|
const (
|
||||||
// tplInstall template for installation page
|
// tplInstall template for installation page
|
||||||
tplInstall base.TplName = "install"
|
tplInstall base.TplName = "install"
|
||||||
|
tplPostInstall base.TplName = "post-install"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstallInit prepare for rendering installation page
|
// InstallInit prepare for rendering installation page
|
||||||
func InstallInit(ctx *context.Context) {
|
func InstallInit(ctx *context.Context) {
|
||||||
if setting.InstallLock {
|
if setting.InstallLock {
|
||||||
ctx.NotFound("Install", errors.New("Installation is prohibited"))
|
ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
|
||||||
|
ctx.HTML(200, tplPostInstall)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +359,8 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalInit(graceful.GetManager().HammerContext())
|
// Re-read settings
|
||||||
|
PostInstallInit(ctx.Req.Context())
|
||||||
|
|
||||||
// Create admin account
|
// Create admin account
|
||||||
if len(form.AdminName) > 0 {
|
if len(form.AdminName) > 0 {
|
||||||
|
@ -380,6 +383,11 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
|
||||||
u, _ = models.GetUserByName(u.Name)
|
u, _ = models.GetUserByName(u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
days := 86400 * setting.LogInRememberDays
|
||||||
|
ctx.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
||||||
|
ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
|
||||||
|
setting.CookieRememberName, u.Name, days, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
||||||
|
|
||||||
// Auto-login for admin
|
// Auto-login for admin
|
||||||
if err = ctx.Session.Set("uid", u.ID); err != nil {
|
if err = ctx.Session.Set("uid", u.ID); err != nil {
|
||||||
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
|
@ -397,12 +405,18 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("First-time run install finished!")
|
log.Info("First-time run install finished!")
|
||||||
// FIXME: This isn't really enough to completely take account of new configuration
|
|
||||||
// We should really be restarting:
|
|
||||||
// - On windows this is probably just a simple restart
|
|
||||||
// - On linux we can't just use graceful.RestartProcess() everything that was passed in on LISTEN_FDS
|
|
||||||
// (active or not) needs to be passed out and everything new passed out too.
|
|
||||||
// This means we need to prevent the cleanup goroutine from running prior to the second GlobalInit
|
|
||||||
ctx.Flash.Success(ctx.Tr("install.install_success"))
|
ctx.Flash.Success(ctx.Tr("install.install_success"))
|
||||||
ctx.Redirect(form.AppURL + "user/login")
|
|
||||||
|
ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
|
||||||
|
ctx.HTML(200, tplPostInstall)
|
||||||
|
|
||||||
|
// Now get the http.Server from this request and shut it down
|
||||||
|
// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
|
||||||
|
srv := ctx.Req.Context().Value(http.ServerContextKey).(*http.Server)
|
||||||
|
go func() {
|
||||||
|
if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil {
|
||||||
|
log.Error("Unable to shutdown the install server! Error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -301,6 +301,15 @@ func NewMacaron() *macaron.Macaron {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterInstallRoute registers the install routes
|
||||||
|
func RegisterInstallRoute(m *macaron.Macaron) {
|
||||||
|
m.Combo("/", routers.InstallInit).Get(routers.Install).
|
||||||
|
Post(binding.BindIgnErr(auth.InstallForm{}), routers.InstallPost)
|
||||||
|
m.NotFound(func(ctx *context.Context) {
|
||||||
|
ctx.Redirect(setting.AppURL, 302)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterRoutes routes routes to Macaron
|
// RegisterRoutes routes routes to Macaron
|
||||||
func RegisterRoutes(m *macaron.Macaron) {
|
func RegisterRoutes(m *macaron.Macaron) {
|
||||||
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
|
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<p>{{.i18n.Tr "install.docker_helper" "https://docs.gitea.io/en-us/install-with-docker/" | Safe}}</p>
|
<p>{{.i18n.Tr "install.docker_helper" "https://docs.gitea.io/en-us/install-with-docker/" | Safe}}</p>
|
||||||
|
|
||||||
<form class="ui form" action="{{AppSubUrl}}/install" method="post">
|
<form class="ui form" action="{{AppSubUrl}}/" method="post">
|
||||||
<!-- Database Settings -->
|
<!-- Database Settings -->
|
||||||
<h4 class="ui dividing header">{{.i18n.Tr "install.db_title"}}</h4>
|
<h4 class="ui dividing header">{{.i18n.Tr "install.db_title"}}</h4>
|
||||||
<p>{{.i18n.Tr "install.requite_db_desc"}}</p>
|
<p>{{.i18n.Tr "install.requite_db_desc"}}</p>
|
||||||
|
@ -307,4 +307,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<img style="display: none" src="{{StaticUrlPrefix}}/img/loading.png"/>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|
24
templates/post-install.tmpl
Normal file
24
templates/post-install.tmpl
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div class="install">
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui grid">
|
||||||
|
<div class="sixteen wide column content">
|
||||||
|
<div class="home">
|
||||||
|
<div class="ui stackable middle very relaxed page grid">
|
||||||
|
<div id="repo_migrating" class="sixteen wide center aligned centered column">
|
||||||
|
<div>
|
||||||
|
<img src="{{StaticUrlPrefix}}/img/loading.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui stackable middle very relaxed page grid">
|
||||||
|
<div class="sixteen wide center aligned centered column">
|
||||||
|
<p><a href="{{AppUrl}}user/login">{{AppUrl}}user/login</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
Loading…
Reference in a new issue