forked from p497jmxr8/djiango
Compare commits
No commits in common. 'develop' and 'main' have entirely different histories.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
@ -0,0 +1,176 @@
|
||||
name: 自动部署到生产环境
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Django CI"]
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: '部署环境'
|
||||
required: true
|
||||
default: 'production'
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
- staging
|
||||
image_tag:
|
||||
description: '镜像标签 (默认: latest)'
|
||||
required: false
|
||||
default: 'latest'
|
||||
type: string
|
||||
skip_tests:
|
||||
description: '跳过测试直接部署'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
REGISTRY: registry.cn-shenzhen.aliyuncs.com
|
||||
IMAGE_NAME: liangliangyy/djangoblog
|
||||
NAMESPACE: djangoblog
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: 构建镜像并部署到生产环境
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置部署参数
|
||||
id: deploy-params
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "trigger_type=手动触发" >> $GITHUB_OUTPUT
|
||||
echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT
|
||||
echo "image_tag=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT
|
||||
echo "skip_tests=${{ github.event.inputs.skip_tests }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "trigger_type=CI自动触发" >> $GITHUB_OUTPUT
|
||||
echo "environment=production" >> $GITHUB_OUTPUT
|
||||
echo "image_tag=latest" >> $GITHUB_OUTPUT
|
||||
echo "skip_tests=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: 显示部署信息
|
||||
run: |
|
||||
echo "🚀 部署信息:"
|
||||
echo " 触发方式: ${{ steps.deploy-params.outputs.trigger_type }}"
|
||||
echo " 部署环境: ${{ steps.deploy-params.outputs.environment }}"
|
||||
echo " 镜像标签: ${{ steps.deploy-params.outputs.image_tag }}"
|
||||
echo " 跳过测试: ${{ steps.deploy-params.outputs.skip_tests }}"
|
||||
|
||||
- name: 设置Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 登录私有镜像仓库
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: 提取镜像元数据
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=${{ steps.deploy-params.outputs.image_tag }}
|
||||
|
||||
- name: 构建并推送Docker镜像
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: 部署到生产服务器
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_PORT || 22 }}
|
||||
script: |
|
||||
echo "🚀 开始部署 DjangoBlog..."
|
||||
|
||||
# 检查kubectl是否可用
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo "❌ 错误: kubectl 未安装或不在PATH中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查命名空间是否存在
|
||||
if ! kubectl get namespace ${{ env.NAMESPACE }} &> /dev/null; then
|
||||
echo "❌ 错误: 命名空间 ${{ env.NAMESPACE }} 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 更新deployment镜像
|
||||
echo "📦 更新deployment镜像为: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }}"
|
||||
kubectl set image deployment/djangoblog \
|
||||
djangoblog=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }} \
|
||||
-n ${{ env.NAMESPACE }}
|
||||
|
||||
# 重启deployment
|
||||
echo "🔄 重启deployment..."
|
||||
kubectl -n ${{ env.NAMESPACE }} rollout restart deployment djangoblog
|
||||
|
||||
# 等待deployment完成
|
||||
echo "⏳ 等待deployment完成..."
|
||||
kubectl rollout status deployment/djangoblog -n ${{ env.NAMESPACE }} --timeout=300s
|
||||
|
||||
# 检查deployment状态
|
||||
echo "✅ 检查deployment状态..."
|
||||
kubectl get deployment djangoblog -n ${{ env.NAMESPACE }}
|
||||
kubectl get pods -l app=djangoblog -n ${{ env.NAMESPACE }}
|
||||
|
||||
echo "🎉 部署完成!"
|
||||
|
||||
- name: 发送部署通知
|
||||
if: always()
|
||||
run: |
|
||||
# 设置通知内容
|
||||
if [ "${{ job.status }}" = "success" ]; then
|
||||
TITLE="✅ DjangoBlog部署成功"
|
||||
STATUS="成功"
|
||||
else
|
||||
TITLE="❌ DjangoBlog部署失败"
|
||||
STATUS="失败"
|
||||
fi
|
||||
|
||||
MESSAGE="部署状态: ${STATUS}
|
||||
触发方式: ${{ steps.deploy-params.outputs.trigger_type }}
|
||||
部署环境: ${{ steps.deploy-params.outputs.environment }}
|
||||
镜像标签: ${{ steps.deploy-params.outputs.image_tag }}
|
||||
提交者: ${{ github.actor }}
|
||||
时间: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
# 发送Server酱通知
|
||||
if [ -n "${{ secrets.SERVERCHAN_KEY }}" ]; then
|
||||
echo "{\"title\": \"${TITLE}\", \"desp\": \"${MESSAGE}\"}" > /tmp/serverchan.json
|
||||
|
||||
curl --location "https://sctapi.ftqq.com/${{ secrets.SERVERCHAN_KEY }}.send" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data @/tmp/serverchan.json \
|
||||
--silent > /dev/null
|
||||
|
||||
rm -f /tmp/serverchan.json
|
||||
echo "📱 部署通知已发送"
|
||||
fi
|
||||
@ -0,0 +1,371 @@
|
||||
name: Django CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# 标准测试 - Python 3.10
|
||||
- python-version: "3.10"
|
||||
test-type: "standard"
|
||||
database: "mysql"
|
||||
elasticsearch: false
|
||||
coverage: false
|
||||
|
||||
# 标准测试 - Python 3.11
|
||||
- python-version: "3.11"
|
||||
test-type: "standard"
|
||||
database: "mysql"
|
||||
elasticsearch: false
|
||||
coverage: false
|
||||
|
||||
# 完整测试 - 包含ES和覆盖率
|
||||
- python-version: "3.11"
|
||||
test-type: "full"
|
||||
database: "mysql"
|
||||
elasticsearch: true
|
||||
coverage: true
|
||||
|
||||
# Docker构建测试
|
||||
- python-version: "3.11"
|
||||
test-type: "docker"
|
||||
database: "none"
|
||||
elasticsearch: false
|
||||
coverage: false
|
||||
|
||||
name: Test (${{ matrix.test-type }}, Python ${{ matrix.python-version }})
|
||||
|
||||
steps:
|
||||
- name: Checkout代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置测试信息
|
||||
id: test-info
|
||||
run: |
|
||||
echo "test_name=${{ matrix.test-type }}-py${{ matrix.python-version }}" >> $GITHUB_OUTPUT
|
||||
if [ "${{ matrix.test-type }}" = "docker" ]; then
|
||||
echo "skip_python_setup=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "skip_python_setup=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# MySQL数据库设置 (只有需要数据库的测试才执行)
|
||||
- name: 启动MySQL数据库
|
||||
if: matrix.database == 'mysql'
|
||||
uses: samin/mysql-action@v1.3
|
||||
with:
|
||||
host port: 3306
|
||||
container port: 3306
|
||||
character set server: utf8mb4
|
||||
collation server: utf8mb4_general_ci
|
||||
mysql version: latest
|
||||
mysql root password: root
|
||||
mysql database: djangoblog
|
||||
mysql user: root
|
||||
mysql password: root
|
||||
|
||||
# Elasticsearch设置 (只有完整测试才执行)
|
||||
- name: 配置系统参数 (ES)
|
||||
if: matrix.elasticsearch == true
|
||||
run: |
|
||||
sudo swapoff -a
|
||||
sudo sysctl -w vm.swappiness=1
|
||||
sudo sysctl -w fs.file-max=262144
|
||||
sudo sysctl -w vm.max_map_count=262144
|
||||
|
||||
- name: 启动Elasticsearch
|
||||
if: matrix.elasticsearch == true
|
||||
uses: miyataka/elasticsearch-github-actions@1
|
||||
with:
|
||||
stack-version: '7.12.1'
|
||||
plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip'
|
||||
|
||||
# Python环境设置 (Docker测试跳过)
|
||||
- name: 设置Python ${{ matrix.python-version }}
|
||||
if: steps.test-info.outputs.skip_python_setup == 'false'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'requirements.txt'
|
||||
|
||||
# 多层缓存策略优化
|
||||
- name: 缓存Python依赖
|
||||
if: steps.test-info.outputs.skip_python_setup == 'false'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
.pytest_cache
|
||||
key: ${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('**/pyproject.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-
|
||||
${{ runner.os }}-python-${{ matrix.python-version }}-
|
||||
${{ runner.os }}-python-
|
||||
|
||||
# Django缓存优化 (测试数据库等)
|
||||
- name: 缓存Django资源
|
||||
if: matrix.test-type != 'docker'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.coverage*
|
||||
htmlcov/
|
||||
.django_cache/
|
||||
key: ${{ runner.os }}-django-${{ matrix.test-type }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-django-${{ matrix.test-type }}-
|
||||
${{ runner.os }}-django-
|
||||
|
||||
- name: 安装Python依赖
|
||||
if: steps.test-info.outputs.skip_python_setup == 'false'
|
||||
run: |
|
||||
echo "📦 安装Python依赖 (Python ${{ matrix.python-version }})"
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
|
||||
# 安装基础依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 根据测试类型安装额外依赖
|
||||
if [ "${{ matrix.coverage }}" = "true" ]; then
|
||||
echo "📊 安装覆盖率工具"
|
||||
pip install coverage[toml]
|
||||
fi
|
||||
|
||||
# 验证关键依赖
|
||||
echo "🔍 验证关键依赖安装"
|
||||
python -c "import django; print(f'Django version: {django.get_version()}')"
|
||||
python -c "import MySQLdb; print('MySQL client: OK')" || python -c "import pymysql; print('PyMySQL client: OK')"
|
||||
|
||||
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
||||
python -c "import elasticsearch; print('Elasticsearch client: OK')"
|
||||
fi
|
||||
|
||||
# Django环境准备
|
||||
- name: 准备Django环境
|
||||
if: matrix.test-type != 'docker'
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
||||
run: |
|
||||
echo "🔧 准备Django测试环境"
|
||||
|
||||
# 等待数据库就绪
|
||||
echo "⏳ 等待MySQL数据库启动..."
|
||||
for i in {1..30}; do
|
||||
if python -c "import MySQLdb; MySQLdb.connect(host='127.0.0.1', user='root', passwd='root', db='djangoblog')" 2>/dev/null; then
|
||||
echo "✅ MySQL数据库连接成功"
|
||||
break
|
||||
fi
|
||||
echo "🔄 等待数据库启动... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 等待Elasticsearch就绪 (如果启用)
|
||||
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
||||
echo "⏳ 等待Elasticsearch启动..."
|
||||
for i in {1..30}; do
|
||||
if curl -s http://127.0.0.1:9200/_cluster/health | grep -q '"status":"green"\|"status":"yellow"'; then
|
||||
echo "✅ Elasticsearch连接成功"
|
||||
break
|
||||
fi
|
||||
echo "🔄 等待Elasticsearch启动... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
|
||||
# Django测试执行
|
||||
- name: 执行数据库迁移
|
||||
if: matrix.test-type != 'docker'
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
||||
run: |
|
||||
echo "🗄️ 执行数据库迁移"
|
||||
|
||||
# 检查迁移文件
|
||||
echo "📋 检查待应用的迁移..."
|
||||
python manage.py showmigrations
|
||||
|
||||
# 检查是否有未创建的迁移
|
||||
python manage.py makemigrations --check --verbosity 2
|
||||
|
||||
# 执行迁移
|
||||
python manage.py migrate --verbosity 2
|
||||
|
||||
echo "✅ 数据库迁移完成"
|
||||
|
||||
- name: 运行Django测试
|
||||
if: matrix.test-type != 'docker'
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
||||
run: |
|
||||
echo "🧪 开始执行 ${{ matrix.test-type }} 测试 (Python ${{ matrix.python-version }})"
|
||||
|
||||
# 显示Django配置信息
|
||||
python manage.py diffsettings | head -20
|
||||
|
||||
# 运行测试
|
||||
if [ "${{ matrix.coverage }}" = "true" ]; then
|
||||
echo "📊 运行测试并生成覆盖率报告"
|
||||
coverage run --source='.' --omit='*/venv/*,*/migrations/*,*/tests/*,manage.py' manage.py test --verbosity=2
|
||||
|
||||
echo "📈 生成覆盖率报告"
|
||||
coverage xml
|
||||
coverage report --show-missing
|
||||
coverage html
|
||||
|
||||
echo "📋 覆盖率统计:"
|
||||
coverage report | tail -1
|
||||
else
|
||||
echo "🧪 运行标准测试"
|
||||
python manage.py test --verbosity=2 --failfast
|
||||
fi
|
||||
|
||||
echo "✅ 测试执行完成"
|
||||
|
||||
# 覆盖率报告上传 (只有完整测试才执行)
|
||||
- name: 上传覆盖率到Codecov
|
||||
if: matrix.coverage == true && success()
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-${{ steps.test-info.outputs.test_name }}
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
- name: 上传覆盖率到Codecov (备用)
|
||||
if: matrix.coverage == true && failure()
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-${{ steps.test-info.outputs.test_name }}-fallback
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
# Docker构建测试
|
||||
- name: 设置QEMU
|
||||
if: matrix.test-type == 'docker'
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: 设置Docker Buildx
|
||||
if: matrix.test-type == 'docker'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker构建测试
|
||||
if: matrix.test-type == 'docker'
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: djangoblog/djangoblog:test-${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# 收集测试工件 (失败时收集调试信息)
|
||||
- name: 收集测试工件
|
||||
if: failure() && matrix.test-type != 'docker'
|
||||
run: |
|
||||
echo "🔍 收集测试失败的调试信息"
|
||||
|
||||
# 收集Django日志
|
||||
if [ -d "logs" ]; then
|
||||
echo "📄 Django日志文件:"
|
||||
ls -la logs/
|
||||
if [ -f "logs/djangoblog.log" ]; then
|
||||
echo "🔍 最新日志内容:"
|
||||
tail -100 logs/djangoblog.log
|
||||
fi
|
||||
fi
|
||||
|
||||
# 显示数据库状态
|
||||
echo "🗄️ 数据库连接状态:"
|
||||
python -c "
|
||||
try:
|
||||
from django.db import connection
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT VERSION()')
|
||||
print(f'MySQL版本: {cursor.fetchone()[0]}')
|
||||
cursor.execute('SHOW TABLES')
|
||||
tables = cursor.fetchall()
|
||||
print(f'数据库表数量: {len(tables)}')
|
||||
except Exception as e:
|
||||
print(f'数据库连接错误: {e}')
|
||||
" || true
|
||||
|
||||
# Elasticsearch状态 (如果启用)
|
||||
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
||||
echo "🔍 Elasticsearch状态:"
|
||||
curl -s http://127.0.0.1:9200/_cluster/health?pretty || true
|
||||
fi
|
||||
|
||||
# 上传测试工件
|
||||
- name: 上传覆盖率HTML报告
|
||||
if: matrix.coverage == true && always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report-${{ steps.test-info.outputs.test_name }}
|
||||
path: htmlcov/
|
||||
retention-days: 30
|
||||
|
||||
# 性能统计
|
||||
- name: 测试性能统计
|
||||
if: always() && matrix.test-type != 'docker'
|
||||
run: |
|
||||
echo "⚡ 测试性能统计:"
|
||||
echo " 开始时间: $(date -d '@${{ job.started_at }}' '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo '未知')"
|
||||
echo " 当前时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
# 系统资源使用情况
|
||||
echo "💻 系统资源:"
|
||||
echo " CPU使用: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)%"
|
||||
echo " 内存使用: $(free -h | awk '/^Mem:/ {printf "%.1f%%", $3/$2 * 100}')"
|
||||
echo " 磁盘使用: $(df -h / | awk 'NR==2{printf "%s", $5}')"
|
||||
|
||||
# 测试结果汇总
|
||||
- name: 测试完成总结
|
||||
if: always()
|
||||
run: |
|
||||
echo "📋 ============ 测试执行总结 ============"
|
||||
echo " 🏷️ 测试类型: ${{ matrix.test-type }}"
|
||||
echo " 🐍 Python版本: ${{ matrix.python-version }}"
|
||||
echo " 🗄️ 数据库: ${{ matrix.database }}"
|
||||
echo " 🔍 Elasticsearch: ${{ matrix.elasticsearch }}"
|
||||
echo " 📊 覆盖率: ${{ matrix.coverage }}"
|
||||
echo " ⚡ 状态: ${{ job.status }}"
|
||||
echo " 📅 完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "============================================"
|
||||
|
||||
# 根据测试结果显示不同消息
|
||||
if [ "${{ job.status }}" = "success" ]; then
|
||||
echo "🎉 测试执行成功!"
|
||||
else
|
||||
echo "❌ 测试执行失败,请检查上面的日志"
|
||||
fi
|
||||
@ -0,0 +1,8 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/DjangoBlog.iml" filepath="$PROJECT_DIR$/.idea/DjangoBlog.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,10 +0,0 @@
|
||||
[run]
|
||||
source = .
|
||||
include = *.py
|
||||
omit =
|
||||
*migrations*
|
||||
*tests*
|
||||
*.html
|
||||
*whoosh_cn_backend*
|
||||
*settings.py*
|
||||
*venv*
|
||||
@ -1,136 +0,0 @@
|
||||
name: Django CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
|
||||
jobs:
|
||||
build-normal:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: ["3.10","3.11" ]
|
||||
|
||||
steps:
|
||||
- name: Start MySQL
|
||||
uses: samin/mysql-action@v1.3
|
||||
with:
|
||||
host port: 3306
|
||||
container port: 3306
|
||||
character set server: utf8mb4
|
||||
collation server: utf8mb4_general_ci
|
||||
mysql version: latest
|
||||
mysql root password: root
|
||||
mysql database: djangoblog
|
||||
mysql user: root
|
||||
mysql password: root
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Run Tests
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
run: |
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py test
|
||||
|
||||
build-with-es:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: ["3.10","3.11" ]
|
||||
|
||||
steps:
|
||||
- name: Start MySQL
|
||||
uses: samin/mysql-action@v1.3
|
||||
with:
|
||||
host port: 3306
|
||||
container port: 3306
|
||||
character set server: utf8mb4
|
||||
collation server: utf8mb4_general_ci
|
||||
mysql version: latest
|
||||
mysql root password: root
|
||||
mysql database: djangoblog
|
||||
mysql user: root
|
||||
mysql password: root
|
||||
|
||||
- name: Configure sysctl limits
|
||||
run: |
|
||||
sudo swapoff -a
|
||||
sudo sysctl -w vm.swappiness=1
|
||||
sudo sysctl -w fs.file-max=262144
|
||||
sudo sysctl -w vm.max_map_count=262144
|
||||
|
||||
- uses: miyataka/elasticsearch-github-actions@1
|
||||
|
||||
with:
|
||||
stack-version: '7.12.1'
|
||||
plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip'
|
||||
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Run Tests
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: 127.0.0.1:9200
|
||||
run: |
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
coverage run manage.py test
|
||||
coverage xml
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: djangoblog/djangoblog:dev
|
||||
@ -1,41 +0,0 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BasePlugin:
|
||||
# 插件元数据
|
||||
PLUGIN_NAME = None
|
||||
PLUGIN_DESCRIPTION = None
|
||||
PLUGIN_VERSION = None
|
||||
|
||||
def __init__(self):
|
||||
if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]):
|
||||
raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.")
|
||||
self.init_plugin()
|
||||
self.register_hooks()
|
||||
|
||||
def init_plugin(self):
|
||||
"""
|
||||
插件初始化逻辑
|
||||
子类可以重写此方法来实现特定的初始化操作
|
||||
"""
|
||||
logger.info(f'{self.PLUGIN_NAME} initialized.')
|
||||
|
||||
def register_hooks(self):
|
||||
"""
|
||||
注册插件钩子
|
||||
子类可以重写此方法来注册特定的钩子
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_plugin_info(self):
|
||||
"""
|
||||
获取插件信息
|
||||
:return: 包含插件元数据的字典
|
||||
"""
|
||||
return {
|
||||
'name': self.PLUGIN_NAME,
|
||||
'description': self.PLUGIN_DESCRIPTION,
|
||||
'version': self.PLUGIN_VERSION
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
ARTICLE_DETAIL_LOAD = 'article_detail_load'
|
||||
ARTICLE_CREATE = 'article_create'
|
||||
ARTICLE_UPDATE = 'article_update'
|
||||
ARTICLE_DELETE = 'article_delete'
|
||||
|
||||
ARTICLE_CONTENT_HOOK_NAME = "the_content"
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def load_plugins():
|
||||
"""
|
||||
Dynamically loads and initializes plugins from the 'plugins' directory.
|
||||
This function is intended to be called when the Django app registry is ready.
|
||||
"""
|
||||
for plugin_name in settings.ACTIVE_PLUGINS:
|
||||
plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name)
|
||||
if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')):
|
||||
try:
|
||||
__import__(f'plugins.{plugin_name}.plugin')
|
||||
logger.info(f"Successfully loaded plugin: {plugin_name}")
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e)
|
||||
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
.button {
|
||||
border: none;
|
||||
padding: 4px 80px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
let wait = 60;
|
||||
|
||||
function time(o) {
|
||||
if (wait == 0) {
|
||||
o.removeAttribute("disabled");
|
||||
o.value = "获取验证码";
|
||||
wait = 60
|
||||
return false
|
||||
} else {
|
||||
o.setAttribute("disabled", true);
|
||||
o.value = "重新发送(" + wait + ")";
|
||||
wait--;
|
||||
setTimeout(function () {
|
||||
time(o)
|
||||
},
|
||||
1000)
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("btn").onclick = function () {
|
||||
let id_email = $("#id_email")
|
||||
let token = $("*[name='csrfmiddlewaretoken']").val()
|
||||
let ts = this
|
||||
let myErr = $("#myErr")
|
||||
$.ajax(
|
||||
{
|
||||
url: "/forget_password_code/",
|
||||
type: "POST",
|
||||
data: {
|
||||
"email": id_email.val(),
|
||||
"csrfmiddlewaretoken": token
|
||||
},
|
||||
success: function (result) {
|
||||
if (result != "ok") {
|
||||
myErr.remove()
|
||||
id_email.after("<ul className='errorlist' id='myErr'><li>" + result + "</li></ul>")
|
||||
return
|
||||
}
|
||||
myErr.remove()
|
||||
time(ts)
|
||||
},
|
||||
error: function (e) {
|
||||
alert("发送失败,请重试")
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,13 @@
|
||||
/*!
|
||||
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||
* Copyright 2014-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
/*
|
||||
* See the Getting Started docs for more information:
|
||||
* http://getbootstrap.com/getting-started/#support-ie10-width
|
||||
*/
|
||||
@-ms-viewport { width: device-width; }
|
||||
@-o-viewport { width: device-width; }
|
||||
@viewport { width: device-width; }
|
||||
@ -0,0 +1,58 @@
|
||||
body {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.form-signin-heading {
|
||||
margin: 0 0 15px;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
color: #555;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
margin-bottom: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
height: auto;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.card {
|
||||
width: 304px;
|
||||
padding: 20px 25px 30px;
|
||||
margin: 0 auto 25px;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 2px;
|
||||
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
|
||||
}
|
||||
.card-signin {
|
||||
width: 354px;
|
||||
padding: 40px;
|
||||
}
|
||||
.card-signin .profile-img {
|
||||
display: block;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 221 B |
@ -0,0 +1,51 @@
|
||||
// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
|
||||
// IT'S JUST JUNK FOR OUR DOCS!
|
||||
// ++++++++++++++++++++++++++++++++++++++++++
|
||||
/*!
|
||||
* Copyright 2014-2015 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
|
||||
* details, see https://creativecommons.org/licenses/by/3.0/.
|
||||
*/
|
||||
// Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function emulatedIEMajorVersion() {
|
||||
var groups = /MSIE ([0-9.]+)/.exec(window.navigator.userAgent)
|
||||
if (groups === null) {
|
||||
return null
|
||||
}
|
||||
var ieVersionNum = parseInt(groups[1], 10)
|
||||
var ieMajorVersion = Math.floor(ieVersionNum)
|
||||
return ieMajorVersion
|
||||
}
|
||||
|
||||
function actualNonEmulatedIEMajorVersion() {
|
||||
// Detects the actual version of IE in use, even if it's in an older-IE emulation mode.
|
||||
// IE JavaScript conditional compilation docs: https://msdn.microsoft.com/library/121hztk3%28v=vs.94%29.aspx
|
||||
// @cc_on docs: https://msdn.microsoft.com/library/8ka90k2e%28v=vs.94%29.aspx
|
||||
var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line
|
||||
if (jscriptVersion === undefined) {
|
||||
return 11 // IE11+ not in emulation mode
|
||||
}
|
||||
if (jscriptVersion < 9) {
|
||||
return 8 // IE8 (or lower; haven't tested on IE<8)
|
||||
}
|
||||
return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode
|
||||
}
|
||||
|
||||
var ua = window.navigator.userAgent
|
||||
if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) {
|
||||
return // Opera, which might pretend to be IE
|
||||
}
|
||||
var emulated = emulatedIEMajorVersion()
|
||||
if (emulated === null) {
|
||||
return // Not IE
|
||||
}
|
||||
var nonEmulated = actualNonEmulatedIEMajorVersion()
|
||||
|
||||
if (emulated !== nonEmulated) {
|
||||
window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')
|
||||
}
|
||||
})();
|
||||
@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||
* Copyright 2014-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
// See the Getting Started docs for more information:
|
||||
// http://getbootstrap.com/getting-started/#support-ie10-width
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
|
||||
var msViewportStyle = document.createElement('style')
|
||||
msViewportStyle.appendChild(
|
||||
document.createTextNode(
|
||||
'@-ms-viewport{width:auto!important}'
|
||||
)
|
||||
)
|
||||
document.querySelector('head').appendChild(msViewportStyle)
|
||||
}
|
||||
|
||||
})();
|
||||
@ -0,0 +1,273 @@
|
||||
/*
|
||||
Styles for older IE versions (previous to IE9).
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
body.custom-background-empty {
|
||||
background-color: #fff;
|
||||
}
|
||||
body.custom-background-empty .site,
|
||||
body.custom-background-white .site {
|
||||
box-shadow: none;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.assistive-text,
|
||||
.site .screen-reader-text {
|
||||
clip: rect(1px 1px 1px 1px);
|
||||
}
|
||||
.full-width .site-content {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
img.size-full,
|
||||
img.size-large,
|
||||
img.header-image,
|
||||
img.wp-post-image,
|
||||
img[class*="align"],
|
||||
img[class*="wp-image-"],
|
||||
img[class*="attachment-"] {
|
||||
width: auto; /* Prevent stretching of full-size and large-size images with height and width attributes in IE8 */
|
||||
}
|
||||
.author-avatar {
|
||||
float: left;
|
||||
margin-top: 8px;
|
||||
margin-top: 0.571428571rem;
|
||||
}
|
||||
.author-description {
|
||||
float: right;
|
||||
width: 80%;
|
||||
}
|
||||
.site {
|
||||
box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3);
|
||||
margin: 48px auto;
|
||||
max-width: 960px;
|
||||
overflow: hidden;
|
||||
padding: 0 40px;
|
||||
}
|
||||
.site-content {
|
||||
float: left;
|
||||
width: 65.104166667%;
|
||||
}
|
||||
body.template-front-page .site-content,
|
||||
body.attachment .site-content,
|
||||
body.full-width .site-content {
|
||||
width: 100%;
|
||||
}
|
||||
.widget-area {
|
||||
float: right;
|
||||
width: 26.041666667%;
|
||||
}
|
||||
.site-header h1,
|
||||
.site-header h2 {
|
||||
text-align: left;
|
||||
}
|
||||
.site-header h1 {
|
||||
font-size: 26px;
|
||||
line-height: 1.846153846;
|
||||
}
|
||||
.main-navigation ul.nav-menu,
|
||||
.main-navigation div.nav-menu > ul {
|
||||
border-bottom: 1px solid #ededed;
|
||||
border-top: 1px solid #ededed;
|
||||
display: inline-block !important;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
.main-navigation ul {
|
||||
margin: 0;
|
||||
text-indent: 0;
|
||||
}
|
||||
.main-navigation li a,
|
||||
.main-navigation li {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ie7 .main-navigation li a,
|
||||
.ie7 .main-navigation li {
|
||||
display: inline;
|
||||
}
|
||||
.main-navigation li a {
|
||||
border-bottom: 0;
|
||||
color: #6a6a6a;
|
||||
line-height: 3.692307692;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.main-navigation li a:hover {
|
||||
color: #000;
|
||||
}
|
||||
.main-navigation li {
|
||||
margin: 0 40px 0 0;
|
||||
position: relative;
|
||||
}
|
||||
.main-navigation li ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
z-index: 1;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
}
|
||||
.ie7 .main-navigation li ul {
|
||||
clip: inherit;
|
||||
display: none;
|
||||
left: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
.main-navigation li ul ul,
|
||||
.ie7 .main-navigation li ul ul {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
}
|
||||
.main-navigation ul li:hover > ul,
|
||||
.main-navigation ul li:focus > ul,
|
||||
.main-navigation .focus > ul {
|
||||
border-left: 0;
|
||||
clip: inherit;
|
||||
overflow: inherit;
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
}
|
||||
.ie7 .main-navigation ul li:hover > ul,
|
||||
.ie7 .main-navigation ul li:focus > ul {
|
||||
display: block;
|
||||
}
|
||||
.main-navigation li ul li a {
|
||||
background: #efefef;
|
||||
border-bottom: 1px solid #ededed;
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
line-height: 2.181818182;
|
||||
padding: 8px 10px;
|
||||
width: 180px;
|
||||
}
|
||||
.main-navigation li ul li a:hover {
|
||||
background: #e3e3e3;
|
||||
color: #444;
|
||||
}
|
||||
.main-navigation .current-menu-item > a,
|
||||
.main-navigation .current-menu-ancestor > a,
|
||||
.main-navigation .current_page_item > a,
|
||||
.main-navigation .current_page_ancestor > a {
|
||||
color: #636363;
|
||||
font-weight: bold;
|
||||
}
|
||||
.main-navigation .menu-toggle {
|
||||
display: none;
|
||||
}
|
||||
.entry-header .entry-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
#respond form input[type="text"] {
|
||||
width: 46.333333333%;
|
||||
}
|
||||
#respond form textarea.blog-textarea {
|
||||
width: 79.666666667%;
|
||||
}
|
||||
.template-front-page .site-content,
|
||||
.template-front-page article {
|
||||
overflow: hidden;
|
||||
}
|
||||
.template-front-page.has-post-thumbnail article {
|
||||
float: left;
|
||||
width: 47.916666667%;
|
||||
}
|
||||
.entry-page-image {
|
||||
float: right;
|
||||
margin-bottom: 0;
|
||||
width: 47.916666667%;
|
||||
}
|
||||
/* IE Front Page Template Widget fix */
|
||||
.template-front-page .widget-area {
|
||||
clear: both;
|
||||
}
|
||||
.template-front-page .widget {
|
||||
width: 100% !important;
|
||||
border: none;
|
||||
}
|
||||
.template-front-page .widget-area .widget,
|
||||
.template-front-page .first.front-widgets,
|
||||
.template-front-page.two-sidebars .widget-area .front-widgets {
|
||||
float: left;
|
||||
margin-bottom: 24px;
|
||||
width: 51.875%;
|
||||
}
|
||||
.template-front-page .second.front-widgets,
|
||||
.template-front-page .widget-area .widget:nth-child(odd) {
|
||||
clear: right;
|
||||
}
|
||||
.template-front-page .first.front-widgets,
|
||||
.template-front-page .second.front-widgets,
|
||||
.template-front-page.two-sidebars .widget-area .front-widgets + .front-widgets {
|
||||
float: right;
|
||||
margin: 0 0 24px;
|
||||
width: 39.0625%;
|
||||
}
|
||||
.template-front-page.two-sidebars .widget,
|
||||
.template-front-page.two-sidebars .widget:nth-child(even) {
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
/* add input font for <IE9 Password Box to make the bullets show up */
|
||||
input[type="password"] {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* RTL overrides for IE7 and IE8
|
||||
-------------------------------------------------------------- */
|
||||
.rtl .site-header h1,
|
||||
.rtl .site-header h2 {
|
||||
text-align: right;
|
||||
}
|
||||
.rtl .widget-area,
|
||||
.rtl .author-description {
|
||||
float: left;
|
||||
}
|
||||
.rtl .author-avatar,
|
||||
.rtl .site-content {
|
||||
float: right;
|
||||
}
|
||||
.rtl .main-navigation ul.nav-menu,
|
||||
.rtl .main-navigation div.nav-menu > ul {
|
||||
text-align: right;
|
||||
}
|
||||
.rtl .main-navigation ul li ul li,
|
||||
.rtl .main-navigation ul li ul li ul li {
|
||||
margin-left: 40px;
|
||||
margin-right: auto;
|
||||
}
|
||||
.rtl .main-navigation li ul ul {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.ie7 .rtl .main-navigation li ul ul {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.ie7 .rtl .main-navigation ul li {
|
||||
z-index: 99;
|
||||
}
|
||||
.ie7 .rtl .main-navigation li ul {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.ie7 .rtl .main-navigation li {
|
||||
margin-right: auto;
|
||||
margin-left: 40px;
|
||||
}
|
||||
.ie7 .rtl .main-navigation li ul ul ul {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
/* Make clicks pass-through */
|
||||
#nprogress {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: red;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
/* Fancy blur effect */
|
||||
#nprogress .peg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
|
||||
opacity: 1.0;
|
||||
|
||||
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||
-ms-transform: rotate(3deg) translate(0px, -4px);
|
||||
transform: rotate(3deg) translate(0px, -4px);
|
||||
}
|
||||
|
||||
/* Remove these to get rid of the spinner */
|
||||
#nprogress .spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#nprogress .spinner-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
|
||||
border: solid 2px transparent;
|
||||
border-top-color: red;
|
||||
border-left-color: red;
|
||||
border-radius: 50%;
|
||||
|
||||
-webkit-animation: nprogress-spinner 400ms linear infinite;
|
||||
animation: nprogress-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent #nprogress .spinner,
|
||||
.nprogress-custom-parent #nprogress .bar {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@-webkit-keyframes nprogress-spinner {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes nprogress-spinner {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@ -0,0 +1,305 @@
|
||||
|
||||
.icon-sn-google {
|
||||
background-position: 0 -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-google {
|
||||
background-color: #4285f4;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.fa-sn-google {
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.icon-sn-github {
|
||||
background-position: -28px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-github {
|
||||
background-color: #333;
|
||||
background-position: -28px 0;
|
||||
}
|
||||
|
||||
.fa-sn-github {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.icon-sn-weibo {
|
||||
background-position: -56px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-weibo {
|
||||
background-color: #e90d24;
|
||||
background-position: -56px 0;
|
||||
}
|
||||
|
||||
.fa-sn-weibo {
|
||||
color: #e90d24;
|
||||
}
|
||||
|
||||
.icon-sn-qq {
|
||||
background-position: -84px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-qq {
|
||||
background-color: #0098e6;
|
||||
background-position: -84px 0;
|
||||
}
|
||||
|
||||
.fa-sn-qq {
|
||||
color: #0098e6;
|
||||
}
|
||||
|
||||
.icon-sn-twitter {
|
||||
background-position: -112px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-twitter {
|
||||
background-color: #50abf1;
|
||||
background-position: -112px 0;
|
||||
}
|
||||
|
||||
.fa-sn-twitter {
|
||||
color: #50abf1;
|
||||
}
|
||||
|
||||
.icon-sn-facebook {
|
||||
background-position: -140px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-facebook {
|
||||
background-color: #4862a3;
|
||||
background-position: -140px 0;
|
||||
}
|
||||
|
||||
.fa-sn-facebook {
|
||||
color: #4862a3;
|
||||
}
|
||||
|
||||
.icon-sn-renren {
|
||||
background-position: -168px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-renren {
|
||||
background-color: #197bc8;
|
||||
background-position: -168px 0;
|
||||
}
|
||||
|
||||
.fa-sn-renren {
|
||||
color: #197bc8;
|
||||
}
|
||||
|
||||
.icon-sn-tqq {
|
||||
background-position: -196px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-tqq {
|
||||
background-color: #1f9ed2;
|
||||
background-position: -196px 0;
|
||||
}
|
||||
|
||||
.fa-sn-tqq {
|
||||
color: #1f9ed2;
|
||||
}
|
||||
|
||||
.icon-sn-douban {
|
||||
background-position: -224px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-douban {
|
||||
background-color: #279738;
|
||||
background-position: -224px 0;
|
||||
}
|
||||
|
||||
.fa-sn-douban {
|
||||
color: #279738;
|
||||
}
|
||||
|
||||
.icon-sn-weixin {
|
||||
background-position: -252px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-weixin {
|
||||
background-color: #00b500;
|
||||
background-position: -252px 0;
|
||||
}
|
||||
|
||||
.fa-sn-weixin {
|
||||
color: #00b500;
|
||||
}
|
||||
|
||||
.icon-sn-dotted {
|
||||
background-position: -280px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-dotted {
|
||||
background-color: #eee;
|
||||
background-position: -280px 0;
|
||||
}
|
||||
|
||||
.fa-sn-dotted {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.icon-sn-site {
|
||||
background-position: -308px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-site {
|
||||
background-color: #00b500;
|
||||
background-position: -308px 0;
|
||||
}
|
||||
|
||||
.fa-sn-site {
|
||||
color: #00b500;
|
||||
}
|
||||
|
||||
.icon-sn-linkedin {
|
||||
background-position: -336px -28px;
|
||||
}
|
||||
|
||||
.icon-sn-bg-linkedin {
|
||||
background-color: #0077b9;
|
||||
background-position: -336px 0;
|
||||
}
|
||||
|
||||
.fa-sn-linkedin {
|
||||
color: #0077b9;
|
||||
}
|
||||
|
||||
[class*=icon-sn-] {
|
||||
display: inline-block;
|
||||
background-image: url('../img/icon-sn.svg');
|
||||
background-repeat: no-repeat;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
background-size: auto 56px;
|
||||
}
|
||||
|
||||
[class*=icon-sn-]:hover {
|
||||
opacity: .8;
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
|
||||
.btn-sn-google {
|
||||
background: #4285f4;
|
||||
}
|
||||
|
||||
.btn-sn-google:active, .btn-sn-google:focus, .btn-sn-google:hover {
|
||||
background: #2a75f3;
|
||||
}
|
||||
|
||||
.btn-sn-github {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.btn-sn-github:active, .btn-sn-github:focus, .btn-sn-github:hover {
|
||||
background: #262626;
|
||||
}
|
||||
|
||||
.btn-sn-weibo {
|
||||
background: #e90d24;
|
||||
}
|
||||
|
||||
.btn-sn-weibo:active, .btn-sn-weibo:focus, .btn-sn-weibo:hover {
|
||||
background: #d10c20;
|
||||
}
|
||||
|
||||
.btn-sn-qq {
|
||||
background: #0098e6;
|
||||
}
|
||||
|
||||
.btn-sn-qq:active, .btn-sn-qq:focus, .btn-sn-qq:hover {
|
||||
background: #0087cd;
|
||||
}
|
||||
|
||||
.btn-sn-twitter {
|
||||
background: #50abf1;
|
||||
}
|
||||
|
||||
.btn-sn-twitter:active, .btn-sn-twitter:focus, .btn-sn-twitter:hover {
|
||||
background: #38a0ef;
|
||||
}
|
||||
|
||||
.btn-sn-facebook {
|
||||
background: #4862a3;
|
||||
}
|
||||
|
||||
.btn-sn-facebook:active, .btn-sn-facebook:focus, .btn-sn-facebook:hover {
|
||||
background: #405791;
|
||||
}
|
||||
|
||||
.btn-sn-renren {
|
||||
background: #197bc8;
|
||||
}
|
||||
|
||||
.btn-sn-renren:active, .btn-sn-renren:focus, .btn-sn-renren:hover {
|
||||
background: #166db1;
|
||||
}
|
||||
|
||||
.btn-sn-tqq {
|
||||
background: #1f9ed2;
|
||||
}
|
||||
|
||||
.btn-sn-tqq:active, .btn-sn-tqq:focus, .btn-sn-tqq:hover {
|
||||
background: #1c8dbc;
|
||||
}
|
||||
|
||||
.btn-sn-douban {
|
||||
background: #279738;
|
||||
}
|
||||
|
||||
.btn-sn-douban:active, .btn-sn-douban:focus, .btn-sn-douban:hover {
|
||||
background: #228330;
|
||||
}
|
||||
|
||||
.btn-sn-weixin {
|
||||
background: #00b500;
|
||||
}
|
||||
|
||||
.btn-sn-weixin:active, .btn-sn-weixin:focus, .btn-sn-weixin:hover {
|
||||
background: #009c00;
|
||||
}
|
||||
|
||||
.btn-sn-dotted {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.btn-sn-dotted:active, .btn-sn-dotted:focus, .btn-sn-dotted:hover {
|
||||
background: #e1e1e1;
|
||||
}
|
||||
|
||||
.btn-sn-site {
|
||||
background: #00b500;
|
||||
}
|
||||
|
||||
.btn-sn-site:active, .btn-sn-site:focus, .btn-sn-site:hover {
|
||||
background: #009c00;
|
||||
}
|
||||
|
||||
.btn-sn-linkedin {
|
||||
background: #0077b9;
|
||||
}
|
||||
|
||||
.btn-sn-linkedin:active, .btn-sn-linkedin:focus, .btn-sn-linkedin:hover {
|
||||
background: #0067a0;
|
||||
}
|
||||
|
||||
[class*=btn-sn-], [class*=btn-sn-]:active, [class*=btn-sn-]:focus, [class*=btn-sn-]:hover {
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-sn-more {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.btn-sn-more, .btn-sn-more:active, .btn-sn-more:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[class*=btn-sn-] [class*=icon-sn-] {
|
||||
background-color: transparent;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue