initial commit
All checks were successful
Gitea Action Maven build / build (push) Successful in 48s
Gitea Action Maven build / deploy (push) Successful in 8s

This commit is contained in:
Eduard Kuksa 2025-02-23 23:40:35 +07:00
commit 4066e0bec6
97 changed files with 7641 additions and 0 deletions

93
.gitea/workflows/main.yml Normal file
View File

@ -0,0 +1,93 @@
name: Gitea Action Maven build
on:
push:
branches: ['master']
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Set up Maven
uses: stCarolas/setup-maven@v5
with:
maven-version: 3.9.9
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'oracle'
cache: 'maven'
- name: Build, Test and Package with Maven
run: mvn -B verify --file pom.xml -e
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: searchengine-artifact
path: |
target/*.jar
Dockerfile
docker-compose.yml
- name: Create Maven settings.xml
run: |
cat <<EOF > ~/.m2/settings.xml
<settings>
<servers>
<server>
<id>gitea</id>
<username>${{ vars.OWNER }}</username>
<password>${{ secrets.ACCESS_TOKEN }}</password>
</server>
</servers>
</settings>
EOF
- name: Replace {owner} in pom.xml
run: sed -i "s/{owner}/${{ vars.OWNER }}/g" pom.xml
- name: Deploy to Gitea Packages
run: mvn deploy
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: searchengine-artifact
path: artifact
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_PRIVATE_KEY }}
known_hosts: ${{ secrets.KNOWN_HOSTS }}
- name: Create Directory on Local Server
run: ssh -T ${{ vars.DEPLOY_USER }}@${{ vars.DEPLOY_HOST }} mkdir -p ${{ vars.DEPLOY_PATH }}
- name: Deploy to Local Server
run: scp -r artifact/* ${{ vars.DEPLOY_USER }}@${{ vars.DEPLOY_HOST }}:${{ vars.DEPLOY_PATH }}
- name: Create .env file on Local Server
run: |
ssh -T ${{ vars.DEPLOY_USER }}@${{ vars.DEPLOY_HOST }} << EOF
echo "DB_USER=${{ secrets.DB_USER }}" > ${{ vars.DEPLOY_PATH }}/.env
echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> ${{ vars.DEPLOY_PATH }}/.env
echo "DB_URL=${{ vars.DB_URL }}" >> ${{ vars.DEPLOY_PATH }}/.env
EOF
- name: Build and Run Docker Image with Docker Compose
run: |
ssh -T ${{ vars.DEPLOY_USER }}@${{ vars.DEPLOY_HOST }} << EOF
cd ${{ vars.DEPLOY_PATH }}
docker compose down
docker compose up -d --build
EOF

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM openjdk:21-jdk-slim
WORKDIR /app
COPY target/searchengine-*.jar searchengine.jar
COPY .env .
EXPOSE 8080
CMD ["java", "-jar", "searchengine.jar"]

16
README.md Normal file
View File

@ -0,0 +1,16 @@
## Описание проекта
**Searchengine** — поисковый движок, созданный с использованием фреймворка **Spring Boot** . Этот проект позволяет пользователям искать информацию на предложенных сайтах. Поисковый движок индексирует сайты заранее заданные администратором и предоставляет возможность быстрого поиска по ключевым словам.
Основные функции:
- Индексация указанных сайтов.
- Выполнение полнотекстового поиска.
- Ранжирование результатов по релевантности.
- Простой пользовательский интерфейс для взаимодействия с системой.
## Технологии
Проект разработан с использованием следующих технологий:
- Java 21+
- Spring Boot 3.x
- Spring Data JPA
- Thymeleaf (для простого веб-интерфейса)
- Maven (управление зависимостями)
- MySQL (база данных)

24
application.yaml Normal file
View File

@ -0,0 +1,24 @@
server:
port: 8080
spring:
datasource:
username: ${DB_USER}
password: ${DB_PASSWORD}
url: ${DB_URL}
jpa:
hibernate:
# ddl-auto: update
# Ниже - временный вариант, таблица удаляется после завершения работы приложения
ddl-auto: create-drop
show-sql: true
open-in-view: false
indexing-settings:
sites:
- url: https://www.lenta.ru
name: Лента.ру
- url: https://www.skillbox.ru
name: Skillbox
- url: https://www.playback.ru
name: PlayBack.Ru

Binary file not shown.

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
image: searchengine
container_name: searchengine
ports:
- "8090:8080"
restart: on-failure:3

81
pom.xml Normal file
View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.kuksa</groupId>
<artifactId>searchengine</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>me.paulschwarz</groupId>
<artifactId>spring-dotenv</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>gitea</id>
<url>https://git.kuksa.dev/api/packages/{owner}/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitea</id>
<url>https://git.kuksa.dev/api/packages/{owner}/maven</url>
</repository>
<snapshotRepository>
<id>gitea</id>
<url>https://git.kuksa.dev/api/packages/{owner}/maven</url>
</snapshotRepository>
</distributionManagement>
</project>

View File

@ -0,0 +1,12 @@
package searchengine;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,11 @@
package searchengine.config;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class Site {
private String url;
private String name;
}

View File

@ -0,0 +1,16 @@
package searchengine.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "indexing-settings")
public class SitesList {
private List<Site> sites;
}

View File

@ -0,0 +1,31 @@
package searchengine.controllers;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import searchengine.config.SitesList;
import searchengine.dto.statistics.StatisticsResponse;
import searchengine.services.StatisticsService;
@RestController
@RequestMapping("/api")
public class ApiController {
private final StatisticsService statisticsService;
public ApiController(StatisticsService statisticsService) {
this.statisticsService = statisticsService;
}
@GetMapping("/statistics")
public ResponseEntity<StatisticsResponse> statistics() {
return ResponseEntity.ok(statisticsService.getStatistics());
}
@GetMapping("/startIndexing")
public ResponseEntity<StatisticsResponse> startIndexing() {
System.out.println(new SitesList().getSites());
return ResponseEntity.ok(statisticsService.getStatistics());
}
}

View File

@ -0,0 +1,18 @@
package searchengine.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class DefaultController {
/**
* Метод формирует страницу из HTML-файла index.html,
* который находится в папке resources/templates.
* Это делает библиотека Thymeleaf.
*/
@RequestMapping("/")
public String index() {
return "index";
}
}

View File

@ -0,0 +1,14 @@
package searchengine.dto.statistics;
import lombok.Data;
@Data
public class DetailedStatisticsItem {
private String url;
private String name;
private String status;
private long statusTime;
private String error;
private int pages;
private int lemmas;
}

View File

@ -0,0 +1,11 @@
package searchengine.dto.statistics;
import lombok.Data;
import java.util.List;
@Data
public class StatisticsData {
private TotalStatistics total;
private List<DetailedStatisticsItem> detailed;
}

View File

@ -0,0 +1,9 @@
package searchengine.dto.statistics;
import lombok.Data;
@Data
public class StatisticsResponse {
private boolean result;
private StatisticsData statistics;
}

View File

@ -0,0 +1,11 @@
package searchengine.dto.statistics;
import lombok.Data;
@Data
public class TotalStatistics {
private int sites;
private int pages;
private int lemmas;
private boolean indexing;
}

View File

@ -0,0 +1,30 @@
package searchengine.model;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "page", indexes = @Index(columnList = "path"))
public class PageEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private int id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "site_id", nullable = false)
private SiteEntity site;
@Column(columnDefinition = "TEXT NOT NULL, UNIQUE KEY INDEX_PATH (path(255))") // Unique index version
// @Column(columnDefinition = "TEXT NOT NULL, INDEX PATH_INDEX USING BTREE(path(255))") // Non-unique index version
private String path;
@Column(nullable = false)
private int code;
@Column(nullable = false, columnDefinition = "MEDIUMTEXT")
private String content;
}

View File

@ -0,0 +1,34 @@
package searchengine.model;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
@Entity
@Table(name = "site")
public class SiteEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private int id;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private StatusType status;
@Column(nullable = false)
private LocalDateTime status_time;
@Column(columnDefinition = "TEXT")
private String last_error;
@Column(nullable = false, columnDefinition = "VARCHAR(255)")
private String url;
@Column(nullable = false, columnDefinition = "VARCHAR(255)")
private String name;
}

View File

@ -0,0 +1,7 @@
package searchengine.model;
public enum StatusType {
INDEXING,
INDEXED,
FAILED
}

View File

@ -0,0 +1,9 @@
package searchengine.repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import searchengine.model.PageEntity;
@Repository
public interface PageRepository extends CrudRepository<PageEntity, Integer> {
}

View File

@ -0,0 +1,9 @@
package searchengine.repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import searchengine.model.SiteEntity;
@Repository
public interface SiteRepository extends CrudRepository<SiteEntity, Integer> {
}

View File

@ -0,0 +1,4 @@
package searchengine.services;
public interface IndexingService {
}

View File

@ -0,0 +1,16 @@
package searchengine.services;
public class IndexingServiceImpl implements IndexingService {
boolean indexingIsRunning = false;
public void startIndexing() {
if (indexingIsRunning) {
System.out.println("Индексация уже запущена");
return;
}
// Логика для запуска индексации или перенидексации сайтов
System.out.println("Запуск индексации");
indexingIsRunning = true;
System.out.println("Индексация запущена");
}
}

View File

@ -0,0 +1,7 @@
package searchengine.services;
import searchengine.dto.statistics.StatisticsResponse;
public interface StatisticsService {
StatisticsResponse getStatistics();
}

View File

@ -0,0 +1,64 @@
package searchengine.services;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import searchengine.config.Site;
import searchengine.config.SitesList;
import searchengine.dto.statistics.DetailedStatisticsItem;
import searchengine.dto.statistics.StatisticsData;
import searchengine.dto.statistics.StatisticsResponse;
import searchengine.dto.statistics.TotalStatistics;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Service
@RequiredArgsConstructor
public class StatisticsServiceImpl implements StatisticsService {
private final Random random = new Random();
private final SitesList sites;
@Override
public StatisticsResponse getStatistics() {
String[] statuses = { "INDEXED", "FAILED", "INDEXING" };
String[] errors = {
"Ошибка индексации: главная страница сайта не доступна",
"Ошибка индексации: сайт не доступен",
""
};
TotalStatistics total = new TotalStatistics();
total.setSites(sites.getSites().size());
total.setIndexing(true);
List<DetailedStatisticsItem> detailed = new ArrayList<>();
List<Site> sitesList = sites.getSites();
for(int i = 0; i < sitesList.size(); i++) {
Site site = sitesList.get(i);
DetailedStatisticsItem item = new DetailedStatisticsItem();
item.setName(site.getName());
item.setUrl(site.getUrl());
int pages = random.nextInt(1_000);
int lemmas = pages * random.nextInt(1_000);
item.setPages(pages);
item.setLemmas(lemmas);
item.setStatus(statuses[i % 3]);
item.setError(errors[i % 3]);
item.setStatusTime(System.currentTimeMillis() -
(random.nextInt(10_000)));
total.setPages(total.getPages() + pages);
total.setLemmas(total.getLemmas() + lemmas);
detailed.add(item);
}
StatisticsResponse response = new StatisticsResponse();
StatisticsData data = new StatisticsData();
data.setTotal(total);
data.setDetailed(detailed);
response.setStatistics(data);
response.setResult(true);
return response;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat Black";
src: url("Montserrat-Black.eot"); /* IE9 */
src: url("Montserrat-Black.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-Black.woff") format("woff"), /* chrome, firefox */
url("Montserrat-Black.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-Black.svg#Montserrat Black") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat";
src: url("Montserrat-Bold.eot"); /* IE9 */
src: url("Montserrat-Bold.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-Bold.woff") format("woff"), /* chrome, firefox */
url("Montserrat-Bold.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-Bold.svg#Montserrat") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat ExtraBold";
src: url("Montserrat-ExtraBold.eot"); /* IE9 */
src: url("Montserrat-ExtraBold.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-ExtraBold.woff") format("woff"), /* chrome, firefox */
url("Montserrat-ExtraBold.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-ExtraBold.svg#Montserrat ExtraBold") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat ExtraLight";
src: url("Montserrat-ExtraLight.eot"); /* IE9 */
src: url("Montserrat-ExtraLight.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-ExtraLight.woff") format("woff"), /* chrome, firefox */
url("Montserrat-ExtraLight.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-ExtraLight.svg#Montserrat ExtraLight") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat Light";
src: url("Montserrat-Light.eot"); /* IE9 */
src: url("Montserrat-Light.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-Light.woff") format("woff"), /* chrome, firefox */
url("Montserrat-Light.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-Light.svg#Montserrat Light") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat Medium";
src: url("Montserrat-Medium.eot"); /* IE9 */
src: url("Montserrat-Medium.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-Medium.woff") format("woff"), /* chrome, firefox */
url("Montserrat-Medium.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-Medium.svg#Montserrat Medium") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat";
src: url("Montserrat-Regular.eot"); /* IE9 */
src: url("Montserrat-Regular.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-Regular.woff") format("woff"), /* chrome, firefox */
url("Montserrat-Regular.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-Regular.svg#Montserrat") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat SemiBold";
src: url("Montserrat-SemiBold.eot"); /* IE9 */
src: url("Montserrat-SemiBold.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-SemiBold.woff") format("woff"), /* chrome, firefox */
url("Montserrat-SemiBold.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-SemiBold.svg#Montserrat SemiBold") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,12 @@
@font-face {
font-family: "Montserrat Thin";
src: url("Montserrat-Thin.eot"); /* IE9 */
src: url("Montserrat-Thin.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("Montserrat-Thin.woff") format("woff"), /* chrome, firefox */
url("Montserrat-Thin.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("Montserrat-Thin.svg#Montserrat Thin") format("svg"); /* iOS 4.1- */
font-style: normal;
font-weight: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 386.257 386.257"><path d="M0 96.879l193.129 192.5 193.128-192.5z"/></svg>

After

Width:  |  Height:  |  Size: 126 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 0C115.3 0 0 115.3 0 256s115.3 256 256 256 256-115.3 256-256S396.7 0 256 0z" fill="#ff3636"/><path d="M512 256c0 140.7-115.3 256-256 256V0c140.7 0 256 115.3 256 256z" fill="#f40000"/><path fill="#e7e7e7" d="M298.299 256l84.901 84.901-42.299 42.299L256 298.299 171.099 383.2 128.8 340.901 213.701 256 128.8 171.099l42.299-42.299L256 213.701l84.901-84.901 42.299 42.299z"/><path fill="#d3d3d8" d="M298.299 256l84.901 84.901-42.299 42.299L256 298.299v-84.598l84.901-84.901 42.299 42.299z"/></svg>

After

Width:  |  Height:  |  Size: 568 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 0C115.3 0 0 115.3 0 256s115.3 256 256 256 256-115.3 256-256S396.7 0 256 0z" fill="#7d0"/><path d="M512 256c0 140.7-115.3 256-256 256V0c140.7 0 256 115.3 256 256z" fill="#6b0"/><path fill="#e7e7e7" d="M401.8 212.5L226 388.299l-126.301-126 42.602-42.6 83.699 84 30-30L359.5 170.2z"/><path fill="#d3d3d8" d="M401.8 212.5L256 358.299v-84.6L359.5 170.2z"/></svg>

After

Width:  |  Height:  |  Size: 433 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#ffb74f" d="M432.106 250.534v219.487H296.578V336.975h-75.179v133.046H79.894V250.534L256 115.075z"/><path d="M439.485 183.135V90.306h-74.167v35.772L256 41.979 0 238.92l53.633 69.712L256 152.959l202.367 155.672L512 238.92l-72.515-55.785z" fill="#ff7d3c"/><path fill="#ff9a00" d="M432.106 250.534v219.487H296.578V336.975H256v-221.9z"/><path fill="#ff4e19" d="M512 238.92l-53.633 69.712L256 152.959V41.979l109.318 84.099V90.306h74.167v92.829z"/></svg>

After

Width:  |  Height:  |  Size: 521 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256c0 141.384 114.615 256 256 256l22.261-256L256 0C114.615 0 0 114.615 0 256z" fill="#bfdcff"/><path d="M256 0v512c141.384 0 256-114.616 256-256S397.384 0 256 0z" fill="#8bc0ff"/><path fill="#3897ff" d="M155.826 144.696h66.783v222.609h-66.783z"/><path fill="#2d79cc" d="M289.391 144.696h66.783v222.609h-66.783z"/></svg>

After

Width:  |  Height:  |  Size: 393 B

View File

@ -0,0 +1 @@
<svg height="512" width="512" xmlns="http://www.w3.org/2000/svg"><path d="M179.907 317.89l63.63-63.63 17.675 17.676-63.63 63.63z" fill="#83a3ab"/><path d="M176.371 314.355l63.63-63.63 10.605 10.605-63.63 63.63zM66.192 403.381l-53.033 95.46c17.545 17.545 46.094 17.546 63.64 0l21.213-21.213z" fill="#93b7c0"/><path d="M34.373 413.988L13.16 435.201c-17.546 17.546-17.545 46.094 0 63.64l74.246-74.246z" fill="#acd5df"/><path d="M98.012 477.627L240.06 335.58l-31.82-31.82-92.237 49.811-49.811 92.237z" fill="#3a6fd8"/><path d="M34.335 413.954L176.36 271.93l31.815 31.815L66.15 445.769z" fill="#3b88f5"/><path d="M463.526 48.474l-175.539 58.513-58.513 175.539c64.632 64.632 169.421 64.632 234.052 0s64.632-169.42 0-234.052z" fill="#93b7c0"/><path d="M229.474 48.474c-64.632 64.632-64.632 169.421 0 234.052L463.526 48.474c-64.632-64.632-169.42-64.632-234.052 0z" fill="#acd5df"/><path d="M421.1 90.9l-111.9 37.3-37.3 111.9c41.2 41.2 107.999 41.2 149.2 0s41.2-107.999 0-149.2z" fill="#c4f3ff"/><path d="M271.9 90.9c-41.2 41.2-41.2 107.999 0 149.2L421.1 90.9c-41.201-41.2-107.999-41.2-149.2 0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
/*
jQuery Masked Input Plugin
Copyright (c) 2007 - 2015 Josh Bush (digitalbush.com)
Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
Version: 1.4.1
*/
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b,c=navigator.userAgent,d=/iphone/i.test(c),e=/chrome/i.test(c),f=/android/i.test(c);a.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},autoclear:!0,dataName:"rawMaskFn",placeholder:"_"},a.fn.extend({caret:function(a,b){var c;if(0!==this.length&&!this.is(":hidden"))return"number"==typeof a?(b="number"==typeof b?b:a,this.each(function(){this.setSelectionRange?this.setSelectionRange(a,b):this.createTextRange&&(c=this.createTextRange(),c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",a),c.select())})):(this[0].setSelectionRange?(a=this[0].selectionStart,b=this[0].selectionEnd):document.selection&&document.selection.createRange&&(c=document.selection.createRange(),a=0-c.duplicate().moveStart("character",-1e5),b=a+c.text.length),{begin:a,end:b})},unmask:function(){return this.trigger("unmask")},mask:function(c,g){var h,i,j,k,l,m,n,o;if(!c&&this.length>0){h=a(this[0]);var p=h.data(a.mask.dataName);return p?p():void 0}return g=a.extend({autoclear:a.mask.autoclear,placeholder:a.mask.placeholder,completed:null},g),i=a.mask.definitions,j=[],k=n=c.length,l=null,a.each(c.split(""),function(a,b){"?"==b?(n--,k=a):i[b]?(j.push(new RegExp(i[b])),null===l&&(l=j.length-1),k>a&&(m=j.length-1)):j.push(null)}),this.trigger("unmask").each(function(){function h(){if(g.completed){for(var a=l;m>=a;a++)if(j[a]&&C[a]===p(a))return;g.completed.call(B)}}function p(a){return g.placeholder.charAt(a<g.placeholder.length?a:0)}function q(a){for(;++a<n&&!j[a];);return a}function r(a){for(;--a>=0&&!j[a];);return a}function s(a,b){var c,d;if(!(0>a)){for(c=a,d=q(b);n>c;c++)if(j[c]){if(!(n>d&&j[c].test(C[d])))break;C[c]=C[d],C[d]=p(d),d=q(d)}z(),B.caret(Math.max(l,a))}}function t(a){var b,c,d,e;for(b=a,c=p(a);n>b;b++)if(j[b]){if(d=q(b),e=C[b],C[b]=c,!(n>d&&j[d].test(e)))break;c=e}}function u(){var a=B.val(),b=B.caret();if(o&&o.length&&o.length>a.length){for(A(!0);b.begin>0&&!j[b.begin-1];)b.begin--;if(0===b.begin)for(;b.begin<l&&!j[b.begin];)b.begin++;B.caret(b.begin,b.begin)}else{for(A(!0);b.begin<n&&!j[b.begin];)b.begin++;B.caret(b.begin,b.begin)}h()}function v(){A(),B.val()!=E&&B.change()}function w(a){if(!B.prop("readonly")){var b,c,e,f=a.which||a.keyCode;o=B.val(),8===f||46===f||d&&127===f?(b=B.caret(),c=b.begin,e=b.end,e-c===0&&(c=46!==f?r(c):e=q(c-1),e=46===f?q(e):e),y(c,e),s(c,e-1),a.preventDefault()):13===f?v.call(this,a):27===f&&(B.val(E),B.caret(0,A()),a.preventDefault())}}function x(b){if(!B.prop("readonly")){var c,d,e,g=b.which||b.keyCode,i=B.caret();if(!(b.ctrlKey||b.altKey||b.metaKey||32>g)&&g&&13!==g){if(i.end-i.begin!==0&&(y(i.begin,i.end),s(i.begin,i.end-1)),c=q(i.begin-1),n>c&&(d=String.fromCharCode(g),j[c].test(d))){if(t(c),C[c]=d,z(),e=q(c),f){var k=function(){a.proxy(a.fn.caret,B,e)()};setTimeout(k,0)}else B.caret(e);i.begin<=m&&h()}b.preventDefault()}}}function y(a,b){var c;for(c=a;b>c&&n>c;c++)j[c]&&(C[c]=p(c))}function z(){B.val(C.join(""))}function A(a){var b,c,d,e=B.val(),f=-1;for(b=0,d=0;n>b;b++)if(j[b]){for(C[b]=p(b);d++<e.length;)if(c=e.charAt(d-1),j[b].test(c)){C[b]=c,f=b;break}if(d>e.length){y(b+1,n);break}}else C[b]===e.charAt(d)&&d++,k>b&&(f=b);return a?z():k>f+1?g.autoclear||C.join("")===D?(B.val()&&B.val(""),y(0,n)):z():(z(),B.val(B.val().substring(0,f+1))),k?b:l}var B=a(this),C=a.map(c.split(""),function(a,b){return"?"!=a?i[a]?p(b):a:void 0}),D=C.join(""),E=B.val();B.data(a.mask.dataName,function(){return a.map(C,function(a,b){return j[b]&&a!=p(b)?a:null}).join("")}),B.one("unmask",function(){B.off(".mask").removeData(a.mask.dataName)}).on("focus.mask",function(){if(!B.prop("readonly")){clearTimeout(b);var a;E=B.val(),a=A(),b=setTimeout(function(){B.get(0)===document.activeElement&&(z(),a==c.replace("?","").length?B.caret(0,a):B.caret(a))},10)}}).on("blur.mask",v).on("keydown.mask",w).on("keypress.mask",x).on("input.mask paste.mask",function(){B.prop("readonly")||setTimeout(function(){var a=A(!0);B.caret(a),h()},0)}),e&&f&&B.off("input.mask").on("input.mask",u),A()})}})});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,162 @@
<!--END-->
<!--END--><!DOCTYPE html><!--[if IE 7]>
<html class="ie7" lang="ru">
<![endif]-->
<!--[if IE 8]>
<html class="ie8" lang="ru">
<![endif]-->
<!--[if IE 9]>
<html class="ie9" lang="ru">
<![endif]-->
<!--[if gt IE 9]><!--> <html lang="ru"> <!--<![endif]-->
<head>
<title>Site Search Engine</title>
<meta name="description" content="Site Search Engine">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<!--meta( http-equiv="cache-control" content="no-cache")-->
<!--meta( http-equiv="expires" content="0")-->
<!--link(rel="preload" href="assets/css/extra.min.css?v=" + version as="style" crossorigin="anonymous")-->
<link href="favicon.ico" rel="shortcut icon">
<link rel="preload" href="/assets/fonts/Montserrat/Montserrat-SemiBold.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/Montserrat/Montserrat-Light.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/Montserrat/Montserrat-Medium.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="/assets/fonts/Montserrat/Montserrat-ExtraBold.woff2" as="font" crossorigin="anonymous">
<link rel="stylesheet" href="/assets/css/fonts.css?v=82368483">
<link rel="stylesheet" href="/assets/css/basic.css?v=82368483">
<link rel="stylesheet" href="/assets/css/extra.css?v=82368483"><!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
<script>
var backendApiUrl = 'api'
</script>
<script defer src="/assets/js/scripts.js?v=38874865"></script>
</head>
<body class="Site">
<!--if lt IE 8
p.error-browser
| Ваш браузер&nbsp;
em устарел!&nbsp;
a(href="http://browsehappy.com/") Выберите новую версию
+s
| браузера здесь&nbsp;
| для правильного отображения сайта.
-->
<div class="Site-loader">
<div class="Site-loader-block">
<div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
</div>
</div>
<div class="Middle">
<div class="wrap">
<div class="Site-loadingIsComplete">
<main class="Tabs Tabs_column Middle-main">
<div class="Column">
<div class="Tabs-links"><a class="Tabs-link Tabs-link_ACTIVE" href="#dashboard"><img class="Tabs-icon" src="/assets/img/icons/dashboard.svg" alt="dashboard.svg"/><span class="Tabs-linkText">Dashboard</span></a><a class="Tabs-link" href="#management"><img class="Tabs-icon" src="/assets/img/icons/management.svg" alt="management.svg"/><span class="Tabs-linkText">Management</span></a><a class="Tabs-link" href="#search"><img class="Tabs-icon" src="/assets/img/icons/search.svg" alt="search.svg"/><span class="Tabs-linkText">Search</span></a>
</div>
</div>
<div class="Tabs-wrap">
<div class="Tabs-block" id="dashboard">
<div class="Section">
<div class="Section-header">
<h2 class="Section-title">Dashboard
</h2>
</div>
<div class="Statistics">
<div class="Statistics-info">
<div class="Statistics-block"><span class="Statistics-amount" id="totalSites"></span><span class="Statistics-title">sites</span>
</div>
<div class="Statistics-block"><span class="Statistics-amount" id="totalPages"></span><span class="Statistics-title">pages</span>
</div>
<div class="Statistics-block"><span class="Statistics-amount" id="totalLemmas"></span><span class="Statistics-title">lemmas</span>
</div>
</div>
<div class="HideBlock Statistics-example">
<header class="HideBlock-header HideBlock-trigger">
<strong class="HideBlock-title"><span class="Statistics-status"></span>
</strong>
<button class="HideBlock-btn" type="button">
</button>
</header>
<div class="HideBlock-content">
<p class="Statistics-description">
</p>
</div>
</div>
</div>
</div>
</div>
<div class="Tabs-block" id="management">
<div class="Section">
<div class="Section-header">
<h2 class="Section-title">Management
</h2>
</div>
<div>
<button class="btn btn_primary API-startIndexing" data-btntype="check" data-check="false" data-alttext="Stop indexing" data-send="startIndexing" data-altsend="stopIndexing"><span class="btn-content">Start indexing</span>
</button>
</div><br>
<div class="UpdatePageBlock">
<h3>Add/update page:
</h3>
<form class="form form_close" action="#" method="post" data-send="indexPage">
<div class="form-group form-group_row">
<input class="form-input" id="page" name="page" type="text"/>
<button class="btn btn_primary form-btn" type="submit">Add/update
</button>
</div>
</form>
</div>
</div>
</div>
<div class="Tabs-block" id="search">
<div class="Section">
<div class="Section-header">
<h2 class="Section-title">Search
</h2>
</div>
<form class="form form_close" action="#" method="post" data-send="search" data-sendlimit="10">
<div class="form-group">
<div class="form-selectWrap">
<!-- - var options = setOptions(items, ['value', 'selected', 'disabled']);-->
<select class="form-select" name="site">
<option value="" selected="selected">All sites
</option>
</select>
</div>
</div>
<div class="form-group form-group_row">
<input class="form-input" id="query" name="query" type="text" placeholder="Query"/>
<button class="btn btn_primary form-btn" type="submit">Search
</button>
</div>
</form>
<div class="SearchResult">
<strong class="SearchResult-title">Found&#32;<span class="SearchResult-amount">0</span>&#32;results
</strong>
<div class="SearchResult-content">
</div>
<div class="SearchResult-footer SearchResult-footer_hide">
<button class="btn btn_primary" data-send="search" data-sendtype="next">Show more<span class="SearchResult-remain">(0)</span>
</button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
</div>
<script src="/assets/plg/jQuery/jquery-3.5.1.min.js"></script>
</body></html>