mod zone WIP

pull/6762/head
Thibault Duplessis 2020-06-03 09:35:51 -06:00
parent 570201a2a6
commit f801000243
8 changed files with 95 additions and 72 deletions

View File

@ -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
)
}
)
)
)
)

View File

@ -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",

View File

@ -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 " > "

View File

@ -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) {

View File

@ -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],

View File

@ -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"

View File

@ -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;
}

View File

@ -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);
});
}