mod zone WIP
parent
570201a2a6
commit
f801000243
|
@ -453,8 +453,7 @@ object mod {
|
|||
|
||||
private val sortNumberTh = th(attr("data-sort-method") := "number")
|
||||
private val dataSort = attr("data-sort")
|
||||
private val dataIps = attr("data-ips")
|
||||
private val dataFps = attr("data-fps")
|
||||
private val dataTags = attr("data-tags")
|
||||
private val playban = iconTag("p")
|
||||
private val alt: Frag = i("A")
|
||||
private val shadowban: Frag = iconTag("c")
|
||||
|
@ -504,11 +503,10 @@ object mod {
|
|||
val dox = isGranted(_.Doxing) || (o.lameOrAlt && !o.hasTitle)
|
||||
val userNotes =
|
||||
notes.filter(n => n.to == o.id && (ctx.me.exists(n.isFrom) || isGranted(_.Doxing)))
|
||||
tr(
|
||||
o == u option (cls := "same"),
|
||||
dataIps := other.ips.mkString(","),
|
||||
dataFps := other.fps.map(_.value).mkString(",")
|
||||
)(
|
||||
val row =
|
||||
if (o == u) tr(cls := "same")
|
||||
else tr(dataTags := s"${other.ips.mkString(" ")} ${other.fps.mkString(" ")}")
|
||||
row(
|
||||
if (dox || o == u) td(dataSort := o.id)(userLink(o, withBestRating = true, params = "?mod"))
|
||||
else td,
|
||||
if (dox) td(othersWithEmail emailValueOf o)
|
||||
|
@ -557,37 +555,39 @@ object mod {
|
|||
def identification(spy: UserSpy): Frag =
|
||||
mzSection("identification")(
|
||||
div(cls := "spy_ips")(
|
||||
strong(spy.ips.size, " IP addresses"),
|
||||
ul(
|
||||
spy.ipsByLocations.map {
|
||||
case (location, ips) => {
|
||||
li(
|
||||
p(location.toString),
|
||||
ul(
|
||||
ips.map { ip =>
|
||||
li(cls := "ip")(
|
||||
a(
|
||||
cls := List("address" -> true, "blocked" -> ip.blocked),
|
||||
href := routes.Mod.singleIp(ip.ip.value.value)
|
||||
)(
|
||||
tag("ip")(ip.ip.value.value),
|
||||
" ",
|
||||
momentFromNowServer(ip.ip.date)
|
||||
),
|
||||
ip.proxy option span(cls := "proxy")("PROXY")
|
||||
)
|
||||
}
|
||||
table(cls := "slist")(
|
||||
thead(
|
||||
tr(
|
||||
th(pluralize("IP", spy.prints.size)),
|
||||
sortNumberTh("Alts"),
|
||||
th,
|
||||
sortNumberTh("Date"),
|
||||
th
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
spy.ips.sortBy(-_.ip.date.getMillis).map { ip =>
|
||||
tr(cls := ip.blocked option "blocked", title := ip.location.toString)(
|
||||
td(a(ip.ip.value)),
|
||||
td(dataSort := ip.alts.score)(altMarks(ip.alts)),
|
||||
td(ip.proxy option span(cls := "proxy")("PROXY")),
|
||||
td(dataSort := ip.ip.date.getMillis)(momentFromNowServer(ip.ip.date)),
|
||||
td(
|
||||
button(
|
||||
cls := "button button-empty",
|
||||
href := routes.Mod.singleIpBan(!ip.blocked, ip.ip.value.value)
|
||||
)("BAN")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
div(cls := "spy_fps")(
|
||||
table(cls := "slist")(
|
||||
thead(
|
||||
tr(
|
||||
th(strong(pluralize("Print", spy.prints.size))),
|
||||
th(pluralize("Print", spy.prints.size)),
|
||||
sortNumberTh("Alts"),
|
||||
sortNumberTh("Date"),
|
||||
th
|
||||
|
@ -611,11 +611,23 @@ object mod {
|
|||
)
|
||||
),
|
||||
div(cls := "spy_uas")(
|
||||
strong(spy.uas.size, " User agent(s)"),
|
||||
ul(
|
||||
spy.uas.sorted.map { ua =>
|
||||
li(ua.value, " ", momentFromNowServer(ua.date))
|
||||
}
|
||||
table(cls := "slist")(
|
||||
thead(
|
||||
tr(
|
||||
th(pluralize("User Agent", spy.uas.size)),
|
||||
sortNumberTh("Date"),
|
||||
th
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
spy.uas.map { ua =>
|
||||
tr(
|
||||
td(ua.value.toString),
|
||||
td(dataSort := ua.date.getMillis)(momentFromNowServer(ua.date)),
|
||||
td
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -36,7 +36,7 @@ libraryDependencies ++= akka.bundle ++ Seq(
|
|||
scalaz, chess, compression, scalalib, hasher,
|
||||
reactivemongo.driver, maxmind, prismic, scalatags,
|
||||
kamon.core, kamon.influxdb, kamon.metrics, kamon.prometheus,
|
||||
scrimage, scaffeine, lettuce
|
||||
scrimage, scaffeine, lettuce, uaparser
|
||||
) ++ {
|
||||
if (useEpoll) Seq(epoll, reactivemongo.epoll)
|
||||
else Seq.empty
|
||||
|
@ -278,7 +278,7 @@ lazy val oauth = module("oauth",
|
|||
|
||||
lazy val security = module("security",
|
||||
Seq(common, hub, db, user, i18n, slack, oauth),
|
||||
Seq(scalatags, maxmind, hasher, specs2) ++ reactivemongo.bundle
|
||||
Seq(scalatags, maxmind, hasher, uaparser, specs2) ++ reactivemongo.bundle
|
||||
)
|
||||
|
||||
lazy val shutup = module("shutup",
|
||||
|
|
|
@ -47,8 +47,6 @@ case class Location(
|
|||
city: Option[String]
|
||||
) {
|
||||
|
||||
def comparable = (country, ~region, ~city)
|
||||
|
||||
def shortCountry: String = ~country.split(',').headOption
|
||||
|
||||
override def toString = List(shortCountry.some, region, city).flatten mkString " > "
|
||||
|
|
|
@ -202,6 +202,7 @@ object Store {
|
|||
|
||||
case class Dated[V](value: V, date: DateTime) extends Ordered[Dated[V]] {
|
||||
def compare(other: Dated[V]) = other.date compareTo date
|
||||
def map[X](f: V => X) = copy(value = f(value))
|
||||
}
|
||||
|
||||
case class Info(ip: IpAddress, ua: String, fp: Option[FingerHash], date: DateTime) {
|
||||
|
|
|
@ -3,6 +3,8 @@ package lila.security
|
|||
import org.joda.time.DateTime
|
||||
import reactivemongo.api.ReadPreference
|
||||
import reactivemongo.api.bson._
|
||||
import org.uaparser.scala.{ Parser => UAParser }
|
||||
import org.uaparser.scala.Client
|
||||
|
||||
import lila.common.{ EmailAddress, IpAddress }
|
||||
import lila.db.dsl._
|
||||
|
@ -11,7 +13,7 @@ import lila.user.{ User, UserRepo }
|
|||
case class UserSpy(
|
||||
ips: List[UserSpy.IPData],
|
||||
prints: List[UserSpy.FPData],
|
||||
uas: List[Store.Dated[String]],
|
||||
uas: List[Store.Dated[Client]],
|
||||
otherUsers: List[UserSpy.OtherUser]
|
||||
) {
|
||||
|
||||
|
@ -20,9 +22,6 @@ case class UserSpy(
|
|||
def rawIps = ips map (_.ip.value)
|
||||
def rawFps = prints map (_.fp.value)
|
||||
|
||||
def ipsByLocations: List[(Location, List[UserSpy.IPData])] =
|
||||
ips.sortBy(_.ip).groupBy(_.location).toList.sortBy(_._1.comparable)
|
||||
|
||||
def otherUserIds = otherUsers.map(_.user.id)
|
||||
|
||||
def usersSharingIp =
|
||||
|
@ -78,7 +77,7 @@ final class UserSpyApi(
|
|||
Alts(othersByFp.getOrElse(fp.value, Set.empty))
|
||||
)
|
||||
}.toList,
|
||||
uas = distinctRecent(infos.map(_.datedUa)).toList,
|
||||
uas = distinctRecent(infos.map(_.datedUa map parseUa)).toList,
|
||||
otherUsers = otherUsers
|
||||
)
|
||||
}
|
||||
|
@ -87,6 +86,8 @@ final class UserSpyApi(
|
|||
private[security] def userHasPrint(u: User): Fu[Boolean] =
|
||||
store.coll.secondaryPreferred.exists($doc("user" -> u.id, "fp" $exists true))
|
||||
|
||||
private val parseUa = UAParser.default.parse _
|
||||
|
||||
private def fetchOtherUsers(
|
||||
user: User,
|
||||
ipSet: Set[IpAddress],
|
||||
|
|
|
@ -30,6 +30,7 @@ object Dependencies {
|
|||
val autoconfig = "io.methvin.play" %% "autoconfig-macros" % "0.3.2" % "provided"
|
||||
val scalatest = "org.scalatest" %% "scalatest" % "3.1.0" % Test
|
||||
val akkatestkit = "com.typesafe.akka" %% "akka-testkit" % "2.6.5" % Test
|
||||
val uaparser = "org.uaparser" %% "uap-scala" % "0.11.0"
|
||||
|
||||
object flexmark {
|
||||
val version = "0.50.50"
|
||||
|
|
|
@ -114,28 +114,17 @@
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
.slist td {
|
||||
padding: 0;
|
||||
&:first-child {
|
||||
padding-left: 1em;
|
||||
width: 0%;
|
||||
.slist {
|
||||
tbody td {
|
||||
padding: 0;
|
||||
&:first-child {
|
||||
padding-left: 1em;
|
||||
width: 0%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.spy_ips {
|
||||
white-space: nowrap;
|
||||
> ul > li {
|
||||
list-style: inside disc;
|
||||
}
|
||||
ul p {
|
||||
thead th:first-child {
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
}
|
||||
li li {
|
||||
margin-left: 1em;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
.spy_fps {
|
||||
white-space: nowrap;
|
||||
a {
|
||||
font-family: monospace;
|
||||
|
@ -146,6 +135,12 @@
|
|||
i {
|
||||
margin-left: 1ch;
|
||||
}
|
||||
tr:hover {
|
||||
background: mix($c-brag, $c-bg-box, 20%);
|
||||
}
|
||||
}
|
||||
.spy_ips a {
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
.spy_uas {
|
||||
padding-left: 1em;
|
||||
|
@ -400,3 +395,7 @@
|
|||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#main-wrap.very-long {
|
||||
margin-bottom: 100vh;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ function streamLoad() {
|
|||
|
||||
function loadZone() {
|
||||
$zone.html(lichess.spinnerHtml).removeClass('none');
|
||||
$('#main-wrap').addClass('full-screen-force');
|
||||
$('#main-wrap').addClass('full-screen-force very-long');
|
||||
$zone.html('');
|
||||
streamLoad();
|
||||
window.addEventListener('scroll', onScroll);
|
||||
|
@ -30,7 +30,7 @@ function loadZone() {
|
|||
}
|
||||
function unloadZone() {
|
||||
$zone.addClass('none');
|
||||
$('#main-wrap').removeClass('full-screen-force');
|
||||
$('#main-wrap').removeClass('full-screen-force very-long');
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
scrollTo('#top');
|
||||
}
|
||||
|
@ -55,12 +55,14 @@ function userMod($zone) {
|
|||
$('#mz_menu > a:not(.available)').each(function() {
|
||||
$(this).toggleClass('available', !!$($(this).attr('href')).length);
|
||||
});
|
||||
makeReady('#mz_menu > a', (el, i) => {
|
||||
const id = el.href.replace(/.+(#\w+)$/, '$1'), n = '' + (i + 1);
|
||||
$(el).prepend(`<i>${n}</i>`);
|
||||
Mousetrap.bind(n, () => {
|
||||
console.log(id, n);
|
||||
scrollTo(id);
|
||||
makeReady('#mz_menu', el => {
|
||||
$(el).find('a').each(function(i) {
|
||||
const id = this.href.replace(/.+(#\w+)$/, '$1'), n = '' + (i + 1);
|
||||
$(this).prepend(`<i>${n}</i>`);
|
||||
Mousetrap.bind(n, () => {
|
||||
console.log(id, n);
|
||||
scrollTo(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -87,13 +89,22 @@ function userMod($zone) {
|
|||
makeReady('#mz_others table', el =>
|
||||
tablesort(el, { descending: true })
|
||||
);
|
||||
makeReady('.spy_fps table', el => {
|
||||
makeReady('#mz_identification table', el => {
|
||||
tablesort(el, { descending: true });
|
||||
$(el).find('.button').click(function() {
|
||||
$.post($(this).attr('href'));
|
||||
$(this).parent().parent().toggleClass('blocked');
|
||||
return false;
|
||||
});
|
||||
$(el).find('tr').on('mouseenter', function() {
|
||||
const v = $(this).find('td:first').text();
|
||||
$('#mz_others tbody tr').each(function() {
|
||||
$(this).toggleClass('none', !($(this).data('tags') || '').includes(v));
|
||||
});
|
||||
});
|
||||
$(el).on('mouseleave', function() {
|
||||
$('#mz_others tbody tr').removeClass('none');
|
||||
});
|
||||
});
|
||||
makeReady('#mz_others .more-others', el => {
|
||||
$(el).addClass('.ready').click(() => {
|
||||
|
@ -105,7 +116,7 @@ function userMod($zone) {
|
|||
|
||||
function makeReady(selector, f) {
|
||||
$zone.find(selector + ':not(.ready)').each(function(i) {
|
||||
f($(this).addClass('.ready')[0], i);
|
||||
f($(this).addClass('ready')[0], i);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue