mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #1995 from overleaf/ta-conversion-metrics-query-fix
Pass conversion metrics query params to v1 GitOrigin-RevId: 0f6cb0118ac92896590fbe26b24a6b2a723e1f2c
This commit is contained in:
parent
d9b5941642
commit
90b1c4a23c
7 changed files with 1770 additions and 0 deletions
278
services/web/modules/launchpad/app/src/LaunchpadController.js
Normal file
278
services/web/modules/launchpad/app/src/LaunchpadController.js
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
/* eslint-disable
|
||||||
|
handle-callback-err,
|
||||||
|
max-len,
|
||||||
|
no-unused-vars,
|
||||||
|
*/
|
||||||
|
// TODO: This file was created by bulk-decaffeinate.
|
||||||
|
// Fix any style issues and re-enable lint.
|
||||||
|
/*
|
||||||
|
* decaffeinate suggestions:
|
||||||
|
* DS102: Remove unnecessary code created because of implicit returns
|
||||||
|
* DS207: Consider shorter variations of null checks
|
||||||
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
|
*/
|
||||||
|
let LaunchpadController
|
||||||
|
const Settings = require('settings-sharelatex')
|
||||||
|
const Path = require('path')
|
||||||
|
const Url = require('url')
|
||||||
|
const logger = require('logger-sharelatex')
|
||||||
|
const metrics = require('metrics-sharelatex')
|
||||||
|
const UserRegistrationHandler = require('../../../../app/src/Features/User/UserRegistrationHandler')
|
||||||
|
const EmailHandler = require('../../../../app/src/Features/Email/EmailHandler')
|
||||||
|
const _ = require('underscore')
|
||||||
|
const UserGetter = require('../../../../app/src/Features/User/UserGetter')
|
||||||
|
const { User } = require('../../../../app/src/models/User')
|
||||||
|
const AuthenticationController = require('../../../../app/src/Features/Authentication/AuthenticationController')
|
||||||
|
|
||||||
|
module.exports = LaunchpadController = {
|
||||||
|
_getAuthMethod() {
|
||||||
|
if (Settings.ldap) {
|
||||||
|
return 'ldap'
|
||||||
|
} else if (Settings.saml) {
|
||||||
|
return 'saml'
|
||||||
|
} else {
|
||||||
|
return 'local'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
launchpadPage(req, res, next) {
|
||||||
|
// TODO: check if we're using external auth?
|
||||||
|
// * how does all this work with ldap and saml?
|
||||||
|
const sessionUser = AuthenticationController.getSessionUser(req)
|
||||||
|
const authMethod = LaunchpadController._getAuthMethod()
|
||||||
|
return LaunchpadController._atLeastOneAdminExists(function(
|
||||||
|
err,
|
||||||
|
adminUserExists
|
||||||
|
) {
|
||||||
|
if (err != null) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
if (!sessionUser) {
|
||||||
|
if (!adminUserExists) {
|
||||||
|
return res.render(Path.resolve(__dirname, '../views/launchpad'), {
|
||||||
|
adminUserExists,
|
||||||
|
authMethod
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return AuthenticationController._redirectToLoginPage(req, res)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return UserGetter.getUser(sessionUser._id, { isAdmin: 1 }, function(
|
||||||
|
err,
|
||||||
|
user
|
||||||
|
) {
|
||||||
|
if (err != null) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
if (user && user.isAdmin) {
|
||||||
|
return res.render(Path.resolve(__dirname, '../views/launchpad'), {
|
||||||
|
adminUserExists,
|
||||||
|
authMethod
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return res.redirect('/restricted')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
_atLeastOneAdminExists(callback) {
|
||||||
|
if (callback == null) {
|
||||||
|
callback = function(err, exists) {}
|
||||||
|
}
|
||||||
|
return UserGetter.getUser(
|
||||||
|
{ isAdmin: true },
|
||||||
|
{ _id: 1, isAdmin: 1 },
|
||||||
|
function(err, user) {
|
||||||
|
if (err != null) {
|
||||||
|
return callback(err)
|
||||||
|
}
|
||||||
|
return callback(null, user != null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
sendTestEmail(req, res, next) {
|
||||||
|
const { email } = req.body
|
||||||
|
if (!email) {
|
||||||
|
logger.log({}, 'no email address supplied')
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
logger.log({ email }, 'sending test email')
|
||||||
|
const emailOptions = { to: email }
|
||||||
|
return EmailHandler.sendEmail('testEmail', emailOptions, function(err) {
|
||||||
|
if (err != null) {
|
||||||
|
logger.warn({ email }, 'error sending test email')
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
logger.log({ email }, 'sent test email')
|
||||||
|
return res.sendStatus(201)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
registerExternalAuthAdmin(authMethod) {
|
||||||
|
return function(req, res, next) {
|
||||||
|
if (LaunchpadController._getAuthMethod() !== authMethod) {
|
||||||
|
logger.log(
|
||||||
|
{ authMethod },
|
||||||
|
'trying to register external admin, but that auth service is not enabled, disallow'
|
||||||
|
)
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
const { email } = req.body
|
||||||
|
if (!email) {
|
||||||
|
logger.log({ authMethod }, 'no email supplied, disallow')
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log({ email }, 'attempted register first admin user')
|
||||||
|
return LaunchpadController._atLeastOneAdminExists(function(err, exists) {
|
||||||
|
if (err != null) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
logger.log(
|
||||||
|
{ email },
|
||||||
|
'already have at least one admin user, disallow'
|
||||||
|
)
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
email,
|
||||||
|
password: 'password_here',
|
||||||
|
first_name: email,
|
||||||
|
last_name: ''
|
||||||
|
}
|
||||||
|
logger.log(
|
||||||
|
{ body, authMethod },
|
||||||
|
'creating admin account for specified external-auth user'
|
||||||
|
)
|
||||||
|
|
||||||
|
return UserRegistrationHandler.registerNewUser(body, function(
|
||||||
|
err,
|
||||||
|
user
|
||||||
|
) {
|
||||||
|
if (err != null) {
|
||||||
|
logger.warn(
|
||||||
|
{ err, email, authMethod },
|
||||||
|
'error with registerNewUser'
|
||||||
|
)
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return User.update(
|
||||||
|
{ _id: user._id },
|
||||||
|
{ $set: { isAdmin: true } },
|
||||||
|
function(err) {
|
||||||
|
if (err != null) {
|
||||||
|
logger.warn(
|
||||||
|
{ user_id: user._id, err },
|
||||||
|
'error setting user to admin'
|
||||||
|
)
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationController.setRedirectInSession(req, '/launchpad')
|
||||||
|
logger.log(
|
||||||
|
{ email, user_id: user._id, authMethod },
|
||||||
|
'created first admin account'
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.json({ redir: '/launchpad', email })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
registerAdmin(req, res, next) {
|
||||||
|
const { email } = req.body
|
||||||
|
const { password } = req.body
|
||||||
|
if (!email || !password) {
|
||||||
|
logger.log({}, 'must supply both email and password, disallow')
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log({ email }, 'attempted register first admin user')
|
||||||
|
return LaunchpadController._atLeastOneAdminExists(function(err, exists) {
|
||||||
|
if (err != null) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
logger.log(
|
||||||
|
{ email: req.body.email },
|
||||||
|
'already have at least one admin user, disallow'
|
||||||
|
)
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = { email, password }
|
||||||
|
return UserRegistrationHandler.registerNewUser(body, function(err, user) {
|
||||||
|
if (err != null) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log({ user_id: user._id }, 'making user an admin')
|
||||||
|
const proceed = () =>
|
||||||
|
User.update({ _id: user._id }, { $set: { isAdmin: true } }, function(
|
||||||
|
err
|
||||||
|
) {
|
||||||
|
if (err != null) {
|
||||||
|
logger.err(
|
||||||
|
{ user_id: user._id, err },
|
||||||
|
'error setting user to admin'
|
||||||
|
)
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationController.setRedirectInSession(req, '/launchpad')
|
||||||
|
logger.log(
|
||||||
|
{ email, user_id: user._id },
|
||||||
|
'created first admin account'
|
||||||
|
)
|
||||||
|
return res.json({
|
||||||
|
redir: '',
|
||||||
|
id: user._id.toString(),
|
||||||
|
first_name: user.first_name,
|
||||||
|
last_name: user.last_name,
|
||||||
|
email: user.email,
|
||||||
|
created: Date.now()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
Settings.overleaf != null &&
|
||||||
|
Settings.createV1AccountOnLogin != null
|
||||||
|
) {
|
||||||
|
logger.log(
|
||||||
|
{ user_id: user._id },
|
||||||
|
'Creating backing account in v1 for new admin user'
|
||||||
|
)
|
||||||
|
const SharelatexAuthController = require('../../../overleaf-integration/app/src/SharelatexAuth/SharelatexAuthController')
|
||||||
|
return UserGetter.getUser(user._id, function(err, user) {
|
||||||
|
if (err != null) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
return SharelatexAuthController._createBackingAccountIfNeeded(
|
||||||
|
user,
|
||||||
|
req,
|
||||||
|
function(err) {
|
||||||
|
if (err != null) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
return proceed()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return proceed()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
53
services/web/modules/launchpad/app/src/LaunchpadRouter.js
Normal file
53
services/web/modules/launchpad/app/src/LaunchpadRouter.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/* eslint-disable
|
||||||
|
max-len,
|
||||||
|
*/
|
||||||
|
// TODO: This file was created by bulk-decaffeinate.
|
||||||
|
// Fix any style issues and re-enable lint.
|
||||||
|
/*
|
||||||
|
* decaffeinate suggestions:
|
||||||
|
* DS102: Remove unnecessary code created because of implicit returns
|
||||||
|
* DS207: Consider shorter variations of null checks
|
||||||
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
|
*/
|
||||||
|
const logger = require('logger-sharelatex')
|
||||||
|
const LaunchpadController = require('./LaunchpadController')
|
||||||
|
const AuthenticationController = require('../../../../app/src/Features/Authentication/AuthenticationController')
|
||||||
|
const AuthorizationMiddleware = require('../../../../app/src/Features/Authorization/AuthorizationMiddleware')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
apply(webRouter, apiRouter) {
|
||||||
|
logger.log({}, 'Init launchpad router')
|
||||||
|
|
||||||
|
webRouter.get('/launchpad', LaunchpadController.launchpadPage)
|
||||||
|
webRouter.post(
|
||||||
|
'/launchpad/register_admin',
|
||||||
|
LaunchpadController.registerAdmin
|
||||||
|
)
|
||||||
|
webRouter.post(
|
||||||
|
'/launchpad/register_ldap_admin',
|
||||||
|
LaunchpadController.registerExternalAuthAdmin('ldap')
|
||||||
|
)
|
||||||
|
webRouter.post(
|
||||||
|
'/launchpad/register_saml_admin',
|
||||||
|
LaunchpadController.registerExternalAuthAdmin('saml')
|
||||||
|
)
|
||||||
|
webRouter.post(
|
||||||
|
'/launchpad/send_test_email',
|
||||||
|
AuthorizationMiddleware.ensureUserIsSiteAdmin,
|
||||||
|
LaunchpadController.sendTestEmail
|
||||||
|
)
|
||||||
|
|
||||||
|
if (AuthenticationController.addEndpointToLoginWhitelist != null) {
|
||||||
|
AuthenticationController.addEndpointToLoginWhitelist('/launchpad')
|
||||||
|
AuthenticationController.addEndpointToLoginWhitelist(
|
||||||
|
'/launchpad/register_admin'
|
||||||
|
)
|
||||||
|
AuthenticationController.addEndpointToLoginWhitelist(
|
||||||
|
'/launchpad/register_ldap_admin'
|
||||||
|
)
|
||||||
|
return AuthenticationController.addEndpointToLoginWhitelist(
|
||||||
|
'/launchpad/register_saml_admin'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
267
services/web/modules/launchpad/app/views/launchpad.pug
Normal file
267
services/web/modules/launchpad/app/views/launchpad.pug
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
extends ../../../../app/views/layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
script(type="text/javascript").
|
||||||
|
window.data = {
|
||||||
|
adminUserExists: !{adminUserExists == true},
|
||||||
|
ideJsPath: "!{buildJsPath('ide.js')}",
|
||||||
|
authMethod: "!{authMethod}"
|
||||||
|
}
|
||||||
|
|
||||||
|
script(type="text/javascript" src='/socket.io/socket.io.js')
|
||||||
|
|
||||||
|
style.
|
||||||
|
hr { margin-bottom: 5px; }
|
||||||
|
.status-check {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.alert { margin-top: 15px; margin-bottom: 15px; }
|
||||||
|
a small { cursor: pointer; color: #a93529; }
|
||||||
|
.launchpad-body img { width: 150px; margin-top: 15px; }
|
||||||
|
|
||||||
|
|
||||||
|
.content.content-alt(ng-controller="LaunchpadController")
|
||||||
|
.container(ng-cloak)
|
||||||
|
.row
|
||||||
|
.col-md-8.col-md-offset-2
|
||||||
|
.card.launchpad-body
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
.text-center
|
||||||
|
h1 #{translate('welcome_to_sl')}
|
||||||
|
p
|
||||||
|
img(src=buildImgPath('/brand/lion.svg'))
|
||||||
|
|
||||||
|
<!-- wrapper -->
|
||||||
|
.row
|
||||||
|
.col-md-8.col-md-offset-2
|
||||||
|
|
||||||
|
|
||||||
|
<!-- create first admin form -->
|
||||||
|
.row(ng-if="shouldShowAdminForm()")
|
||||||
|
.col-md-12
|
||||||
|
h2 #{translate('create_first_admin_account')}
|
||||||
|
|
||||||
|
// Local Auth Form
|
||||||
|
div(ng-if="authMethod == 'local'")
|
||||||
|
form(async-form="register", name="registerForm", on-success="onCreateAdminSuccess"
|
||||||
|
on-error="onCreateAdminError"
|
||||||
|
action="/launchpad/register_admin", method="POST", ng-cloak)
|
||||||
|
input(name='_csrf', type='hidden', value=csrfToken)
|
||||||
|
form-messages(for="registerForm")
|
||||||
|
.form-group
|
||||||
|
label(for='email') #{translate("email")}
|
||||||
|
input.form-control(
|
||||||
|
type='email',
|
||||||
|
name='email',
|
||||||
|
placeholder="email@example.com"
|
||||||
|
required,
|
||||||
|
ng-model="email",
|
||||||
|
ng-model-options="{ updateOn: 'blur' }",
|
||||||
|
focus="true"
|
||||||
|
)
|
||||||
|
span.small.text-primary(ng-show="registerForm.email.$invalid && registerForm.email.$dirty")
|
||||||
|
| #{translate("must_be_email_address")}
|
||||||
|
.form-group
|
||||||
|
label(for='password') #{translate("password")}
|
||||||
|
input.form-control#passwordField(
|
||||||
|
type='password',
|
||||||
|
name='password',
|
||||||
|
placeholder="********",
|
||||||
|
required,
|
||||||
|
ng-model="password",
|
||||||
|
complex-password
|
||||||
|
)
|
||||||
|
span.small.text-primary(ng-show="registerForm.password.$error.complexPassword",
|
||||||
|
ng-bind-html="complexPasswordErrorMessage")
|
||||||
|
.actions
|
||||||
|
button.btn-primary.btn(
|
||||||
|
type='submit'
|
||||||
|
ng-disabled="registerForm.inflight || registerForm.password.$error.required|| registerForm.password.$error.complexPassword || createAdminSuccess"
|
||||||
|
)
|
||||||
|
span(ng-show="!registerForm.inflight") #{translate("register")}
|
||||||
|
span(ng-show="registerForm.inflight") #{translate("registering")}...
|
||||||
|
|
||||||
|
// Ldap Form
|
||||||
|
div(ng-if="authMethod == 'ldap'")
|
||||||
|
h3 #{translate('ldap')}
|
||||||
|
p
|
||||||
|
| #{translate('ldap_create_admin_instructions')}
|
||||||
|
|
||||||
|
form(async-form="register", name="registerLdapForm", on-success="onCreateAdminSuccess"
|
||||||
|
on-error="onCreateAdminError"
|
||||||
|
action="/launchpad/register_ldap_admin", method="POST", ng-cloak)
|
||||||
|
input(name='_csrf', type='hidden', value=csrfToken)
|
||||||
|
form-messages(for="registerLdapForm")
|
||||||
|
.form-group
|
||||||
|
label(for='email') #{translate("email")}
|
||||||
|
input.form-control(
|
||||||
|
type='email',
|
||||||
|
name='email',
|
||||||
|
placeholder="email@example.com"
|
||||||
|
required,
|
||||||
|
ng-model="email",
|
||||||
|
ng-model-options="{ updateOn: 'blur' }",
|
||||||
|
focus="true"
|
||||||
|
)
|
||||||
|
span.small.text-primary(ng-show="registerLdapForm.email.$invalid && registerLdapForm.email.$dirty")
|
||||||
|
| #{translate("must_be_email_address")}
|
||||||
|
.actions
|
||||||
|
button.btn-primary.btn(
|
||||||
|
type='submit'
|
||||||
|
ng-disabled="registerLdapForm.inflight || registerLdapForm.password.$error.required|| registerLdapForm.password.$error.complexPassword || createAdminSuccess"
|
||||||
|
)
|
||||||
|
span(ng-show="!registerLdapForm.inflight") #{translate("register")}
|
||||||
|
span(ng-show="registerLdapForm.inflight") #{translate("registering")}...
|
||||||
|
|
||||||
|
|
||||||
|
// Saml Form
|
||||||
|
div(ng-if="authMethod == 'saml'")
|
||||||
|
h3 #{translate('saml')}
|
||||||
|
p
|
||||||
|
| #{translate('saml_create_admin_instructions')}
|
||||||
|
|
||||||
|
form(async-form="register", name="registerSamlForm", on-success="onCreateAdminSuccess"
|
||||||
|
on-error="onCreateAdminError"
|
||||||
|
action="/launchpad/register_saml_admin", method="POST", ng-cloak)
|
||||||
|
input(name='_csrf', type='hidden', value=csrfToken)
|
||||||
|
form-messages(for="registerSamlForm")
|
||||||
|
.form-group
|
||||||
|
label(for='email') #{translate("email")}
|
||||||
|
input.form-control(
|
||||||
|
type='email',
|
||||||
|
name='email',
|
||||||
|
placeholder="email@example.com"
|
||||||
|
required,
|
||||||
|
ng-model="email",
|
||||||
|
ng-model-options="{ updateOn: 'blur' }",
|
||||||
|
focus="true"
|
||||||
|
)
|
||||||
|
span.small.text-primary(ng-show="registerSamlForm.email.$invalid && registerSamlForm.email.$dirty")
|
||||||
|
| #{translate("must_be_email_address")}
|
||||||
|
.actions
|
||||||
|
button.btn-primary.btn(
|
||||||
|
type='submit'
|
||||||
|
ng-disabled="registerSamlForm.inflight || registerSamlForm.password.$error.required|| registerSamlForm.password.$error.complexPassword || createAdminSuccess"
|
||||||
|
)
|
||||||
|
span(ng-show="!registerSamlForm.inflight") #{translate("register")}
|
||||||
|
span(ng-show="registerSamlForm.inflight") #{translate("registering")}...
|
||||||
|
|
||||||
|
|
||||||
|
<!-- success message -->
|
||||||
|
.row(ng-if="createAdminSuccess")
|
||||||
|
.col-md-12.text-center
|
||||||
|
.alert.alert-success
|
||||||
|
| !{translate('admin_user_created_message', {link: '/login?redir=/launchpad'})}
|
||||||
|
|
||||||
|
<!-- error message -->
|
||||||
|
.row(ng-if="createAdminError")
|
||||||
|
.col-md-12.text-center
|
||||||
|
.alert.alert-danger
|
||||||
|
| #{translate('generic_something_went_wrong')}
|
||||||
|
|
||||||
|
br
|
||||||
|
|
||||||
|
<!-- status indicators -->
|
||||||
|
div(ng-if="!shouldShowAdminForm()")
|
||||||
|
.row
|
||||||
|
.col-md-12.status-indicators
|
||||||
|
|
||||||
|
h2 #{translate('status_checks')}
|
||||||
|
|
||||||
|
<!-- ide js -->
|
||||||
|
.row.status-check
|
||||||
|
.col-sm-5
|
||||||
|
| #{translate('editor_resources')}
|
||||||
|
.col-sm-7
|
||||||
|
div(ng-switch="statusChecks.ideJs.status")
|
||||||
|
|
||||||
|
span(ng-switch-when="inflight")
|
||||||
|
i.fa.fa-fw.fa-spinner.fa-spin
|
||||||
|
span #{translate('checking')}
|
||||||
|
|
||||||
|
span(ng-switch-when="ok")
|
||||||
|
i.fa.fa-check
|
||||||
|
span #{translate('ok')}
|
||||||
|
a(ng-click="tryFetchIdeJs()")
|
||||||
|
small #{translate('retry')}
|
||||||
|
|
||||||
|
span(ng-switch-when="error")
|
||||||
|
i.fa.fa-exclamation
|
||||||
|
span #{translate('error')}
|
||||||
|
a(ng-click="tryFetchIdeJs()")
|
||||||
|
small #{translate('retry')}
|
||||||
|
div.alert.alert-danger
|
||||||
|
| {{ statusChecks.ideJs.error }}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- websocket -->
|
||||||
|
.row.status-check
|
||||||
|
.col-sm-5
|
||||||
|
| #{translate('websockets')}
|
||||||
|
.col-sm-7
|
||||||
|
div(ng-switch="statusChecks.websocket.status")
|
||||||
|
|
||||||
|
span(ng-switch-when="inflight")
|
||||||
|
i.fa.fa-fw.fa-spinner.fa-spin
|
||||||
|
span #{translate('checking')}
|
||||||
|
|
||||||
|
span(ng-switch-when="ok")
|
||||||
|
i.fa.fa-check
|
||||||
|
span #{translate('ok')}
|
||||||
|
a(ng-click="tryOpenWebSocket()")
|
||||||
|
small #{translate('retry')}
|
||||||
|
|
||||||
|
span(ng-switch-when="error")
|
||||||
|
i.fa.fa-exclamation
|
||||||
|
span #{translate('error')}
|
||||||
|
a(ng-click="tryOpenWebSocket()")
|
||||||
|
small #{translate('retry')}
|
||||||
|
div.alert.alert-danger
|
||||||
|
| {{ statusChecks.websocket.error }}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- break -->
|
||||||
|
hr
|
||||||
|
|
||||||
|
<!-- other actions -->
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
h2 #{translate('other_actions')}
|
||||||
|
|
||||||
|
h3 #{translate('send_test_email')}
|
||||||
|
form.form
|
||||||
|
.form-group
|
||||||
|
label(for="emailInput") Email
|
||||||
|
input(type="text", name="emailInput" ng-model="testEmail.emailAddress").form-control
|
||||||
|
button(ng-click="sendTestEmail()", ng-disabled="testEmail.inflight").btn.btn-primary
|
||||||
|
span(ng-show="!testEmail.inflight") #{translate("send")}
|
||||||
|
span(ng-show="testEmail.inflight") #{translate("sending")}...
|
||||||
|
div(ng-if="testEmail.status == 'ok'")
|
||||||
|
.alert.alert-success
|
||||||
|
| #{translate('email_sent')}
|
||||||
|
div(ng-if="testEmail.status == 'error'")
|
||||||
|
.alert.alert-danger
|
||||||
|
| #{translate('generic_something_went_wrong')}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- break -->
|
||||||
|
hr
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Go to ShareLaTex -->
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
.text-center
|
||||||
|
br
|
||||||
|
p
|
||||||
|
a(href="/admin").btn.btn-md
|
||||||
|
| Go To Admin Panel
|
||||||
|
|
|
||||||
|
a(href="/project").btn.btn-md.btn-primary
|
||||||
|
| Start Using ShareLaTeX
|
||||||
|
br
|
9
services/web/modules/launchpad/index.js
Normal file
9
services/web/modules/launchpad/index.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/* eslint-disable
|
||||||
|
no-unused-vars,
|
||||||
|
*/
|
||||||
|
// TODO: This file was created by bulk-decaffeinate.
|
||||||
|
// Fix any style issues and re-enable lint.
|
||||||
|
let Launchpad
|
||||||
|
const LaunchpadRouter = require('./app/src/LaunchpadRouter')
|
||||||
|
|
||||||
|
module.exports = Launchpad = { router: LaunchpadRouter }
|
|
@ -0,0 +1,155 @@
|
||||||
|
/* eslint-disable
|
||||||
|
max-len,
|
||||||
|
no-return-assign,
|
||||||
|
no-undef,
|
||||||
|
*/
|
||||||
|
// TODO: This file was created by bulk-decaffeinate.
|
||||||
|
// Fix any style issues and re-enable lint.
|
||||||
|
/*
|
||||||
|
* decaffeinate suggestions:
|
||||||
|
* DS102: Remove unnecessary code created because of implicit returns
|
||||||
|
* DS207: Consider shorter variations of null checks
|
||||||
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
|
*/
|
||||||
|
define(['base'], App =>
|
||||||
|
App.controller('LaunchpadController', function($scope, $http, $timeout) {
|
||||||
|
$scope.adminUserExists = window.data.adminUserExists
|
||||||
|
$scope.ideJsPath = window.data.ideJsPath
|
||||||
|
$scope.authMethod = window.data.authMethod
|
||||||
|
|
||||||
|
$scope.createAdminSuccess = null
|
||||||
|
$scope.createAdminError = null
|
||||||
|
|
||||||
|
$scope.statusChecks = {
|
||||||
|
ideJs: { status: 'inflight', error: null },
|
||||||
|
websocket: { status: 'inflight', error: null },
|
||||||
|
healthCheck: { status: 'inflight', error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.testEmail = {
|
||||||
|
emailAddress: '',
|
||||||
|
inflight: false,
|
||||||
|
status: null // | 'ok' | 'success'
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.shouldShowAdminForm = () => !$scope.adminUserExists
|
||||||
|
|
||||||
|
$scope.onCreateAdminSuccess = function(response) {
|
||||||
|
const { status } = response
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
return ($scope.createAdminSuccess = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.onCreateAdminError = () => ($scope.createAdminError = true)
|
||||||
|
|
||||||
|
$scope.sendTestEmail = function() {
|
||||||
|
$scope.testEmail.inflight = true
|
||||||
|
$scope.testEmail.status = null
|
||||||
|
return $http
|
||||||
|
.post('/launchpad/send_test_email', {
|
||||||
|
email: $scope.testEmail.emailAddress,
|
||||||
|
_csrf: window.csrfToken
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
const { status } = response
|
||||||
|
$scope.testEmail.inflight = false
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
return ($scope.testEmail.status = 'ok')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
$scope.testEmail.inflight = false
|
||||||
|
return ($scope.testEmail.status = 'error')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.tryFetchIdeJs = function() {
|
||||||
|
$scope.statusChecks.ideJs.status = 'inflight'
|
||||||
|
return $timeout(
|
||||||
|
() =>
|
||||||
|
$http
|
||||||
|
.get($scope.ideJsPath)
|
||||||
|
.then(function(response) {
|
||||||
|
const { status } = response
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
return ($scope.statusChecks.ideJs.status = 'ok')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(response) {
|
||||||
|
const { status } = response
|
||||||
|
$scope.statusChecks.ideJs.status = 'error'
|
||||||
|
return ($scope.statusChecks.ideJs.error = new Error(
|
||||||
|
`Http status: ${status}`
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.tryOpenWebSocket = function() {
|
||||||
|
$scope.statusChecks.websocket.status = 'inflight'
|
||||||
|
return $timeout(function() {
|
||||||
|
if (typeof io === 'undefined' || io === null) {
|
||||||
|
$scope.statusChecks.websocket.status = 'error'
|
||||||
|
$scope.statusChecks.websocket.error = 'socket.io not loaded'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const socket = io.connect(
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
reconnect: false,
|
||||||
|
'connect timeout': 30 * 1000,
|
||||||
|
'force new connection': true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
socket.on('connectionAccepted', function() {
|
||||||
|
$scope.statusChecks.websocket.status = 'ok'
|
||||||
|
return $scope.$apply(function() {})
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('connectionRejected', function(err) {
|
||||||
|
$scope.statusChecks.websocket.status = 'error'
|
||||||
|
$scope.statusChecks.websocket.error = err
|
||||||
|
return $scope.$apply(function() {})
|
||||||
|
})
|
||||||
|
|
||||||
|
return socket.on('connect_failed', function(err) {
|
||||||
|
$scope.statusChecks.websocket.status = 'error'
|
||||||
|
$scope.statusChecks.websocket.error = err
|
||||||
|
return $scope.$apply(function() {})
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.tryHealthCheck = function() {
|
||||||
|
$scope.statusChecks.healthCheck.status = 'inflight'
|
||||||
|
return $http
|
||||||
|
.get('/health_check')
|
||||||
|
.then(function(response) {
|
||||||
|
const { status } = response
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
return ($scope.statusChecks.healthCheck.status = 'ok')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(response) {
|
||||||
|
const { status } = response
|
||||||
|
$scope.statusChecks.healthCheck.status = 'error'
|
||||||
|
return ($scope.statusChecks.healthCheck.error = new Error(
|
||||||
|
`Http status: ${status}`
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.runStatusChecks = function() {
|
||||||
|
$timeout(() => $scope.tryFetchIdeJs(), 1000)
|
||||||
|
return $timeout(() => $scope.tryOpenWebSocket(), 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kick off the status checks on load
|
||||||
|
if ($scope.adminUserExists) {
|
||||||
|
return $scope.runStatusChecks()
|
||||||
|
}
|
||||||
|
}))
|
3
services/web/modules/launchpad/public/src/main/index.js
Normal file
3
services/web/modules/launchpad/public/src/main/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// TODO: This file was created by bulk-decaffeinate.
|
||||||
|
// Sanity-check the conversion and remove this comment.
|
||||||
|
define(['main/launchpad/controllers/LaunchpadController'], function() {})
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue